Files

192 lines
4.4 KiB
Go

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)
}
})
}
}