carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package dbverify
2
3import (
4 "context"
5 "testing"
6 "time"
7
8 "github.com/carlosframework/platform/internal/release"
9)
10
11func at(s string) time.Time {
12 t, err := time.Parse(time.RFC3339, s)
13 if err != nil {
14 panic(err)
15 }
16 return t
17}
18
19// A capped run must not erase the hosts it did not reach — and must not
20// restamp them either. The old timestamp is what makes thin coverage visible.
21func TestMergeKeepsUnvisitedHostsWithTheirOldTimestamp(t *testing.T) {
22 old := at("2026-08-01T00:00:00Z")
23 prior := &Record{V: 1, Box: "box-a", CheckedAt: old, Hosts: map[string]Verdict{
24 "a.example.com": {OK: true, VerifiedAt: old},
25 "b.example.com": {OK: true, VerifiedAt: old},
26 }}
27 now := at("2026-08-15T09:00:00Z")
28
29 rec := Merge(prior, "box-a", now, []string{"a.example.com", "b.example.com"},
30 map[string]Verdict{"a.example.com": {OK: true, VerifiedAt: now}})
31
32 if got := rec.Hosts["a.example.com"].VerifiedAt; !got.Equal(now) {
33 t.Fatalf("visited host kept the old stamp: %v", got)
34 }
35 b, ok := rec.Hosts["b.example.com"]
36 if !ok {
37 t.Fatal("a host the capped run never reached was dropped from the record")
38 }
39 if !b.VerifiedAt.Equal(old) {
40 t.Fatalf("an unvisited host was restamped %v — its coverage would read as fresh", b.VerifiedAt)
41 }
42}
43
44// A retired instance's last "ok" must not linger and pad the coverage of a
45// database that no longer exists.
46func TestMergeDropsHostsNoLongerInTheFleet(t *testing.T) {
47 old := at("2026-08-01T00:00:00Z")
48 prior := &Record{V: 1, Hosts: map[string]Verdict{
49 "gone.example.com": {OK: true, VerifiedAt: old},
50 "live.example.com": {OK: true, VerifiedAt: old},
51 }}
52 rec := Merge(prior, "box-a", at("2026-08-15T09:00:00Z"), []string{"live.example.com"}, nil)
53 if _, ok := rec.Hosts["gone.example.com"]; ok {
54 t.Fatal("a retired host kept its verdict")
55 }
56 if _, ok := rec.Hosts["live.example.com"]; !ok {
57 t.Fatal("a live host lost its verdict")
58 }
59}
60
61// The three-valued vocabulary is the point: unreachable is not a pass and not
62// an alarm.
63func TestUnverifiedIsNeitherPassNorFailure(t *testing.T) {
64 rec := &Record{Hosts: map[string]Verdict{
65 "ok.example.com": {OK: true},
66 "bad.example.com": {OK: false, Detail: "integrity_check: page 4 corrupt"},
67 "unknown.example.com": {Unverified: true, Detail: "s3: connection refused"},
68 }}
69 if got := rec.Failures(); got != 1 {
70 t.Fatalf("Failures() = %d, want 1 — only the damaged replica is an alarm", got)
71 }
72 if got := rec.Unverified(); got != 1 {
73 t.Fatalf("Unverified() = %d, want 1", got)
74 }
75 if rec.Hosts["unknown.example.com"].OK {
76 t.Fatal("an undecided verdict reported OK")
77 }
78}
79
80func TestLoadPublishRoundTrip(t *testing.T) {
81 ctx := context.Background()
82 store := release.NewDirStore(t.TempDir())
83
84 // Never run: absent is not an error.
85 got, err := Load(ctx, store)
86 if err != nil {
87 t.Fatalf("Load on a fresh deployment: %v", err)
88 }
89 if got != nil {
90 t.Fatalf("Load invented a record: %+v", got)
91 }
92
93 now := at("2026-08-15T09:00:00Z")
94 rec := Merge(nil, "box-a", now, []string{"a.example.com"},
95 map[string]Verdict{"a.example.com": {OK: true, VerifiedAt: now}})
96 if err := Publish(ctx, store, rec); err != nil {
97 t.Fatalf("Publish: %v", err)
98 }
99
100 got, err = Load(ctx, store)
101 if err != nil {
102 t.Fatalf("Load: %v", err)
103 }
104 if got == nil || !got.Hosts["a.example.com"].OK {
105 t.Fatalf("round-trip lost the verdict: %+v", got)
106 }
107 if got.Box != "box-a" || !got.CheckedAt.Equal(now) {
108 t.Fatalf("round-trip lost the provenance: %+v", got)
109 }
110
111 // And a second publish replaces rather than conflicting.
112 rec2 := Merge(got, "box-a", now.Add(time.Hour), []string{"a.example.com"},
113 map[string]Verdict{"a.example.com": {OK: false, Detail: "integrity_check: bad"}})
114 if err := Publish(ctx, store, rec2); err != nil {
115 t.Fatalf("second Publish: %v", err)
116 }
117 got, err = Load(ctx, store)
118 if err != nil {
119 t.Fatalf("Load after replace: %v", err)
120 }
121 if got.Failures() != 1 {
122 t.Fatalf("replace did not land: %+v", got)
123 }
124}
125
126// A moved host's restore-verification entry is removed by the landing
127// (moves spec §6.3, §7.6 local): the load, the delete and the put happen
128// inside each CAS attempt (Plan 2 ruling G31), so a record that changed
129// between the read and the write is re-read rather than overwritten with
130// a stale body. No record, or no entry for the host, is nothing to do.
131func TestRemoveHostDeletesInsideTheCAS(t *testing.T) {
132 ctx := context.Background()
133 s := release.NewDirStore(t.TempDir())
134 if err := RemoveHost(ctx, s, "a.example"); err != nil {
135 t.Fatalf("no record: %v", err)
136 }
137 rec := &Record{V: 1, Box: "b", CheckedAt: at("2026-10-07T09:00:00Z"), Hosts: map[string]Verdict{
138 "a.example": {OK: true, VerifiedAt: at("2026-10-07T08:00:00Z")},
139 "b.example": {OK: true, VerifiedAt: at("2026-10-07T08:00:00Z")},
140 }}
141 if err := Publish(ctx, s, rec); err != nil {
142 t.Fatal(err)
143 }
144 if err := RemoveHost(ctx, s, "c.example"); err != nil {
145 t.Fatalf("no entry: %v", err)
146 }
147 // Another writer adds a host between this remove's read and its put: the
148 // race is lost once, then won on a fresh read that keeps the newcomer.
149 raced := false
150 racing := releaseStoreFunc{Store: s, beforePut: func() {
151 if raced {
152 return
153 }
154 raced = true
155 cur, _ := Load(ctx, s)
156 cur.Hosts["d.example"] = Verdict{Unverified: true, VerifiedAt: at("2026-10-07T09:30:00Z")}
157 if err := Publish(ctx, s, cur); err != nil {
158 t.Fatal(err)
159 }
160 }}
161 if err := RemoveHost(ctx, racing, "a.example"); err != nil {
162 t.Fatal(err)
163 }
164 got, err := Load(ctx, s)
165 if err != nil {
166 t.Fatal(err)
167 }
168 if _, ok := got.Hosts["a.example"]; ok || len(got.Hosts) != 2 || !got.Hosts["d.example"].Unverified || !got.Hosts["b.example"].OK {
169 t.Fatalf("after the remove: %+v", got.Hosts)
170 }
171 if !raced {
172 t.Fatal("the race was never run")
173 }
174}
175
176// releaseStoreFunc is a release.Store whose Put first runs beforePut.
177type releaseStoreFunc struct {
178 release.Store
179 beforePut func()
180}
181
182func (r releaseStoreFunc) Put(ctx context.Context, key string, body []byte, ifMatch string) (string, error) {
183 if r.beforePut != nil {
184 r.beforePut()
185 }
186 return r.Store.Put(ctx, key, body, ifMatch)
187}
188