package db import ( "testing" ) func migratedTestDB(t *testing.T) *DB { t.Helper() d := openTestDB(t) if err := d.Migrate(); err != nil { t.Fatalf("Migrate: %v", err) } return d } func TestWriteAndListAuditEvents(t *testing.T) { d := migratedTestDB(t) err := d.WriteAuditEvent("login_ok", "user-uuid-1", "", "", "10.0.0.1", nil) if err != nil { t.Fatalf("WriteAuditEvent: %v", err) } err = d.WriteAuditEvent("manifest_pushed", "user-uuid-1", "myapp", "sha256:abc", "10.0.0.1", map[string]string{"tag": "latest"}) if err != nil { t.Fatalf("WriteAuditEvent: %v", err) } events, err := d.ListAuditEvents(AuditFilter{}) if err != nil { t.Fatalf("ListAuditEvents: %v", err) } if len(events) != 2 { t.Fatalf("event count: got %d, want 2", len(events)) } // Most recent first. if events[0].EventType != "manifest_pushed" { t.Fatalf("first event type: got %q, want %q", events[0].EventType, "manifest_pushed") } if events[0].Repository != "myapp" { t.Fatalf("repository: got %q, want %q", events[0].Repository, "myapp") } if events[0].Digest != "sha256:abc" { t.Fatalf("digest: got %q, want %q", events[0].Digest, "sha256:abc") } if events[0].Details["tag"] != "latest" { t.Fatalf("details.tag: got %q, want %q", events[0].Details["tag"], "latest") } } func TestListAuditEventsFilterByType(t *testing.T) { d := migratedTestDB(t) _ = d.WriteAuditEvent("login_ok", "u1", "", "", "", nil) _ = d.WriteAuditEvent("manifest_pushed", "u1", "repo", "", "", nil) _ = d.WriteAuditEvent("login_ok", "u2", "", "", "", nil) events, err := d.ListAuditEvents(AuditFilter{EventType: "login_ok"}) if err != nil { t.Fatalf("ListAuditEvents: %v", err) } if len(events) != 2 { t.Fatalf("event count: got %d, want 2", len(events)) } for _, e := range events { if e.EventType != "login_ok" { t.Fatalf("unexpected event type: %q", e.EventType) } } } func TestListAuditEventsFilterByActor(t *testing.T) { d := migratedTestDB(t) _ = d.WriteAuditEvent("login_ok", "actor-a", "", "", "", nil) _ = d.WriteAuditEvent("login_ok", "actor-b", "", "", "", nil) events, err := d.ListAuditEvents(AuditFilter{ActorID: "actor-a"}) if err != nil { t.Fatalf("ListAuditEvents: %v", err) } if len(events) != 1 { t.Fatalf("event count: got %d, want 1", len(events)) } if events[0].ActorID != "actor-a" { t.Fatalf("actor_id: got %q, want %q", events[0].ActorID, "actor-a") } } func TestListAuditEventsFilterByRepository(t *testing.T) { d := migratedTestDB(t) _ = d.WriteAuditEvent("manifest_pushed", "u1", "repo-a", "", "", nil) _ = d.WriteAuditEvent("manifest_pushed", "u1", "repo-b", "", "", nil) events, err := d.ListAuditEvents(AuditFilter{Repository: "repo-a"}) if err != nil { t.Fatalf("ListAuditEvents: %v", err) } if len(events) != 1 { t.Fatalf("event count: got %d, want 1", len(events)) } } func TestListAuditEventsPagination(t *testing.T) { d := migratedTestDB(t) for i := range 5 { _ = d.WriteAuditEvent("login_ok", "u1", "", "", "", map[string]string{"i": string(rune('0' + i))}) } // First page. page1, err := d.ListAuditEvents(AuditFilter{Limit: 2, Offset: 0}) if err != nil { t.Fatalf("ListAuditEvents page 1: %v", err) } if len(page1) != 2 { t.Fatalf("page 1 count: got %d, want 2", len(page1)) } // Second page. page2, err := d.ListAuditEvents(AuditFilter{Limit: 2, Offset: 2}) if err != nil { t.Fatalf("ListAuditEvents page 2: %v", err) } if len(page2) != 2 { t.Fatalf("page 2 count: got %d, want 2", len(page2)) } // Pages should not overlap. if page1[0].ID == page2[0].ID { t.Fatal("page 1 and page 2 overlap") } // Third page (partial). page3, err := d.ListAuditEvents(AuditFilter{Limit: 2, Offset: 4}) if err != nil { t.Fatalf("ListAuditEvents page 3: %v", err) } if len(page3) != 1 { t.Fatalf("page 3 count: got %d, want 1", len(page3)) } } func TestListAuditEventsNullFields(t *testing.T) { d := migratedTestDB(t) // Write event with all optional fields empty. err := d.WriteAuditEvent("gc_started", "", "", "", "", nil) if err != nil { t.Fatalf("WriteAuditEvent: %v", err) } events, err := d.ListAuditEvents(AuditFilter{}) if err != nil { t.Fatalf("ListAuditEvents: %v", err) } if len(events) != 1 { t.Fatalf("event count: got %d, want 1", len(events)) } e := events[0] if e.ActorID != "" { t.Fatalf("actor_id: got %q, want empty", e.ActorID) } if e.Repository != "" { t.Fatalf("repository: got %q, want empty", e.Repository) } if e.Details != nil { t.Fatalf("details: got %v, want nil", e.Details) } }