package db import ( "testing" "time" ) const testLabelerSrc = "did:plc:labeler" // TestSetTakedown_RepoLevel verifies that an exact-repo row blocks only that // repo and leaves siblings untouched. func TestSetTakedown_RepoLevel(t *testing.T) { db, err := InitDB("file:TestSetTakedown_RepoLevel?mode=memory&cache=shared", LibsqlConfig{}) if err != nil { t.Fatalf("init db: %v", err) } defer db.Close() if err := SetTakedown(db, testLabelerSrc, "did:plc:user", "myrepo", time.Now().UTC()); err != nil { t.Fatalf("set takedown: %v", err) } hit, err := IsTakenDown(db, "did:plc:user", "myrepo") if err != nil { t.Fatalf("is taken down (target): %v", err) } if !hit { t.Fatal("expected target repo to be taken down") } hit, err = IsTakenDown(db, "did:plc:user", "otherrepo") if err != nil { t.Fatalf("is taken down (sibling): %v", err) } if hit { t.Fatal("sibling repo should not be taken down") } hit, err = IsTakenDown(db, "did:plc:other", "myrepo") if err != nil { t.Fatalf("is taken down (other did): %v", err) } if hit { t.Fatal("repo on different did should not be taken down") } } // TestSetTakedown_UserLevel verifies that a user-level row (repo=”) matches // any repo lookup for that DID. func TestSetTakedown_UserLevel(t *testing.T) { db, err := InitDB("file:TestSetTakedown_UserLevel?mode=memory&cache=shared", LibsqlConfig{}) if err != nil { t.Fatalf("init db: %v", err) } defer db.Close() if err := SetTakedown(db, testLabelerSrc, "did:plc:user", "", time.Now().UTC()); err != nil { t.Fatalf("set user-level takedown: %v", err) } for _, repo := range []string{"alpha", "beta", "anything"} { hit, err := IsTakenDown(db, "did:plc:user", repo) if err != nil { t.Fatalf("is taken down %q: %v", repo, err) } if !hit { t.Fatalf("user-level takedown should cover repo %q", repo) } } hit, err := IsTakenDown(db, "did:plc:bystander", "alpha") if err != nil { t.Fatalf("is taken down (bystander): %v", err) } if hit { t.Fatal("user-level takedown should not affect a different did") } } // TestRemoveTakedown_Idempotent verifies that removing a missing row is a // no-op and removing an existing row flips IsTakenDown back to false. func TestRemoveTakedown_Idempotent(t *testing.T) { db, err := InitDB("file:TestRemoveTakedown_Idempotent?mode=memory&cache=shared", LibsqlConfig{}) if err != nil { t.Fatalf("init db: %v", err) } defer db.Close() if err := RemoveTakedown(db, testLabelerSrc, "did:plc:ghost", "ghost"); err != nil { t.Fatalf("remove missing row: %v", err) } if err := SetTakedown(db, testLabelerSrc, "did:plc:user", "myrepo", time.Now().UTC()); err != nil { t.Fatalf("set takedown: %v", err) } if err := RemoveTakedown(db, testLabelerSrc, "did:plc:user", "myrepo"); err != nil { t.Fatalf("remove existing row: %v", err) } hit, err := IsTakenDown(db, "did:plc:user", "myrepo") if err != nil { t.Fatalf("is taken down after remove: %v", err) } if hit { t.Fatal("expected takedown to be cleared after remove") } } // TestSetTakedown_OnConflictUpdatesCts verifies that re-applying with a later // cts updates the row's timestamp instead of inserting a duplicate. func TestSetTakedown_OnConflictUpdatesCts(t *testing.T) { db, err := InitDB("file:TestSetTakedown_OnConflictUpdatesCts?mode=memory&cache=shared", LibsqlConfig{}) if err != nil { t.Fatalf("init db: %v", err) } defer db.Close() earlier := time.Now().Add(-2 * time.Hour).UTC().Truncate(time.Second) later := time.Now().UTC().Truncate(time.Second) if err := SetTakedown(db, testLabelerSrc, "did:plc:user", "repo", earlier); err != nil { t.Fatalf("set initial: %v", err) } if err := SetTakedown(db, testLabelerSrc, "did:plc:user", "repo", later); err != nil { t.Fatalf("set update: %v", err) } var stored string err = db.QueryRow( `SELECT cts FROM taken_down_subjects WHERE src=? AND did=? AND repo=?`, testLabelerSrc, "did:plc:user", "repo", ).Scan(&stored) if err != nil { t.Fatalf("query cts: %v", err) } got, err := time.Parse(time.RFC3339, stored) if err != nil { t.Fatalf("parse stored cts %q: %v", stored, err) } if !got.Equal(later) { t.Fatalf("expected cts to be updated to %s, got %s", later, got) } } // TestCursor_GetSetRoundtrip verifies that an unknown src returns 0 and // SetCursor/GetCursor round-trip and overwrite. func TestCursor_GetSetRoundtrip(t *testing.T) { db, err := InitDB("file:TestCursor_GetSetRoundtrip?mode=memory&cache=shared", LibsqlConfig{}) if err != nil { t.Fatalf("init db: %v", err) } defer db.Close() got, err := GetCursor(db, "did:plc:nobody") if err != nil { t.Fatalf("get cursor unknown: %v", err) } if got != 0 { t.Fatalf("expected 0 for unknown src, got %d", got) } if err := SetCursor(db, testLabelerSrc, 42); err != nil { t.Fatalf("set cursor: %v", err) } got, err = GetCursor(db, testLabelerSrc) if err != nil { t.Fatalf("get cursor after set: %v", err) } if got != 42 { t.Fatalf("expected 42, got %d", got) } if err := SetCursor(db, testLabelerSrc, 7); err != nil { t.Fatalf("set cursor (overwrite): %v", err) } got, err = GetCursor(db, testLabelerSrc) if err != nil { t.Fatalf("get cursor after overwrite: %v", err) } if got != 7 { t.Fatalf("expected 7 after overwrite, got %d", got) } } // TestSetTakedown_PerSrcIsolation verifies that two labeler srcs can each // own a row for the same (did, repo) and removing one src's row leaves the // other intact. func TestSetTakedown_PerSrcIsolation(t *testing.T) { db, err := InitDB("file:TestSetTakedown_PerSrcIsolation?mode=memory&cache=shared", LibsqlConfig{}) if err != nil { t.Fatalf("init db: %v", err) } defer db.Close() now := time.Now().UTC() if err := SetTakedown(db, "did:plc:labeler-a", "did:plc:user", "repo", now); err != nil { t.Fatalf("set src a: %v", err) } if err := SetTakedown(db, "did:plc:labeler-b", "did:plc:user", "repo", now); err != nil { t.Fatalf("set src b: %v", err) } if err := RemoveTakedown(db, "did:plc:labeler-a", "did:plc:user", "repo"); err != nil { t.Fatalf("remove src a: %v", err) } hit, err := IsTakenDown(db, "did:plc:user", "repo") if err != nil { t.Fatalf("is taken down: %v", err) } if !hit { t.Fatal("repo should still be taken down by src b after src a row removed") } }