package dbverify import ( "context" "testing" "time" "github.com/carlosframework/platform/internal/release" ) func at(s string) time.Time { t, err := time.Parse(time.RFC3339, s) if err != nil { panic(err) } return t } // A capped run must not erase the hosts it did not reach — and must not // restamp them either. The old timestamp is what makes thin coverage visible. func TestMergeKeepsUnvisitedHostsWithTheirOldTimestamp(t *testing.T) { old := at("2026-08-01T00:00:00Z") prior := &Record{V: 1, Box: "box-a", CheckedAt: old, Hosts: map[string]Verdict{ "a.example.com": {OK: true, VerifiedAt: old}, "b.example.com": {OK: true, VerifiedAt: old}, }} now := at("2026-08-15T09:00:00Z") rec := Merge(prior, "box-a", now, []string{"a.example.com", "b.example.com"}, map[string]Verdict{"a.example.com": {OK: true, VerifiedAt: now}}) if got := rec.Hosts["a.example.com"].VerifiedAt; !got.Equal(now) { t.Fatalf("visited host kept the old stamp: %v", got) } b, ok := rec.Hosts["b.example.com"] if !ok { t.Fatal("a host the capped run never reached was dropped from the record") } if !b.VerifiedAt.Equal(old) { t.Fatalf("an unvisited host was restamped %v — its coverage would read as fresh", b.VerifiedAt) } } // A retired instance's last "ok" must not linger and pad the coverage of a // database that no longer exists. func TestMergeDropsHostsNoLongerInTheFleet(t *testing.T) { old := at("2026-08-01T00:00:00Z") prior := &Record{V: 1, Hosts: map[string]Verdict{ "gone.example.com": {OK: true, VerifiedAt: old}, "live.example.com": {OK: true, VerifiedAt: old}, }} rec := Merge(prior, "box-a", at("2026-08-15T09:00:00Z"), []string{"live.example.com"}, nil) if _, ok := rec.Hosts["gone.example.com"]; ok { t.Fatal("a retired host kept its verdict") } if _, ok := rec.Hosts["live.example.com"]; !ok { t.Fatal("a live host lost its verdict") } } // The three-valued vocabulary is the point: unreachable is not a pass and not // an alarm. func TestUnverifiedIsNeitherPassNorFailure(t *testing.T) { rec := &Record{Hosts: map[string]Verdict{ "ok.example.com": {OK: true}, "bad.example.com": {OK: false, Detail: "integrity_check: page 4 corrupt"}, "unknown.example.com": {Unverified: true, Detail: "s3: connection refused"}, }} if got := rec.Failures(); got != 1 { t.Fatalf("Failures() = %d, want 1 — only the damaged replica is an alarm", got) } if got := rec.Unverified(); got != 1 { t.Fatalf("Unverified() = %d, want 1", got) } if rec.Hosts["unknown.example.com"].OK { t.Fatal("an undecided verdict reported OK") } } func TestLoadPublishRoundTrip(t *testing.T) { ctx := context.Background() store := release.NewDirStore(t.TempDir()) // Never run: absent is not an error. got, err := Load(ctx, store) if err != nil { t.Fatalf("Load on a fresh deployment: %v", err) } if got != nil { t.Fatalf("Load invented a record: %+v", got) } now := at("2026-08-15T09:00:00Z") rec := Merge(nil, "box-a", now, []string{"a.example.com"}, map[string]Verdict{"a.example.com": {OK: true, VerifiedAt: now}}) if err := Publish(ctx, store, rec); err != nil { t.Fatalf("Publish: %v", err) } got, err = Load(ctx, store) if err != nil { t.Fatalf("Load: %v", err) } if got == nil || !got.Hosts["a.example.com"].OK { t.Fatalf("round-trip lost the verdict: %+v", got) } if got.Box != "box-a" || !got.CheckedAt.Equal(now) { t.Fatalf("round-trip lost the provenance: %+v", got) } // And a second publish replaces rather than conflicting. rec2 := Merge(got, "box-a", now.Add(time.Hour), []string{"a.example.com"}, map[string]Verdict{"a.example.com": {OK: false, Detail: "integrity_check: bad"}}) if err := Publish(ctx, store, rec2); err != nil { t.Fatalf("second Publish: %v", err) } got, err = Load(ctx, store) if err != nil { t.Fatalf("Load after replace: %v", err) } if got.Failures() != 1 { t.Fatalf("replace did not land: %+v", got) } } // A moved host's restore-verification entry is removed by the landing // (moves spec §6.3, §7.6 local): the load, the delete and the put happen // inside each CAS attempt (Plan 2 ruling G31), so a record that changed // between the read and the write is re-read rather than overwritten with // a stale body. No record, or no entry for the host, is nothing to do. func TestRemoveHostDeletesInsideTheCAS(t *testing.T) { ctx := context.Background() s := release.NewDirStore(t.TempDir()) if err := RemoveHost(ctx, s, "a.example"); err != nil { t.Fatalf("no record: %v", err) } rec := &Record{V: 1, Box: "b", CheckedAt: at("2026-10-07T09:00:00Z"), Hosts: map[string]Verdict{ "a.example": {OK: true, VerifiedAt: at("2026-10-07T08:00:00Z")}, "b.example": {OK: true, VerifiedAt: at("2026-10-07T08:00:00Z")}, }} if err := Publish(ctx, s, rec); err != nil { t.Fatal(err) } if err := RemoveHost(ctx, s, "c.example"); err != nil { t.Fatalf("no entry: %v", err) } // Another writer adds a host between this remove's read and its put: the // race is lost once, then won on a fresh read that keeps the newcomer. raced := false racing := releaseStoreFunc{Store: s, beforePut: func() { if raced { return } raced = true cur, _ := Load(ctx, s) cur.Hosts["d.example"] = Verdict{Unverified: true, VerifiedAt: at("2026-10-07T09:30:00Z")} if err := Publish(ctx, s, cur); err != nil { t.Fatal(err) } }} if err := RemoveHost(ctx, racing, "a.example"); err != nil { t.Fatal(err) } got, err := Load(ctx, s) if err != nil { t.Fatal(err) } if _, ok := got.Hosts["a.example"]; ok || len(got.Hosts) != 2 || !got.Hosts["d.example"].Unverified || !got.Hosts["b.example"].OK { t.Fatalf("after the remove: %+v", got.Hosts) } if !raced { t.Fatal("the race was never run") } } // releaseStoreFunc is a release.Store whose Put first runs beforePut. type releaseStoreFunc struct { release.Store beforePut func() } func (r releaseStoreFunc) Put(ctx context.Context, key string, body []byte, ifMatch string) (string, error) { if r.beforePut != nil { r.beforePut() } return r.Store.Put(ctx, key, body, ifMatch) }