package db import ( "testing" "time" "git.wntrmute.dev/kyle/mcias/internal/model" ) // openTestDB is defined in db_test.go in this package; reused here. func TestListTokensForAccount(t *testing.T) { database := openTestDB(t) acc, err := database.CreateAccount("tokenuser", model.AccountTypeHuman, "hash") if err != nil { t.Fatalf("create account: %v", err) } // No tokens yet. records, err := database.ListTokensForAccount(acc.ID) if err != nil { t.Fatalf("list tokens (empty): %v", err) } if len(records) != 0 { t.Fatalf("expected 0 tokens, got %d", len(records)) } // Track two tokens. now := time.Now().UTC() if err := database.TrackToken("jti-aaa", acc.ID, now, now.Add(time.Hour)); err != nil { t.Fatalf("track token 1: %v", err) } if err := database.TrackToken("jti-bbb", acc.ID, now.Add(time.Second), now.Add(2*time.Hour)); err != nil { t.Fatalf("track token 2: %v", err) } records, err = database.ListTokensForAccount(acc.ID) if err != nil { t.Fatalf("list tokens: %v", err) } if len(records) != 2 { t.Fatalf("expected 2 tokens, got %d", len(records)) } // Newest first. if records[0].JTI != "jti-bbb" { t.Errorf("expected jti-bbb first, got %s", records[0].JTI) } if records[1].JTI != "jti-aaa" { t.Errorf("expected jti-aaa second, got %s", records[1].JTI) } } func TestListAuditEventsFilter(t *testing.T) { database := openTestDB(t) acc1, err := database.CreateAccount("audituser1", model.AccountTypeHuman, "hash") if err != nil { t.Fatalf("create account 1: %v", err) } acc2, err := database.CreateAccount("audituser2", model.AccountTypeHuman, "hash") if err != nil { t.Fatalf("create account 2: %v", err) } // Write events for both accounts with different types. if err := database.WriteAuditEvent(model.EventLoginOK, &acc1.ID, nil, "1.2.3.4", ""); err != nil { t.Fatalf("write audit event 1: %v", err) } if err := database.WriteAuditEvent(model.EventLoginFail, &acc2.ID, nil, "5.6.7.8", ""); err != nil { t.Fatalf("write audit event 2: %v", err) } if err := database.WriteAuditEvent(model.EventTokenIssued, &acc1.ID, nil, "", ""); err != nil { t.Fatalf("write audit event 3: %v", err) } // Filter by account. events, err := database.ListAuditEvents(AuditQueryParams{AccountID: &acc1.ID}) if err != nil { t.Fatalf("list by account: %v", err) } if len(events) != 2 { t.Fatalf("expected 2 events for acc1, got %d", len(events)) } // Filter by event type. events, err = database.ListAuditEvents(AuditQueryParams{EventType: model.EventLoginFail}) if err != nil { t.Fatalf("list by type: %v", err) } if len(events) != 1 { t.Fatalf("expected 1 login_fail event, got %d", len(events)) } // Filter by since (after all events). future := time.Now().Add(time.Hour) events, err = database.ListAuditEvents(AuditQueryParams{Since: &future}) if err != nil { t.Fatalf("list by since (future): %v", err) } if len(events) != 0 { t.Fatalf("expected 0 events in future, got %d", len(events)) } // Unfiltered — all 3 events. events, err = database.ListAuditEvents(AuditQueryParams{}) if err != nil { t.Fatalf("list unfiltered: %v", err) } if len(events) != 3 { t.Fatalf("expected 3 events unfiltered, got %d", len(events)) } _ = acc2 } func TestTailAuditEvents(t *testing.T) { database := openTestDB(t) acc, err := database.CreateAccount("tailuser", model.AccountTypeHuman, "hash") if err != nil { t.Fatalf("create account: %v", err) } // Write 5 events. for i := 0; i < 5; i++ { if err := database.WriteAuditEvent(model.EventLoginOK, &acc.ID, nil, "", ""); err != nil { t.Fatalf("write audit event %d: %v", i, err) } } // Tail 3 — should return the 3 most recent, oldest-first. events, err := database.TailAuditEvents(3) if err != nil { t.Fatalf("tail audit events: %v", err) } if len(events) != 3 { t.Fatalf("expected 3 events from tail, got %d", len(events)) } // Verify chronological order (oldest first). for i := 1; i < len(events); i++ { if events[i].EventTime.Before(events[i-1].EventTime) { // Allow equal times (written in same second). if events[i].EventTime.Equal(events[i-1].EventTime) { continue } t.Errorf("events not in chronological order at index %d", i) } } // Tail more than exist — should return all 5. events, err = database.TailAuditEvents(100) if err != nil { t.Fatalf("tail 100: %v", err) } if len(events) != 5 { t.Fatalf("expected 5 from tail(100), got %d", len(events)) } } func TestListAuditEventsCombinedFilters(t *testing.T) { database := openTestDB(t) acc, err := database.CreateAccount("combo", model.AccountTypeHuman, "hash") if err != nil { t.Fatalf("create account: %v", err) } if err := database.WriteAuditEvent(model.EventLoginOK, &acc.ID, nil, "", ""); err != nil { t.Fatalf("write event: %v", err) } // Combine account + type filters. events, err := database.ListAuditEvents(AuditQueryParams{ AccountID: &acc.ID, EventType: model.EventLoginOK, }) if err != nil { t.Fatalf("combined filter: %v", err) } if len(events) != 1 { t.Fatalf("expected 1 event, got %d", len(events)) } // Combine account + wrong type. events, err = database.ListAuditEvents(AuditQueryParams{ AccountID: &acc.ID, EventType: model.EventLoginFail, }) if err != nil { t.Fatalf("combined filter no match: %v", err) } if len(events) != 0 { t.Fatalf("expected 0 events, got %d", len(events)) } }