| index 941219887..f4b28e030 100644 |
| --- a/internal/host/movespolicy_test.go |
| +++ b/internal/host/movespolicy_test.go |
| @@ -368,12 +368,6 @@ func TestMovesClaimPolicyBindsHeldInputsToThePins(t *testing.T) { |
| for name, mutate := range map[string]func(*provision.Record){ |
| "no inputs": func(r *provision.Record) { r.Move.Inputs = nil }, |
| "no release pinned": func(r *provision.Record) { r.Move.Inputs.Release = "" }, |
| - "no config pinned": func(r *provision.Record) { |
| - r.Move.Inputs.BundleETags, r.Move.Inputs.OverlayETag = nil, "" |
| - }, |
| - "empty etags pinned": func(r *provision.Record) { |
| - r.Move.Inputs.BundleETags, r.Move.Inputs.OverlayETag = map[string]string{"default": "", "environment": ""}, "" |
| - }, |
| } { |
| t.Run(name, func(t *testing.T) { |
| rec := mpRecord() |
| @@ -389,11 +383,6 @@ func TestMovesClaimPolicyBindsHeldInputsToThePins(t *testing.T) { |
| if errConfig != nil { |
| t.Fatalf("the config pins are intact: %v", errConfig) |
| } |
| - case "no config pinned", "empty etags pinned": |
| - refused(t, errConfig, "no pinned config") |
| - if errRelease != nil { |
| - t.Fatalf("the release pin is intact: %v", errRelease) |
| - } |
| default: |
| refused(t, errRelease, "no pinned inputs") |
| refused(t, errConfig, "no pinned inputs") |
| @@ -401,6 +390,70 @@ func TestMovesClaimPolicyBindsHeldInputsToThePins(t *testing.T) { |
| }) |
| } |
| }) |
| + // An app without a default bundle or an overlay pins empty etags at |
| + // readiness (movesheldenv.go): that is a configuration, with its own |
| + // identity, granted when the render names the same empty set. Missing |
| + // Move.Inputs is still refused. |
| + t.Run("an explicitly pinned empty configuration is granted", func(t *testing.T) { |
| + for name, pins := range map[string]*provision.MoveInputs{ |
| + "nil etags": {Release: "v1.2.3"}, |
| + "empty map": {BundleETags: map[string]string{}, Release: "v1.2.3"}, |
| + "all-empty etags": {BundleETags: map[string]string{"default": "", "environment": ""}, OverlayETag: "", Release: "v1.2.3"}, |
| + } { |
| + t.Run(name, func(t *testing.T) { |
| + rec := mpRecord() |
| + rec.Move.Inputs = pins |
| + moves := MovesClaimPolicy((&mpReader{rec: rec}).read) |
| + c, s := at("held-config") |
| + empty := HeldConfigIdentity(nil, "") |
| + if empty.Config == "" { |
| + t.Fatal("the empty configuration has no identity") |
| + } |
| + if err := moves(t.Context(), c, s, empty); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := moves(t.Context(), c, s, HeldConfigIdentity(map[string]string{"default": "", "environment": ""}, "")); err != nil { |
| + t.Fatalf("all-empty etags are the empty configuration: %v", err) |
| + } |
| + refused(t, moves(t.Context(), c, s, HeldConfigIdentity(map[string]string{"default": "e-default"}, "")), "the config is not the pinned one") |
| + refused(t, moves(t.Context(), c, s, HeldConfigIdentity(nil, "e-overlay")), "the config is not the pinned one") |
| + refused(t, moves(t.Context(), c, s, hostwrite.Identity{}), "the identity does not fit the action") |
| + c, s = at("held-release") |
| + if err := moves(t.Context(), c, s, hostwrite.Identity{Release: "v1.2.3"}); err != nil { |
| + t.Fatal(err) |
| + } |
| + }) |
| + } |
| + }) |
| + // The encoding is structural: a value that contains the delimiters of |
| + // a flat encoding cannot collide with another set of etags. |
| + t.Run("delimiters in an etag do not collide", func(t *testing.T) { |
| + collisions := [][2]struct { |
| + etags map[string]string |
| + overlay string |
| + }{ |
| + {{map[string]string{"default": "a;environment=b"}, ""}, {map[string]string{"default": "a", "environment": "b"}, ""}}, |
| + {{map[string]string{"default": "a"}, "x;environment=b"}, {map[string]string{"default": "a", "environment": "b"}, "x"}}, |
| + {{map[string]string{"default": "a;overlay=o"}, ""}, {map[string]string{"default": "a"}, "o"}}, |
| + {{map[string]string{"default=x": "a"}, ""}, {map[string]string{"default": "x=a"}, ""}}, |
| + {{map[string]string{"default": `a","environment":"b`}, ""}, {map[string]string{"default": "a", "environment": "b"}, ""}}, |
| + } |
| + for i, pair := range collisions { |
| + a := HeldConfigIdentity(pair[0].etags, pair[0].overlay) |
| + b := HeldConfigIdentity(pair[1].etags, pair[1].overlay) |
| + if a == b { |
| + t.Fatalf("pair %d collides: %q", i, a.Config) |
| + } |
| + rec := mpRecord() |
| + rec.Move.Inputs.BundleETags, rec.Move.Inputs.OverlayETag = pair[1].etags, pair[1].overlay |
| + moves := MovesClaimPolicy((&mpReader{rec: rec}).read) |
| + c, s := at("held-config") |
| + refused(t, moves(t.Context(), c, s, a), "the config is not the pinned one") |
| + if err := moves(t.Context(), c, s, b); err != nil { |
| + t.Fatalf("pair %d: the pinned set refused: %v", i, err) |
| + } |
| + } |
| + }) |
| t.Run("a record read error", func(t *testing.T) { |
| cause := errors.New("the store is down") |
| moves := MovesClaimPolicy((&mpReader{err: cause}).read) |
| @@ -473,8 +526,13 @@ func TestMovesClaimPolicyBindsHeldInputsToThePins(t *testing.T) { |
| if a != b || a.Release != "" || a.Config == "" { |
| t.Fatalf("%+v vs %+v", a, b) |
| } |
| - if HeldConfigIdentity(nil, "").Config != "" { |
| - t.Fatal("no etags is an identity") |
| + // The empty configuration has one identity, whichever way it is spelt. |
| + e1, e2, e3 := HeldConfigIdentity(nil, ""), HeldConfigIdentity(map[string]string{}, ""), HeldConfigIdentity(map[string]string{"default": ""}, "") |
| + if e1.Config == "" || e1 != e2 || e2 != e3 || e1 == b { |
| + t.Fatalf("%+v %+v %+v", e1, e2, e3) |
| + } |
| + if HeldConfigIdentity(nil, "o") == e1 || HeldConfigIdentity(map[string]string{"default": "d"}, "") == e1 { |
| + t.Fatal("an overlay or an etag alone is the empty configuration") |
| } |
| }) |
| } |