package hostops import ( "context" "errors" "slices" "testing" "github.com/carlosframework/platform/internal/release" ) func relayPut(before, after string) MutationTarget { return MutationTarget{Surface: "bucket", Method: "Put", Key: RelayAuthorityKey, Before: []byte(before), After: []byte(after)} } // control/relay.json is per-app authority: only the entries a rewrite adds, // removes or changes need permits, and a written_at-only rewrite is the one // non-app write of that key. func TestRelayAuthorityClassifiesChangedEntries(t *testing.T) { ctx := t.Context() const ( a = `{"account":"bab","app":"demo","incarnation":"i1","regions":["eu-west-1"]}` b = `{"account":"bab","app":"kass","incarnation":"i2","regions":["eu-west-1"]}` b2 = `{"account":"bab","app":"kass","incarnation":"i3","regions":["eu-west-1"]}` bR = `{"account":"bab","app":"kass","incarnation":"i2","regions":["us-east-1"]}` c = `{"account":"zed","app":"meet","incarnation":"i4","regions":["eu-west-1"]}` at1 = `"written_at":"2026-10-07T12:00:00Z"` at2 = `"written_at":"2026-10-07T12:05:00Z"` ) doc := func(at string, apps ...string) string { out := `{"apps":[` for i, e := range apps { if i > 0 { out += "," } out += e } return out + `],` + at + `}` } for _, tc := range []struct { name string before, after string want []AppScope }{ {"written-at-only", doc(at1, a, b), doc(at2, a, b), nil}, {"order-only", doc(at1, a, b), doc(at2, b, a), nil}, {"first-publish", "", doc(at1, a), []AppScope{{"bab", "demo"}}}, {"added", doc(at1, a), doc(at2, c, a), []AppScope{{"zed", "meet"}}}, {"removed", doc(at1, a, b), doc(at2, a), []AppScope{{"bab", "kass"}}}, {"incarnation", doc(at1, a, b), doc(at2, a, b2), []AppScope{{"bab", "kass"}}}, {"regions", doc(at1, a, b), doc(at2, a, bR), []AppScope{{"bab", "kass"}}}, {"many", doc(at1, a, b), doc(at2, c, b2), []AppScope{{"bab", "demo"}, {"bab", "kass"}, {"zed", "meet"}}}, } { t.Run(tc.name, func(t *testing.T) { c, err := ClassifyMutation(ctx, nil, nil, relayPut(tc.before, tc.after)) if err != nil { t.Fatal(err) } if !slices.Equal(c.Scopes, tc.want) { t.Fatalf("scopes %v, want %v", c.Scopes, tc.want) } if (len(tc.want) == 0) != (c.NonApp != "") { t.Fatalf("non-app reason %q with scopes %v", c.NonApp, c.Scopes) } got, err := RelayAuthorityScopes([]byte(tc.before), []byte(tc.after)) if err != nil || !slices.Equal(got, tc.want) { t.Fatalf("publisher scopes %v %v disagree with the boundary", got, err) } }) } for name, tgt := range map[string]MutationTarget{ "malformed-after": relayPut(doc(at1, a), `{"apps":[`), "malformed-before": relayPut(`not json`, doc(at1, a)), "duplicate-after": relayPut(doc(at1, a), doc(at2, a, a)), "duplicate-before": relayPut(doc(at1, a, a), doc(at2, a)), "unknown-field": relayPut(doc(at1, a), `{"apps":[],"extra":1,`+at2+`}`), "unknown-entry-key": relayPut(doc(at1, a), `{"apps":[{"account":"bab","app":"demo","incarnation":"i1","regions":[],"x":1}],`+at2+`}`), "invalid-identity": relayPut(doc(at1, a), doc(at2, `{"account":"","app":"demo","incarnation":"i1","regions":[]}`)), "trailing-value": relayPut(doc(at1, a), doc(at2, a)+`{}`), } { if _, err := ClassifyMutation(ctx, nil, nil, tgt); !errors.Is(err, ErrIdentity) { t.Fatalf("%s: %v", name, err) } } del := MutationTarget{Surface: "bucket", Method: "Delete", Key: RelayAuthorityKey, Before: []byte(doc(at1, a))} if _, err := ClassifyMutation(ctx, nil, nil, del); !errors.Is(err, ErrUnclassified) { t.Fatalf("aggregate delete: %v", err) } if _, err := ClassifyMutation(ctx, nil, nil, MutationTarget{Method: "Put", Key: "control/relay.json.tmp", After: []byte(`{}`)}); !errors.Is(err, ErrUnclassified) { t.Fatalf("no prefix admission: %v", err) } } // control/relay-issuer.json is the operator's regional issuer pointer: an // exact non-app family when its body is a valid {region: kid} map. func TestRelayIssuerIsExactNonApp(t *testing.T) { ctx := t.Context() put := func(body string) MutationTarget { return MutationTarget{Surface: "bucket", Method: "Put", Key: RelayIssuerKey, After: []byte(body)} } c, err := ClassifyMutation(ctx, nil, nil, put(`{"eu-west-1":"k2","us-east-1":"k1"}`)) if err != nil || c.NonApp == "" || len(c.Scopes) != 0 { t.Fatalf("issuer pointer: %+v %v", c, err) } for _, body := range []string{`{"eu-west-1":"TOOLONGKID"}`, `{"EU":"k1"}`, `[]`, `null`, `{"eu-west-1":"k1"} {}`} { if _, err := ClassifyMutation(ctx, nil, nil, put(body)); !errors.Is(err, ErrIdentity) { t.Fatalf("%s: %v", body, err) } } if _, err := ClassifyMutation(ctx, nil, nil, MutationTarget{Method: "Delete", Key: RelayIssuerKey}); !errors.Is(err, ErrUnclassified) { t.Fatalf("issuer delete: %v", err) } } func boundRunner(t *testing.T, apps ...string) (context.Context, release.Store, *release.DirStore) { t.Helper() ctx := t.Context() w, raw, _ := testWriter(t) for _, app := range apps { seedAdmission(t, raw, "bab", app, openAdmission(app+"-id")) } check, err := NewProductionMutationCheck(raw, w) if err != nil { t.Fatal(err) } data, err := NewMutationBoundary(true, raw, check) if err != nil { t.Fatal(err) } if err = BindMutationRunner(data, w, testOwner, func(_ context.Context, _, app string) (string, error) { return app + "-id", nil }); err != nil { t.Fatal(err) } return ctx, data, raw } // A raw write made under a nested scope is a possible late effect of every // enclosing operation, so a failing multi-scope call holds both apps. func TestNestedScopeErrorHoldsEveryEnclosingApp(t *testing.T) { ctx, data, raw := boundRunner(t, "demo", "kass") failure := errors.New("ambiguous nested failure") err := release.WithAppMutation(ctx, data, "bab", "demo", "outer", func(outer context.Context) error { return release.WithAppMutation(outer, data, "bab", "kass", "inner", func(inner context.Context) error { if _, err := data.Put(inner, "apps/bab/kass/test.json", []byte(`{}`), ""); err != nil { return err } return failure }) }) if !errors.Is(err, failure) { t.Fatalf("nested failure: %v", err) } for _, app := range []string{"demo", "kass"} { a, _ := ReadAdmission(ctx, raw, "bab", app, app+"-id") if a.Value.State != "closed" || a.Value.Op == nil { t.Fatalf("%s released after an ambiguous nested effect: %+v", app, a.Value) } } } // A nested acquisition that fails issues nothing, so the outer operation // still releases as unapplied. func TestFailedNestedAcquisitionReleasesOuter(t *testing.T) { ctx, data, raw := boundRunner(t, "demo") err := release.WithAppMutation(ctx, data, "bab", "demo", "outer", func(outer context.Context) error { // kass has no admission: the nested acquisition refuses before any effect. return release.WithAppMutation(outer, data, "bab", "kass", "inner", func(context.Context) error { t.Fatal("unadmitted nested scope ran") return nil }) }) if err == nil { t.Fatal("nested acquisition of an unadmitted app succeeded") } a, _ := ReadAdmission(ctx, raw, "bab", "demo", "demo-id") if a.Value.State != "open" || a.Value.Op != nil { t.Fatalf("unissued outer operation retained authority: %+v", a.Value) } } // A context whose permit for an app is no longer valid cannot open a fresh // operation for that app by nesting. func TestInvalidNestedPermitRefuses(t *testing.T) { ctx, data, raw := boundRunner(t, "demo") var saved context.Context if err := release.WithAppMutation(ctx, data, "bab", "demo", "outer", func(pctx context.Context) error { saved = pctx return nil }); err != nil { t.Fatal(err) } before, _ := ReadAdmission(ctx, raw, "bab", "demo", "demo-id") called := false err := release.WithAppMutation(saved, data, "bab", "demo", "nested", func(context.Context) error { called = true return nil }) after, _ := ReadAdmission(ctx, raw, "bab", "demo", "demo-id") if called || !errors.Is(err, ErrPending) || before.ETag != after.ETag { t.Fatalf("retired permit acquired a new operation: called=%v err=%v", called, err) } }