carlos / platform Public

test(provision): pin manifest-read-failure seed withhold + removal skip

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 116f5a040217b4a71689dd90d6e54fdbca4e18f5 parent e7b2d51
1 file changed, +52 −0
  • internal/provision/reconcile_test.go +52 −0
diff --git a/internal/provision/reconcile_test.go b/internal/provision/reconcile_test.go
index c6891234d..9212d2ea6 100644
--- a/internal/provision/reconcile_test.go
+++ b/internal/provision/reconcile_test.go
@@ -313,6 +313,58 @@ func TestReconcileFailSafeOnBrokenAccountManifest(t *testing.T) {
}
}
+// A manifest READ FAILURE (as opposed to "no manifest") must not wipe an
+// already-provisioned route's seed: seedFor's "" fallback is only safe to
+// hand a brand-new mint (nothing existing to protect), never the converge
+// branch. This pins the self-review fix in reconcile.go's seedFor/seedIsKnown
+// split — the first passing implementation called SetSeed(host, "") here,
+// which would erase a working seed on a transient blip.
+//
+// It also confirms readFailed from a manifest read failure disables removal
+// for the WHOLE pass, not only the account-listing step the brief's step 1
+// names explicitly: a second, in-scope provisioned route with no matching
+// record this pass (the removal phase's only candidate) must survive too.
+func TestReconcileWithholdsSeedOnManifestReadFailure(t *testing.T) {
+ s, sqid := setup(t)
+ if err := CreateRecord(ctx, s, acmeRecord(sqid)); err != nil {
+ t.Fatal(err)
+ }
+ reg := regWith(t)
+ cfg := ReconcileConfig{Region: "ie", Pools: []string{"tito"}, DataDir: "/data"}
+ if err := Reconcile(ctx, s, reg, cfg); err != nil {
+ t.Fatal(err)
+ }
+ before := *mustGet(t, reg, "acme.go.tito.io")
+ if before.Seed != `["seed"]` {
+ t.Fatalf("fixture's route didn't seed as expected: %+v", before)
+ }
+
+ // A second provisioned, in-scope route with no matching record at all —
+ // the removal phase's only candidate this pass.
+ if err := reg.Upsert(registry.Route{Host: "ghost.go.tito.io", Kind: "instance", App: "tito",
+ Status: "ready", Provisioned: true, Region: "ie", Pool: "tito", Account: sqid}); err != nil {
+ t.Fatal(err)
+ }
+
+ // Break the app's instances.json: GetManifest now fails non-ENOENT.
+ putReplace(t, s, ManifestKey(sqid, "tito"), []byte("not json"))
+
+ if err := Reconcile(ctx, s, reg, cfg); err != nil {
+ t.Fatal(err)
+ }
+
+ after := *mustGet(t, reg, "acme.go.tito.io")
+ if after.Seed != before.Seed {
+ t.Errorf("manifest read failure wiped an existing route's seed: before %q after %q", before.Seed, after.Seed)
+ }
+ after.Seed = before.Seed
+ if after != before {
+ t.Errorf("manifest read failure touched more than Seed: before %+v after %+v", before, after)
+ }
+
+ mustGet(t, reg, "ghost.go.tito.io") // removal skipped for the whole pass
+}
+
// 8. Malformed record.
func TestReconcileSkipsMalformedRecord(t *testing.T) {
s, sqid := setup(t)