carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "errors"
5 "testing"
6 "time"
7)
8
9func TestTemporaryNamespaceAndCooloff(t *testing.T) {
10 for name, want := range map[string]bool{
11 "tmp-docs": true,
12 "tmp-a1b2c3": true,
13 "tmp": false,
14 "tmpdocs": false,
15 "docs-tmp-": false,
16 "": false,
17 } {
18 if got := IsTemporaryName(name); got != want {
19 t.Errorf("IsTemporaryName(%q) = %v, want %v", name, got, want)
20 }
21 }
22 if TemporaryCooloff != 72*time.Hour {
23 t.Fatalf("TemporaryCooloff = %v, want 72h (spec ruling 2)", TemporaryCooloff)
24 }
25}
26
27// The row stores whole seconds and the marker stores nanoseconds. The purge
28// decision is bound to the second (spec §5), so the two still match.
29func TestPurgeAppIfTrashedAtPurgesOnTheSameSecond(t *testing.T) {
30 s := NewDirStore(t.TempDir())
31 _, pub := testKeys(t)
32 seedApp(t, s, "bab", "tmp-docs")
33 trashedAt := time.Date(2026, 9, 26, 10, 0, 0, 750_000_000, time.UTC)
34 if err := TrashApp(ctx, s, "bab", "tmp-docs", "expiry", trashedAt); err != nil {
35 t.Fatal(err)
36 }
37 want := time.Unix(trashedAt.Unix(), 0)
38 if err := PurgeAppIfTrashedAt(ctx, s, pub, "bab", "tmp-docs", want, trashedAt.Add(TemporaryCooloff)); err != nil {
39 t.Fatalf("PurgeAppIfTrashedAt on the matching clock: %v", err)
40 }
41 if got := listUnder(t, s, "apps/bab/tmp-docs/"); len(got) != 0 {
42 t.Fatalf("purge left objects behind: %v", got)
43 }
44}
45
46// A restore, a keep and a fresh trash write a marker with a new clock. The old
47// decision must not fire against it (spec §5, astra 4).
48func TestPurgeAppIfTrashedAtRefusesAnotherTrash(t *testing.T) {
49 s := NewDirStore(t.TempDir())
50 _, pub := testKeys(t)
51 keys := seedApp(t, s, "bab", "tmp-docs")
52 decided := time.Date(2026, 9, 26, 10, 0, 0, 0, time.UTC)
53 if err := TrashApp(ctx, s, "bab", "tmp-docs", "paul@keymail.dev", decided.Add(time.Hour)); err != nil {
54 t.Fatal(err)
55 }
56 err := PurgeAppIfTrashedAt(ctx, s, pub, "bab", "tmp-docs", decided, decided.Add(TemporaryCooloff))
57 if !errors.Is(err, ErrTrashClockMismatch) {
58 t.Fatalf("PurgeAppIfTrashedAt against a newer trash = %v, want ErrTrashClockMismatch", err)
59 }
60 if got := listUnder(t, s, "apps/bab/tmp-docs/"); len(got) != len(keys)+1 {
61 t.Fatalf("a refused purge destroyed objects: %v", got)
62 }
63}
64
65// Everything PurgeApp refuses, this refuses too.
66func TestPurgeAppIfTrashedAtKeepsPurgeAppsGuards(t *testing.T) {
67 priv, pub := testKeys(t)
68 s := NewDirStore(t.TempDir())
69 seedApp(t, s, "bab", "tmp-docs")
70 at := time.Date(2026, 9, 26, 10, 0, 0, 0, time.UTC)
71 if err := PurgeAppIfTrashedAt(ctx, s, pub, "bab", "tmp-docs", at, at); !errors.Is(err, ErrNotTrashed) {
72 t.Fatalf("untrashed app = %v, want ErrNotTrashed", err)
73 }
74 if err := SetProduction(ctx, s, priv, "bab", "tmp-docs", true, "paul@keymail.dev"); err != nil {
75 t.Fatal(err)
76 }
77 if err := TrashApp(ctx, s, "bab", "tmp-docs", "expiry", at); err != nil {
78 t.Fatal(err)
79 }
80 if err := PurgeAppIfTrashedAt(ctx, s, pub, "bab", "tmp-docs", at, at); err == nil {
81 t.Fatal("purged a production-flagged app")
82 }
83 if got := listUnder(t, s, "apps/bab/tmp-docs/"); len(got) == 0 {
84 t.Fatal("a refused purge destroyed objects")
85 }
86}
87