| index 2ba4bff63..ea88ae196 100644 |
| --- a/internal/release/writers_test.go |
| +++ b/internal/release/writers_test.go |
| @@ -358,3 +358,146 @@ func TestPromoteOfAnUnchangedHistoryEntrySignsItsDigest(t *testing.T) { |
| t.Fatalf("promote back to v1: %+v; want the entry's signed digest %s", p, signed) |
| } |
| } |
| + |
| +// digestChannelThenMarkerOff is stable with v1 promoted before the marker |
| +// (no digest), v2 promoted under it (digests), and then the marker removed: |
| +// emission suspended on a channel that already carries digests. |
| +func digestChannelThenMarkerOff(t *testing.T) (Store, []byte, func() *ChannelPointer) { |
| + t.Helper() |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) |
| + shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) |
| + if err := Promote(ctx, s, priv, "bab", "km", "stable", "v1"); err != nil { |
| + t.Fatal(err) |
| + } |
| + markerOn(t, s, priv, "bab") |
| + if err := Promote(ctx, s, priv, "bab", "km", "stable", "v2"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := RemoveDigestMarker(ctx, s, "bab"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if m, err := ReadDigestMarker(ctx, s, pub, "bab"); m != nil || err != nil { |
| + t.Fatalf("marker after removal = %+v, %v", m, err) |
| + } |
| + return s, priv, func() *ChannelPointer { return readPtr(t, s, pub, "stable") } |
| +} |
| + |
| +// Astra r1 #3, ruling P9 reversed: suspending emission does not make a |
| +// digest-less history entry a rollback target on a channel with digests. |
| +func TestRollbackOntoADigestlessEntryIsRefusedWithTheMarkerOff(t *testing.T) { |
| + s, priv, cur := digestChannelThenMarkerOff(t) |
| + before := cur() |
| + err := Rollback(context.Background(), s, priv, "bab", "km", "stable") |
| + var rn *ResignNeededError |
| + if !errors.As(err, &rn) || rn.Version != "v1" { |
| + t.Fatalf("marker off, rollback onto a digest-less v1 = %v; want ResignNeededError naming v1", err) |
| + } |
| + if after := cur(); after.Version != before.Version || !after.PromotedAt.Equal(before.PromotedAt) { |
| + t.Fatalf("the refused rollback changed the pointer: %+v -> %+v", before, after) |
| + } |
| +} |
| + |
| +// The approval-gated rollback runs as a hotfix promote of History[0]; with |
| +// the marker off it is refused the same way. |
| +func TestApprovalGatedRollbackOntoADigestlessEntryIsRefusedWithTheMarkerOff(t *testing.T) { |
| + s, priv, cur := digestChannelThenMarkerOff(t) |
| + before := cur() |
| + _, err := PromoteOnLadder(context.Background(), s, priv, "bab", "km", "stable", "v1", PromoteOptions{Hotfix: true}) |
| + var rn *ResignNeededError |
| + if !errors.As(err, &rn) || rn.Version != "v1" { |
| + t.Fatalf("marker off, hotfix promote of a digest-less v1 = %v; want ResignNeededError naming v1", err) |
| + } |
| + if after := cur(); after.Version != before.Version || !after.PromotedAt.Equal(before.PromotedAt) { |
| + t.Fatalf("the refused promote changed the pointer: %+v -> %+v", before, after) |
| + } |
| +} |
| + |
| +// With the marker off, a promote back onto a history entry whose signed |
| +// digest the bucket's bytes no longer match is still refused. |
| +func TestPromoteOfAReshippedHistoryEntryIsRefusedWithTheMarkerOff(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + markerOn(t, s, priv, "bab") |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) |
| + shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) |
| + for _, v := range []string{"v1", "v2"} { |
| + if err := Promote(ctx, s, priv, "bab", "km", "stable", v); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + if err := RemoveDigestMarker(ctx, s, "bab"); err != nil { |
| + t.Fatal(err) |
| + } |
| + reship(t, s, "bab", "km", "v1", []byte("not what was promoted")) |
| + if _, err := PromoteOnLadder(ctx, s, priv, "bab", "km", "stable", "v1", PromoteOptions{Hotfix: true}); !errors.Is(err, ErrManifestDigest) { |
| + t.Fatalf("marker off, promote of a re-shipped history entry = %v; want ErrManifestDigest", err) |
| + } |
| + if p := readPtr(t, s, pub, "stable"); p.Version != "v2" { |
| + t.Fatalf("the refused promote moved stable to %s", p.Version) |
| + } |
| +} |
| + |
| +// Only emission depends on the marker: with it off, a rollback onto an |
| +// entry that carries a digest succeeds and writes no digest fields at all. |
| +func TestRollbackOntoADigestEntryWithTheMarkerOffWritesNoDigests(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + markerOn(t, s, priv, "bab") |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) |
| + shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) |
| + for _, v := range []string{"v1", "v2"} { |
| + if err := Promote(ctx, s, priv, "bab", "km", "stable", v); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + if err := RemoveDigestMarker(ctx, s, "bab"); err != nil { |
| + t.Fatal(err) |
| + } |
| + if err := Rollback(ctx, s, priv, "bab", "km", "stable"); err != nil { |
| + t.Fatalf("marker off, rollback onto a digest-bearing v1: %v", err) |
| + } |
| + p := readPtr(t, s, pub, "stable") |
| + if p.Version != "v1" || p.Digest != "" || p.HistoryDigests != nil { |
| + t.Fatalf("after a marker-off rollback: %+v; want v1 with no digest fields", p) |
| + } |
| + raw, _, _ := s.Get(ctx, ChannelKey("bab", "km", "stable")) |
| + if strings.Contains(string(raw), "digest") { |
| + t.Fatal("the marker-off rollback's wrapper mentions digests") |
| + } |
| +} |
| + |
| +// Task 5's deferred minor: an unverifiable marker refuses rollback and |
| +// fast-track as it refuses promote, and the pointer is unchanged. |
| +func TestRollbackAndFastTrackRefuseAnUnverifiableMarker(t *testing.T) { |
| + s := NewDirStore(t.TempDir()) |
| + ctx := context.Background() |
| + priv, pub := testKeys(t) |
| + shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) |
| + shipRelease(t, s, "bab", "km", "v2", adoptPlatform, []byte("two")) |
| + for _, v := range []string{"v1", "v2"} { |
| + if err := Promote(ctx, s, priv, "bab", "km", "stable", v); err != nil { |
| + t.Fatal(err) |
| + } |
| + } |
| + other, _ := testKeys(t) |
| + markerOn(t, s, other, "bab") |
| + before := readPtr(t, s, pub, "stable") |
| + _, merr := ReadDigestMarker(ctx, s, pub, "bab") |
| + if merr == nil { |
| + t.Fatal("control: a foreign-key marker verified") |
| + } |
| + if err := Rollback(ctx, s, priv, "bab", "km", "stable"); err == nil || err.Error() != fmt.Sprintf(TextMarkerUnreadable, "rollback", "bab", merr) { |
| + t.Fatalf("Rollback with a foreign marker = %v; want the marker refusal", err) |
| + } |
| + if _, err := FastTrack(ctx, s, priv, "bab", "km", "stable", "ops@example.com"); err == nil || err.Error() != fmt.Sprintf(TextMarkerUnreadable, "fast-track", "bab", merr) { |
| + t.Fatalf("FastTrack with a foreign marker = %v; want the marker refusal", err) |
| + } |
| + if after := readPtr(t, s, pub, "stable"); after.Version != before.Version || after.FastTrack || !after.PromotedAt.Equal(before.PromotedAt) { |
| + t.Fatalf("a refused write changed the pointer: %+v -> %+v", before, after) |
| + } |
| +} |