Files
at-container-registry/pkg/appview/db/delete_test.go
T
Evan JarrettandClaude Opus 5 454a6bad3d db: key manifests by manifest_key and drop the rowid
Completes the swap 0033 set up. layers and manifest_references move onto
manifest_key and manifests.id is gone, which removes the last node-allocated
identifier in the AppView schema.

Statement order in 0034 is load-bearing. With foreign keys on, DROP TABLE
performs an implicit DELETE FROM, so dropping manifests while layers still holds
an ON DELETE CASCADE reference deletes every layer row. Migration 0009 did
exactly that; it went unnoticed because the Jetstream backfill rebuilds layers
from PDS records, so the damage healed itself. PRAGMA foreign_keys is no help:
it is a no-op inside a transaction and migrations run in one. So the new
children are built pointing at manifests_new, the old children are dropped
first, and only then is the old manifests table dropped, by which point nothing
references it. Verified both behaviors before relying on them.

manifest_key is declared NOT NULL as well as PRIMARY KEY, because in SQLite a
PRIMARY KEY column still accepts NULL unless it is INTEGER PRIMARY KEY. That
constraint immediately caught four test helpers inserting manifests without one.

Five queries used MAX(id) as "the newest manifest in this repo", which I had
previously reported as absent after grepping only for ORDER BY. A derived key
has no ordering, so recency now comes from created_at with manifest_key as a
deterministic tiebreak. This is a real behavior change, and a fix: the two
disagree whenever a manifest is indexed out of order, which the backfill does
routinely, and created_at is the push time these queries always wanted. Both
directions are tested, including that ties resolve the same way every run.

InsertManifest and BatchInsertManifests no longer read anything back. The key is
derived from (did, repository, digest), so the writer knows it before the
statement runs: the select-back, its per-DID IN list, and the "manifest missing
id after batch insert" branch all go away, along with the UNIQUE-conflict
fallback that existed only to recover a rowid.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 22:44:41 -05:00

309 lines
8.7 KiB
Go

