package audit import ( "bytes" "context" "errors" "io" "strings" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/logger" ) // newSilentApprover 构建一个审计日志被丢弃的 Approver。 func newSilentApprover() *Approver { return NewApprover(logger.New(logger.Options{Out: io.Discard})) } func TestReviewAndApprove_Records(t *testing.T) { t.Parallel() ap, err := newSilentApprover().ReviewAndApprove(context.Background(), "worker", "run ls", "list the project dir") if err != nil { t.Fatalf("ReviewAndApprove unexpected error: %v", err) } if ap.Actor != "worker" { t.Fatalf("Actor = %q, want worker", ap.Actor) } if ap.Action != "run ls" { t.Fatalf("Action = %q, want run ls", ap.Action) } if ap.Description != "list the project dir" { t.Fatalf("Description = %q, want list the project dir", ap.Description) } if !ap.Allowed { t.Fatal("Allowed = false, want true") } if ap.Reviewer != "main" { t.Fatalf("Reviewer = %q, want main", ap.Reviewer) } if ap.At.IsZero() { t.Fatal("At is zero, want a timestamp") } } func TestApproval_Explain_Allowed(t *testing.T) { t.Parallel() ap := Approval{Actor: "worker", Allowed: true} if got, want := ap.Explain(), "「worker 想要干什么,我审查后批准了」"; got != want { t.Fatalf("Explain() = %q, want %q", got, want) } } func TestApproval_Explain_Denied(t *testing.T) { t.Parallel() ap := Approval{Actor: "worker", Allowed: false} if got, want := ap.Explain(), "「worker 想要干什么,我审查后拒绝了」"; got != want { t.Fatalf("Explain() = %q, want %q", got, want) } } func TestReviewAndApprove_Validation(t *testing.T) { t.Parallel() tests := []struct { name string actor string action string desc string wantErr error }{ {name: "blank actor", actor: "", action: "run ls", desc: "list dir", wantErr: ErrUnknownActor}, {name: "space actor", actor: " ", action: "run ls", desc: "list dir", wantErr: ErrUnknownActor}, {name: "blank action", actor: "worker", action: "", desc: "list dir", wantErr: ErrActionMissing}, {name: "blank explain", actor: "worker", action: "run ls", desc: "", wantErr: ErrExplainMissing}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() ap, err := newSilentApprover().ReviewAndApprove(context.Background(), tt.actor, tt.action, tt.desc) if err == nil { t.Fatalf("ReviewAndApprove returned nil error, want %v", tt.wantErr) } if !errors.Is(err, tt.wantErr) { t.Fatalf("error = %v, want Is(%v)", err, tt.wantErr) } if ap != nil { t.Fatalf("Approval = %+v, want nil on validation failure", ap) } }) } } func TestReviewAndApprove_WritesAuditLog(t *testing.T) { t.Parallel() var buf bytes.Buffer ap := NewApprover(logger.New(logger.Options{Level: "debug", Format: "json", Out: &buf})) _, err := ap.ReviewAndApprove(context.Background(), "worker", "run command", "delete prod db") if err != nil { t.Fatalf("ReviewAndApprove unexpected error: %v", err) } got := buf.String() for _, want := range []string{ `"source":"orchestration/audit"`, `"actor":"worker"`, `"action":"run command"`, `"explain":"delete prod db"`, `"allowed":true`, `"reviewer":"main"`, } { if !strings.Contains(got, want) { t.Fatalf("audit log missing %s, got: %s", want, got) } } } func TestReviewAndApprove_CanceledContext(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) cancel() _, err := newSilentApprover().ReviewAndApprove(ctx, "worker", "run ls", "list dir") if !errors.Is(err, context.Canceled) { t.Fatalf("error = %v, want context.Canceled", err) } } func TestNewApprover_NilLogger(t *testing.T) { t.Parallel() ap, err := NewApprover(nil).ReviewAndApprove(context.Background(), "worker", "run ls", "list dir") if err != nil { t.Fatalf("ReviewAndApprove with nil logger returned error: %v", err) } if ap == nil || ap.Actor != "worker" || !ap.Allowed { t.Fatalf("Approval with nil logger not recorded: %+v", ap) } }