package audit import ( "bytes" "context" "encoding/json" "log/slog" "testing" ) func TestNilLoggerIsSafe(t *testing.T) { var l *Logger // Must not panic. l.Log(context.Background(), Event{ Caller: "test", Operation: "issue", Outcome: "success", }) } func TestLogWritesJSON(t *testing.T) { var buf bytes.Buffer h := slog.NewJSONHandler(&buf, &slog.HandlerOptions{ Level: slog.Level(-10), // accept all levels }) l := New(h) l.Log(context.Background(), Event{ Caller: "kyle", Roles: []string{"admin"}, Operation: "issue", Engine: "ca", Mount: "pki", Outcome: "success", Detail: map[string]interface{}{"serial": "01:02:03"}, }) var entry map[string]interface{} if err := json.Unmarshal(buf.Bytes(), &entry); err != nil { t.Fatalf("invalid JSON: %v\nbody: %s", err, buf.String()) } checks := map[string]string{ "caller": "kyle", "operation": "issue", "engine": "ca", "mount": "pki", "outcome": "success", } for k, want := range checks { got, ok := entry[k].(string) if !ok || got != want { t.Errorf("field %q = %q, want %q", k, got, want) } } detail, ok := entry["detail"].(map[string]interface{}) if !ok { t.Fatalf("detail is not a map: %T", entry["detail"]) } if serial, _ := detail["serial"].(string); serial != "01:02:03" { t.Errorf("detail.serial = %q, want %q", serial, "01:02:03") } } func TestLogOmitsEmptyFields(t *testing.T) { var buf bytes.Buffer h := slog.NewJSONHandler(&buf, &slog.HandlerOptions{ Level: slog.Level(-10), }) l := New(h) l.Log(context.Background(), Event{ Caller: "kyle", Operation: "unseal", Outcome: "success", }) var entry map[string]interface{} if err := json.Unmarshal(buf.Bytes(), &entry); err != nil { t.Fatalf("invalid JSON: %v", err) } for _, key := range []string{"roles", "engine", "mount", "resource", "error", "detail"} { if _, ok := entry[key]; ok { t.Errorf("field %q should be omitted for empty value", key) } } } func TestLogIncludesError(t *testing.T) { var buf bytes.Buffer h := slog.NewJSONHandler(&buf, &slog.HandlerOptions{ Level: slog.Level(-10), }) l := New(h) l.Log(context.Background(), Event{ Caller: "operator", Operation: "unseal", Outcome: "denied", Error: "invalid password", }) var entry map[string]interface{} if err := json.Unmarshal(buf.Bytes(), &entry); err != nil { t.Fatalf("invalid JSON: %v", err) } if got, _ := entry["error"].(string); got != "invalid password" { t.Errorf("error = %q, want %q", got, "invalid password") } if got, _ := entry["outcome"].(string); got != "denied" { t.Errorf("outcome = %q, want %q", got, "denied") } } func TestNewWithNilHandlerReturnsNil(t *testing.T) { l := New(nil) if l != nil { t.Errorf("New(nil) = %v, want nil", l) } // Must not panic. l.Log(context.Background(), Event{Caller: "test", Operation: "test", Outcome: "success"}) }