package audit import ( "bytes" "context" "errors" "log/slog" "path/filepath" "strings" "testing" "git.cloudinit.dev/coreci/orca/internal/engine" "git.cloudinit.dev/coreci/orca/internal/store" ) func newTestAudit(t *testing.T) (*Audit, *store.AuditRepo, func()) { t.Helper() path := filepath.Join(t.TempDir(), "test.db") db, err := store.Open(path) if err != nil { t.Fatalf("open db: %v", err) } repo := store.NewAuditRepo(db) eng := engine.NewAudit(repo, nil) return New(eng), repo, func() { _ = db.Close() } } func TestAudit_Emit(t *testing.T) { a, repo, cleanup := newTestAudit(t) defer cleanup() ctx := context.Background() a.Emit(ctx, ActionCertIssued, "cert:node-1", ResultSuccess, map[string]any{"cn": "node-1"}) entries, err := repo.List(ctx, 10) if err != nil { t.Fatalf("List: %v", err) } if len(entries) != 1 { t.Fatalf("expected 1 audit entry, got %d", len(entries)) } e := entries[0] if e.Action != string(ActionCertIssued) { t.Errorf("action: got %q, want %q", e.Action, ActionCertIssued) } if e.Result != string(ResultSuccess) { t.Errorf("result: got %q, want %q", e.Result, ResultSuccess) } if e.Resource != "cert:node-1" { t.Errorf("resource: got %q, want cert:node-1", e.Resource) } if e.Actor != "security" { t.Errorf("actor: got %q, want security", e.Actor) } if e.Error != "" { t.Errorf("error: got %q, want empty", e.Error) } } func TestAudit_EmitWithErr(t *testing.T) { a, repo, cleanup := newTestAudit(t) defer cleanup() ctx := context.Background() a.EmitWithErr(ctx, ActionNodeHandshakeFail, "hs:node-2", errors.New("bad cert"), nil) entries, err := repo.List(ctx, 10) if err != nil { t.Fatalf("List: %v", err) } if len(entries) != 1 { t.Fatalf("expected 1 audit entry, got %d", len(entries)) } e := entries[0] if e.Result != string(ResultFailure) { t.Errorf("result: got %q, want %q", e.Result, ResultFailure) } if !strings.Contains(e.Error, "bad cert") { t.Errorf("error: got %q, want it to contain 'bad cert'", e.Error) } } func TestAudit_LogHandshakeOK(t *testing.T) { var buf bytes.Buffer logger := slog.New(slog.NewTextHandler(&buf, nil)) LogHandshakeOK(logger, "peer-1", "AA:BB:CC") out := buf.String() for _, want := range []string{"event=mtls.handshake", "result=ok", "peer=peer-1", "cert_fp=AA:BB:CC"} { if !strings.Contains(out, want) { t.Errorf("LogHandshakeOK: output missing %q\noutput: %s", want, out) } } } func TestAudit_LogHandshakeFailed(t *testing.T) { var buf bytes.Buffer logger := slog.New(slog.NewTextHandler(&buf, nil)) LogHandshakeFailed(logger, "peer-2", "", errors.New("tls: handshake")) out := buf.String() for _, want := range []string{"event=mtls.handshake", "result=failed", "peer=peer-2", "err=\"tls: handshake\""} { if !strings.Contains(out, want) { t.Errorf("LogHandshakeFailed: output missing %q\noutput: %s", want, out) } } } func TestAudit_LogHandshake_NilLogger(t *testing.T) { LogHandshakeOK(nil, "p", "fp") LogHandshakeFailed(nil, "p", "fp", errors.New("x")) } func TestAudit_NilSafe(t *testing.T) { var a *Audit a.Emit(context.Background(), ActionCertIssued, "x", ResultSuccess, nil) a.EmitWithErr(context.Background(), ActionCertIssued, "x", errors.New("y"), nil) } func TestAction_String(t *testing.T) { if got := ActionCertIssued.String(); got != "cert.issued" { t.Errorf("ActionCertIssued.String(): got %q, want cert.issued", got) } if got := ActionNodeHandshakeOK.String(); got != "node.handshake_ok" { t.Errorf("ActionNodeHandshakeOK.String(): got %q, want node.handshake_ok", got) } } func TestResult_String(t *testing.T) { if got := ResultSuccess.String(); got != "success" { t.Errorf("ResultSuccess.String(): got %q, want success", got) } if got := ResultFailure.String(); got != "failure" { t.Errorf("ResultFailure.String(): got %q, want failure", got) } } func TestFormatAction(t *testing.T) { got := FormatAction(ActionCertIssued, ResultSuccess) want := "action=cert.issued result=success" if got != want { t.Errorf("FormatAction: got %q, want %q", got, want) } }