package db
import (
"fmt"
"testing"
"time"
)
func TestDeleteUserDataFull_DeletesAllData(t *testing.T) {
db, err := InitDB(":memory:", LibsqlConfig{})
if err != nil {
t.Fatalf("Failed to init database: %v", err)
}
defer db.Close()
// Create test user
testUser := &User{
DID: "did:plc:test123",
Handle: "test.bsky.social",
PDSEndpoint: "https://bsky.social",
LastSeen: time.Now(),
}
if err := UpsertUser(db, testUser); err != nil {
t.Fatalf("Failed to create user: %v", err)
}
// Create manifest
_, err = db.Exec(`
INSERT INTO manifests (manifest_key, did, repository, digest, hold_endpoint, schema_version, media_type, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`, ManifestKey(testUser.DID, "myapp", "sha256:abc123"),
testUser.DID, "myapp", "sha256:abc123", "did:web:hold.example.com", 2,
"application/vnd.oci.image.manifest.v1+json", time.Now())
if err != nil {
t.Fatalf("Failed to create manifest: %v", err)
}
// Create tag
_, err = db.Exec(`
INSERT INTO tags (did, repository, tag, digest, created_at)
VALUES (?, ?, ?, ?, ?)
`, testUser.DID, "myapp", "latest", "sha256:abc123", time.Now())
if err != nil {
t.Fatalf("Failed to create tag: %v", err)
}
// Create hold membership data (non-cascading)
_, err = db.Exec(`
INSERT INTO hold_crew_approvals (hold_did, user_did, approved_at, expires_at)
VALUES (?, ?, ?, ?)
`, "did:web:hold.example.com", testUser.DID, time.Now(), time.Now().Add(24*time.Hour))
if err != nil {
t.Fatalf("Failed to create crew approval: %v", err)
}
_, err = db.Exec(`
INSERT INTO hold_crew_members (hold_did, member_did, rkey, permissions)
VALUES (?, ?, ?, ?)
`, "did:web:hold.example.com", testUser.DID, "member1", `["blob:read","blob:write"]`)
if err != nil {
t.Fatalf("Failed to create crew member: %v", err)
}
// Create OAuth store
oauthStore := NewOAuthStore(db)
// Delete all user data
err = DeleteUserDataFull(db, oauthStore, testUser.DID)
if err != nil {
t.Fatalf("DeleteUserDataFull failed: %v", err)
}
// Verify user was deleted
var count int
err = db.QueryRow("SELECT COUNT(*) FROM users WHERE did = ?", testUser.DID).Scan(&count)
if err != nil {
t.Fatalf("Failed to query users: %v", err)
}
if count != 0 {
t.Error("Expected user to be deleted")
}
// Verify manifests were cascade deleted
err = db.QueryRow("SELECT COUNT(*) FROM manifests WHERE did = ?", testUser.DID).Scan(&count)
if err != nil {
t.Fatalf("Failed to query manifests: %v", err)
}
if count != 0 {
t.Error("Expected manifests to be cascade deleted")
}
// Verify tags were cascade deleted
err = db.QueryRow("SELECT COUNT(*) FROM tags WHERE did = ?", testUser.DID).Scan(&count)
if err != nil {
t.Fatalf("Failed to query tags: %v", err)
}
if count != 0 {
t.Error("Expected tags to be cascade deleted")
}
// Verify hold membership data was deleted
err = db.QueryRow("SELECT COUNT(*) FROM hold_crew_approvals WHERE user_did = ?", testUser.DID).Scan(&count)
if err != nil {
t.Fatalf("Failed to query crew approvals: %v", err)
}
if count != 0 {
t.Error("Expected crew approvals to be deleted")
}
err = db.QueryRow("SELECT COUNT(*) FROM hold_crew_members WHERE member_did = ?", testUser.DID).Scan(&count)
if err != nil {
t.Fatalf("Failed to query crew members: %v", err)
}
if count != 0 {
t.Error("Expected crew members to be deleted")
}
}
func TestDeleteUserDataFull_DoesNotAffectOtherUsers(t *testing.T) {
db, err := InitDB(":memory:", LibsqlConfig{})
if err != nil {
t.Fatalf("Failed to init database: %v", err)
}
defer db.Close()
// Create two users
user1 := &User{
DID: "did:plc:user1",
Handle: "user1.bsky.social",
PDSEndpoint: "https://bsky.social",
LastSeen: time.Now(),
}
user2 := &User{
DID: "did:plc:user2",
Handle: "user2.bsky.social",
PDSEndpoint: "https://bsky.social",
LastSeen: time.Now(),
}
if err := UpsertUser(db, user1); err != nil {
t.Fatalf("Failed to create user1: %v", err)
}
if err := UpsertUser(db, user2); err != nil {
t.Fatalf("Failed to create user2: %v", err)
}
// Create manifests for both users
for _, user := range []*User{user1, user2} {
_, err = db.Exec(`
INSERT INTO manifests (manifest_key, did, repository, digest, hold_endpoint, schema_version, media_type, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`, ManifestKey(user.DID, "myapp", "sha256:"+user.DID),
user.DID, "myapp", "sha256:"+user.DID, "did:web:hold.example.com", 2,
"application/vnd.oci.image.manifest.v1+json", time.Now())
if err != nil {
t.Fatalf("Failed to create manifest for %s: %v", user.Handle, err)
}
}
// Create hold membership data for both users
for i, user := range []*User{user1, user2} {
_, err = db.Exec(`
INSERT INTO hold_crew_members (hold_did, member_did, rkey, permissions)
VALUES (?, ?, ?, ?)
`, "did:web:hold.example.com", user.DID, fmt.Sprintf("member%d", i+1), `["blob:read"]`)
if err != nil {
t.Fatalf("Failed to create crew member for %s: %v", user.Handle, err)
}
}
oauthStore := NewOAuthStore(db)
// Delete only user1's data
err = DeleteUserDataFull(db, oauthStore, user1.DID)
if err != nil {
t.Fatalf("DeleteUserDataFull failed: %v", err)
}
// Verify user1 was deleted
var count int
err = db.QueryRow("SELECT COUNT(*) FROM users WHERE did = ?", user1.DID).Scan(&count)
if err != nil {
t.Fatalf("Failed to query users: %v", err)
}
if count != 0 {
t.Error("Expected user1 to be deleted")
}
// Verify user2 still exists
err = db.QueryRow("SELECT COUNT(*) FROM users WHERE did = ?", user2.DID).Scan(&count)
if err != nil {
t.Fatalf("Failed to query users: %v", err)
}
if count != 1 {
t.Error("Expected user2 to still exist")
}
// Verify user2's manifests still exist
err = db.QueryRow("SELECT COUNT(*) FROM manifests WHERE did = ?", user2.DID).Scan(&count)
if err != nil {
t.Fatalf("Failed to query manifests: %v", err)
}
if count != 1 {
t.Error("Expected user2's manifest to still exist")
}
// Verify user2's crew membership still exists
err = db.QueryRow("SELECT COUNT(*) FROM hold_crew_members WHERE member_did = ?", user2.DID).Scan(&count)
if err != nil {
t.Fatalf("Failed to query crew members: %v", err)
}
if count != 1 {
t.Error("Expected user2's crew membership to still exist")
}
}
func TestDeleteUserDataFull_HandlesNonExistentUser(t *testing.T) {
db, err := InitDB(":memory:", LibsqlConfig{})
if err != nil {
t.Fatalf("Failed to init database: %v", err)
}
defer db.Close()
oauthStore := NewOAuthStore(db)
// Try to delete non-existent user - should not error
err = DeleteUserDataFull(db, oauthStore, "did:plc:nonexistent")
if err != nil {
t.Errorf("Expected no error for non-existent user, got: %v", err)
}
}
func TestDeleteUserDataFull_WithNilOAuthStore(t *testing.T) {
db, err := InitDB(":memory:", LibsqlConfig{})
if err != nil {
t.Fatalf("Failed to init database: %v", err)
}
defer db.Close()
testUser := &User{
DID: "did:plc:test123",
Handle: "test.bsky.social",
PDSEndpoint: "https://bsky.social",
LastSeen: time.Now(),
}
if err := UpsertUser(db, testUser); err != nil {
t.Fatalf("Failed to create user: %v", err)
}
// Delete with nil OAuth store - should still work
err = DeleteUserDataFull(db, nil, testUser.DID)
if err != nil {
t.Errorf("Expected no error with nil OAuth store, got: %v", err)
}
// Verify user was deleted
var count int
err = db.QueryRow("SELECT COUNT(*) FROM users WHERE did = ?", testUser.DID).Scan(&count)
if err != nil {
t.Fatalf("Failed to query users: %v", err)
}
if count != 0 {
t.Error("Expected user to be deleted")
}
}
func TestDeleteUserDataFull_DeletesDenials(t *testing.T) {
db, err := InitDB(":memory:", LibsqlConfig{})
if err != nil {
t.Fatalf("Failed to init database: %v", err)
}
defer db.Close()
testUser := &User{
DID: "did:plc:test123",
Handle: "test.bsky.social",
PDSEndpoint: "https://bsky.social",
LastSeen: time.Now(),
}
if err := UpsertUser(db, testUser); err != nil {
t.Fatalf("Failed to create user: %v", err)
}
// Create denial record
_, err = db.Exec(`
INSERT INTO hold_crew_denials (hold_did, user_did, denial_count, next_retry_at, last_denied_at)
VALUES (?, ?, ?, ?, ?)
`, "did:web:hold.example.com", testUser.DID, 1, time.Now().Add(24*time.Hour), time.Now())
if err != nil {
t.Fatalf("Failed to create crew denial: %v", err)
}
oauthStore := NewOAuthStore(db)
err = DeleteUserDataFull(db, oauthStore, testUser.DID)
if err != nil {
t.Fatalf("DeleteUserDataFull failed: %v", err)
}
// Verify denial was deleted
var count int
err = db.QueryRow("SELECT COUNT(*) FROM hold_crew_denials WHERE user_did = ?", testUser.DID).Scan(&count)
if err != nil {
t.Fatalf("Failed to query crew denials: %v", err)
}
if count != 0 {
t.Error("Expected crew denials to be deleted")
}
}