package labeler import ( "database/sql" "testing" "time" _ "github.com/tursodatabase/go-libsql" ) func newTestCache(t *testing.T) *Cache { t.Helper() db, err := sql.Open("libsql", ":memory:") if err != nil { t.Fatalf("open in-memory db: %v", err) } t.Cleanup(func() { _ = db.Close() }) c, err := NewCache(db) if err != nil { t.Fatalf("NewCache: %v", err) } return c } func TestCacheSetAndIsTakenDown(t *testing.T) { c := newTestCache(t) uri := "at://did:plc:alice/io.atcr.manifest/abc" cts := time.Now().UTC().Add(-time.Hour) if _, ok := c.IsTakenDown(uri); ok { t.Fatalf("expected not taken down before Set") } if err := c.Set(uri, "did:web:labeler", cts); err != nil { t.Fatalf("Set: %v", err) } got, ok := c.IsTakenDown(uri) if !ok { t.Fatalf("expected taken down after Set") } if !got.Equal(cts) { t.Fatalf("cts = %v, want %v", got, cts) } } func TestCacheNegateRemovesEntry(t *testing.T) { c := newTestCache(t) uri := "at://did:plc:alice/io.atcr.manifest/abc" if err := c.Set(uri, "did:web:labeler", time.Now()); err != nil { t.Fatal(err) } if err := c.Negate(uri); err != nil { t.Fatalf("Negate: %v", err) } if _, ok := c.IsTakenDown(uri); ok { t.Fatalf("expected not taken down after Negate") } } func TestCacheUserLevelTakedownAppliesToAllManifests(t *testing.T) { c := newTestCache(t) userURI := "at://did:plc:alice" cts := time.Now().UTC() if err := c.Set(userURI, "did:web:labeler", cts); err != nil { t.Fatal(err) } manifestURI := "at://did:plc:alice/io.atcr.manifest/anything" got, ok := c.IsTakenDown(manifestURI) if !ok { t.Fatalf("user-level takedown should apply to manifest URI") } if !got.Equal(cts) { t.Fatalf("cts = %v, want %v", got, cts) } otherURI := "at://did:plc:bob/io.atcr.manifest/x" if _, ok := c.IsTakenDown(otherURI); ok { t.Fatalf("user-level takedown for alice must not affect bob") } } func TestCacheChoosesEarliestCtsAcrossSources(t *testing.T) { c := newTestCache(t) earlier := time.Now().UTC().Add(-2 * time.Hour) later := time.Now().UTC() manifestURI := "at://did:plc:alice/io.atcr.manifest/x" userURI := "at://did:plc:alice" // Per-manifest later, user-level earlier — IsTakenDown should report the earlier one. if err := c.Set(manifestURI, "did:web:labeler", later); err != nil { t.Fatal(err) } if err := c.Set(userURI, "did:web:labeler", earlier); err != nil { t.Fatal(err) } got, ok := c.IsTakenDown(manifestURI) if !ok { t.Fatalf("expected taken down") } if !got.Equal(earlier) { t.Fatalf("cts = %v, want earliest (%v)", got, earlier) } } func TestCachePersistsAcrossInstances(t *testing.T) { db, err := sql.Open("libsql", ":memory:") if err != nil { t.Fatalf("open: %v", err) } t.Cleanup(func() { _ = db.Close() }) first, err := NewCache(db) if err != nil { t.Fatalf("first NewCache: %v", err) } uri := "at://did:plc:alice/io.atcr.manifest/abc" cts := time.Now().UTC() if err := first.Set(uri, "did:web:labeler", cts); err != nil { t.Fatal(err) } // New cache instance, same DB — should warm-load entry. second, err := NewCache(db) if err != nil { t.Fatalf("second NewCache: %v", err) } if _, ok := second.IsTakenDown(uri); !ok { t.Fatalf("second cache should see persisted takedown") } } func TestCacheCursorRoundTrip(t *testing.T) { c := newTestCache(t) const labeler = "did:web:labeler" got, err := c.GetCursor(labeler) if err != nil { t.Fatal(err) } if got != 0 { t.Fatalf("initial cursor = %d, want 0", got) } if err := c.SetCursor(labeler, 42); err != nil { t.Fatal(err) } got, err = c.GetCursor(labeler) if err != nil { t.Fatal(err) } if got != 42 { t.Fatalf("cursor = %d, want 42", got) } if err := c.SetCursor(labeler, 100); err != nil { t.Fatal(err) } got, err = c.GetCursor(labeler) if err != nil { t.Fatal(err) } if got != 100 { t.Fatalf("cursor after upsert = %d, want 100", got) } } func TestIsExpired(t *testing.T) { tests := []struct { name string cts time.Time grace time.Duration expired bool }{ {"in-window", time.Now().Add(-time.Hour), 24 * time.Hour, false}, {"past-window", time.Now().Add(-48 * time.Hour), 24 * time.Hour, true}, {"zero grace expires immediately", time.Now(), 0, true}, {"negative grace expires immediately", time.Now(), -time.Hour, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := IsExpired(tt.cts, tt.grace); got != tt.expired { t.Fatalf("IsExpired = %v, want %v", got, tt.expired) } }) } }