package tenantpurge import ( "context" "os" "path/filepath" "testing" "github.com/klarkxy/nekonest-cloud/relay/internal/tenantfs" ) const testTenantID = "tenant_0123456789abcdef0123456789abcdef" func TestPurgeDeletesLiveDataAndEveryBackupIdempotently(t *testing.T) { dataRoot := t.TempDir() backupRoot := t.TempDir() paths, err := tenantfs.Resolve(dataRoot, testTenantID) if err != nil { t.Fatal(err) } if err := os.WriteFile(paths.Database, []byte("sqlite"), 0o600); err != nil { t.Fatal(err) } backupTenant := filepath.Join(backupRoot, filepath.Base(paths.Root)) for _, name := range []string{"g1", "g2"} { path := filepath.Join(backupTenant, name) if err := os.MkdirAll(path, 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(path, "manifest.json"), []byte("{}"), 0o600); err != nil { t.Fatal(err) } } first, err := Purge(context.Background(), dataRoot, backupRoot, testTenantID, 3) if err != nil { t.Fatal(err) } if !first.DataDeleted || !first.BackupsDeleted || len(first.EvidenceSHA256) != 64 { t.Fatalf("purge result = %#v", first) } if _, err := os.Lstat(paths.Root); !os.IsNotExist(err) { t.Fatalf("live tenant data survived: %v", err) } if _, err := os.Lstat(backupTenant); !os.IsNotExist(err) { t.Fatalf("tenant backups survived: %v", err) } second, err := Purge(context.Background(), dataRoot, backupRoot, testTenantID, 3) if err != nil { t.Fatal(err) } if second.DataDeleted || second.BackupsDeleted || second.EvidenceSHA256 != first.EvidenceSHA256 { t.Fatalf("idempotent retry = %#v", second) } } func TestPurgeRefusesSymbolicLinksWithoutTouchingTarget(t *testing.T) { dataRoot := t.TempDir() backupRoot := t.TempDir() outside := t.TempDir() outsideFile := filepath.Join(outside, "keep.txt") if err := os.WriteFile(outsideFile, []byte("keep"), 0o600); err != nil { t.Fatal(err) } paths, err := tenantfs.Resolve(dataRoot, testTenantID) if err != nil { t.Fatal(err) } if err := os.Symlink(outsideFile, filepath.Join(paths.Root, "escape")); err != nil { t.Skipf("symbolic links unavailable: %v", err) } if _, err := Purge(context.Background(), dataRoot, backupRoot, testTenantID, 1); err == nil { t.Fatal("purge followed or accepted a symbolic link") } if data, err := os.ReadFile(outsideFile); err != nil || string(data) != "keep" { t.Fatalf("outside target changed: data=%q err=%v", data, err) } }