tinyrelayfile
tinyrelay/internal/catalog/delete_test.go/

internal/catalog/delete_test.go raw
1package catalog
2
3import (
4 "context"
5 "database/sql"
6 "os"
7 "path/filepath"
8 "testing"
9 "time"
10)
11
12func TestDeleteRequiresDisabledAndRemovesTenantData(t *testing.T) {
13 ctx := context.Background()
14 cat, err := Open(ctx, t.TempDir())
15 if err != nil {
16 t.Fatal(err)
17 }
18 defer cat.Close()
19 meta, err := cat.Create(ctx, CreateOptions{Name: "erase-me", Owner: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"})
20 if err != nil {
21 t.Fatal(err)
22 }
23 if err := cat.Delete(ctx, meta.ID); err != ErrInvalidTransition {
24 t.Fatalf("delete while creating = %v, want invalid transition", err)
25 }
26 if err := cat.MarkReady(ctx, meta.ID); err != nil {
27 t.Fatal(err)
28 }
29 if err := cat.Disable(ctx, meta.ID); err != nil {
30 t.Fatal(err)
31 }
32 if err := cat.Delete(ctx, meta.ID); err != nil {
33 t.Fatal(err)
34 }
35 if _, err := os.Stat(meta.Paths.Root); !os.IsNotExist(err) {
36 t.Fatalf("tenant root still exists: %v", err)
37 }
38 items, err := cat.List(ctx)
39 if err != nil {
40 t.Fatal(err)
41 }
42 for _, item := range items {
43 if item.ID == meta.ID {
44 t.Fatalf("deleted tenant remains in catalog")
45 }
46 }
47}
48
49func TestDeleteAtomicallyRemovesHostMappings(t *testing.T) {
50 ctx := context.Background()
51 cat, err := Open(ctx, t.TempDir())
52 if err != nil {
53 t.Fatal(err)
54 }
55 defer cat.Close()
56 meta, err := cat.Create(ctx, CreateOptions{Name: "host-delete", Owner: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"})
57 if err != nil {
58 t.Fatal(err)
59 }
60 if err := cat.MarkReady(ctx, meta.ID); err != nil {
61 t.Fatal(err)
62 }
63 if err := cat.Disable(ctx, meta.ID); err != nil {
64 t.Fatal(err)
65 }
66 if _, err := cat.AddHost(ctx, meta.ID, "delete.example", ""); err != nil {
67 t.Fatal(err)
68 }
69 if err := cat.Delete(ctx, meta.ID); err != nil {
70 t.Fatal(err)
71 }
72 if _, err := cat.ResolveHostMapping(ctx, "delete.example"); err != ErrHostMappingNotFound {
73 t.Fatalf("host mapping after delete = %v", err)
74 }
75}
76
77func TestOpenRecoversInterruptedDeletion(t *testing.T) {
78 ctx := context.Background()
79 root := t.TempDir()
80 cat, err := Open(ctx, root)
81 if err != nil {
82 t.Fatal(err)
83 }
84 meta, err := cat.Create(ctx, CreateOptions{Name: "recover-delete", Owner: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"})
85 if err != nil {
86 t.Fatal(err)
87 }
88 if err := cat.MarkReady(ctx, meta.ID); err != nil {
89 t.Fatal(err)
90 }
91 if err := cat.Disable(ctx, meta.ID); err != nil {
92 t.Fatal(err)
93 }
94 staging := filepath.Join(filepath.Dir(meta.Paths.Root), ".deleting-"+meta.ID)
95 if err := cat.dbTransaction(ctx, func(tx *sql.Tx) error {
96 if _, err := tx.ExecContext(ctx, "UPDATE tenants SET status=?,updated_at=? WHERE id=?", StatusDeleting, time.Now().UnixNano(), meta.ID); err != nil {
97 return err
98 }
99 _, err := tx.ExecContext(ctx, "INSERT INTO tenant_deletions(id,root,staging,started_at) VALUES(?,?,?,?)", meta.ID, meta.Paths.Root, staging, time.Now().UnixNano())
100 return err
101 }); err != nil {
102 t.Fatal(err)
103 }
104 if err := os.Rename(meta.Paths.Root, staging); err != nil {
105 t.Fatal(err)
106 }
107 if err := cat.Close(); err != nil {
108 t.Fatal(err)
109 }
110 reopened, err := Open(ctx, root)
111 if err != nil {
112 t.Fatal(err)
113 }
114 defer reopened.Close()
115 if _, err := reopened.GetByID(ctx, meta.ID); err != ErrNotFound {
116 t.Fatalf("recovered deleted tenant = %v", err)
117 }
118 if _, err := os.Stat(staging); !os.IsNotExist(err) {
119 t.Fatalf("staging directory remains: %v", err)
120 }
121}
122
123func TestOpenMigratesLegacyTenantStatusConstraint(t *testing.T) {
124 ctx := context.Background()
125 root := t.TempDir()
126 db, err := sql.Open("sqlite", filepath.Join(root, "catalog.db"))
127 if err != nil {
128 t.Fatal(err)
129 }
130 _, err = db.Exec(`CREATE TABLE tenants (id TEXT PRIMARY KEY, name TEXT NOT NULL COLLATE NOCASE UNIQUE, owner TEXT NOT NULL, template TEXT NOT NULL, status TEXT NOT NULL, host TEXT COLLATE NOCASE UNIQUE, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, CHECK(status IN ('creating','ready','disabled'))); INSERT INTO tenants(id,name,owner,template,status,created_at,updated_at) VALUES('legacy','legacy','0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef','default','disabled',1,1); CREATE TABLE tenant_hosts (tenant_id TEXT NOT NULL, host TEXT PRIMARY KEY COLLATE NOCASE, site TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'configured', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL);`)
131 if err != nil {
132 t.Fatal(err)
133 }
134 if err := db.Close(); err != nil {
135 t.Fatal(err)
136 }
137 cat, err := Open(ctx, root)
138 if err != nil {
139 t.Fatal(err)
140 }
141 defer cat.Close()
142 if err := cat.beginDeletion(ctx, "legacy", cat.tenantPaths("legacy").Root, filepath.Join(root, "tenants", ".deleting-legacy")); err != nil {
143 t.Fatalf("legacy deleting status was not migrated: %v", err)
144 }
145}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs