| new file mode 100644 |
| index 000000000..75510154b |
| --- /dev/null |
| +++ b/internal/release/demand_test.go |
| @@ -0,0 +1,102 @@ |
| +package release |
| + |
| +import ( |
| + "bytes" |
| + "context" |
| + "encoding/json" |
| + "fmt" |
| + "strings" |
| + "testing" |
| +) |
| + |
| +// putRawRelease writes a release the way a NEWER shipper would: the artifact, |
| +// then a manifest whose JSON this binary did not marshal. extra is spliced in |
| +// before "artifacts" and must end with a comma when non-empty, for example |
| +// `"swap":"hold",`. Returns the manifest bytes exactly as stored. |
| +func putRawRelease(t *testing.T, s Store, acct, app, ver string, body []byte, extra string) []byte { |
| + t.Helper() |
| + ctx := context.Background() |
| + if _, err := s.Put(ctx, ArtifactKey(acct, app, ver, adoptPlatform), body, ""); err != nil { |
| + t.Fatalf("put artifact: %v", err) |
| + } |
| + raw := []byte(fmt.Sprintf(`{"schema":1,"app":%q,"version":%q,"kind":"binary",%s"artifacts":{%q:{"file":%q,"sha256":%q,"size":%d}}}`, |
| + app, ver, extra, adoptPlatform, adoptPlatform, etagOf(body), len(body))) |
| + if _, err := s.Put(ctx, ManifestKey(acct, app, ver), raw, ""); err != nil { |
| + t.Fatalf("put manifest: %v", err) |
| + } |
| + return raw |
| +} |
| + |
| +func TestManifestSwapIsAdditive(t *testing.T) { |
| + old := []byte(`{"schema":1,"app":"km","version":"de2766f8","kind":"binary","artifacts":{"linux-arm64":{"file":"linux-arm64","sha256":"` + |
| + strings.Repeat("a", 64) + `","size":1}}}`) |
| + var m Manifest |
| + if err := json.Unmarshal(old, &m); err != nil { |
| + t.Fatalf("a manifest written before swap existed no longer parses: %v", err) |
| + } |
| + if m.Swap != "" { |
| + t.Fatalf("absent swap decoded as %q", m.Swap) |
| + } |
| + out, err := json.MarshalIndent(m, "", " ") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + // Byte-identical marshal is what keeps Ship's idempotent re-ship of an |
| + // old release working. |
| + if bytes.Contains(out, []byte(`"swap"`)) { |
| + t.Fatalf("an empty swap leaked into the marshal:\n%s", out) |
| + } |
| +} |
| + |
| +func TestShipRecordsAHoldDemand(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + m := Manifest{App: "km", Version: "v1", Kind: "binary", Swap: DemandHold, |
| + Artifacts: map[string]Artifact{adoptPlatform: {}}} |
| + if err := Ship(ctx, s, "bab", m, map[string][]byte{adoptPlatform: []byte("bin")}); err != nil { |
| + t.Fatalf("Ship: %v", err) |
| + } |
| + got, err := GetManifest(ctx, s, "bab", "km", "v1") |
| + if err != nil || got.Swap != DemandHold { |
| + t.Fatalf("GetManifest = %+v, %v; want swap %q", got, err, DemandHold) |
| + } |
| +} |
| + |
| +func TestShipRefusesAnUnknownSwapDemand(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + m := Manifest{App: "km", Version: "v1", Kind: "binary", Swap: "drain", |
| + Artifacts: map[string]Artifact{adoptPlatform: {}}} |
| + err := Ship(ctx, s, "bab", m, map[string][]byte{adoptPlatform: []byte("bin")}) |
| + if err == nil || !strings.Contains(err.Error(), `"drain"`) { |
| + t.Fatalf("Ship with swap drain = %v; want a refusal naming the value", err) |
| + } |
| + if _, _, gerr := s.Get(ctx, ManifestKey("bab", "km", "v1")); gerr == nil { |
| + t.Fatal("a refused ship still wrote its manifest") |
| + } |
| + if _, _, gerr := s.Get(ctx, ArtifactKey("bab", "km", "v1", adoptPlatform)); gerr == nil { |
| + t.Fatal("a refused ship still wrote its artifact") |
| + } |
| +} |
| + |
| +// Review Focus 2: a newer shipper's value must not stop a box reading the |
| +// manifest, and must act as hold. |
| +func TestReadsAcceptAnUnknownSwapDemand(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + putRawRelease(t, s, "bab", "km", "v1", []byte("bin"), `"swap":"drain",`) |
| + m, err := GetManifest(context.Background(), s, "bab", "km", "v1") |
| + if err != nil { |
| + t.Fatalf("a manifest from a newer shipper must still read: %v", err) |
| + } |
| + if got := NormalizeDemand(m.Swap); got != DemandHold { |
| + t.Fatalf("NormalizeDemand(%q) = %q; an unknown demand must act as hold", m.Swap, got) |
| + } |
| +} |
| + |
| +func TestNormalizeDemand(t *testing.T) { |
| + for in, want := range map[string]string{"": "", "hold": "hold", "overlap": "hold", "HOLD": "hold", "drain": "hold"} { |
| + if got := NormalizeDemand(in); got != want { |
| + t.Errorf("NormalizeDemand(%q) = %q, want %q", in, got, want) |
| + } |
| + } |
| +} |