carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "bytes"
5 "github.com/carlosframework/platform/internal/rootstate"
6 "os"
7 "strings"
8 "testing"
9 "time"
10)
11
12func TestPlan14HistoryRetainsRecoveryOutcomes(t *testing.T) {
13 for _, state := range []string{"failed", "pending", "ambiguous", "verified"} {
14 t.Run(state, func(t *testing.T) {
15 h, s, b := rootHistoryFixture(t)
16 outcome := HistoryRecovery{Binding: b, Attempt: HistoryAttempt{ID: "attempt-one", Version: "v2", Source: "canonical", SHA256: "digest", Platform: AdoptPlatform(), Started: time.Now().UTC(), PromotedAt: time.Now().UTC()}, Settled: state != "pending", Failed: state == "failed", Verified: state == "verified"}
17 if err := h.RetainRecovery(outcome); err != nil {
18 t.Fatal(err)
19 }
20 h = &RootHistory{Store: s}
21 refs, err := h.Refusals(b)
22 switch state {
23 case "pending", "ambiguous":
24 if err == nil {
25 t.Fatal("ambiguous outcome granted permission")
26 }
27 case "failed":
28 if err != nil || len(refs) != 1 || refs[0].SHA256 != "digest" {
29 t.Fatalf("failed digest lost: %+v %v", refs, err)
30 }
31 case "verified":
32 if err != nil || len(refs) != 0 {
33 t.Fatalf("verified outcome: %+v %v", refs, err)
34 }
35 }
36 view, err := h.MigrationStatus(b, func(HistoryBinding) (string, error) { return "canonical", nil })
37 if err != nil || len(view.Hosts) != 1 || len(view.Hosts[0].Recoveries) != 1 {
38 t.Fatalf("outcome not projected: %+v %v", view, err)
39 }
40 later := outcome
41 later.Attempt.ID = "attempt-two"
42 later.Settled, later.Verified, later.Failed = true, true, false
43 if err := h.RetainRecovery(later); err != nil {
44 t.Fatal(err)
45 }
46 refs, err = h.Refusals(b)
47 if (state == "pending" || state == "ambiguous") && err == nil {
48 t.Fatal("later success erased ambiguity")
49 }
50 if state == "failed" && (err != nil || len(refs) != 1) {
51 t.Fatal("later success erased failed digest")
52 }
53 })
54 }
55}
56
57func TestPlan14HistoryRecoveryCannotWeaken(t *testing.T) {
58 h, s, b := rootHistoryFixture(t)
59 outcome := HistoryRecovery{Binding: b, Attempt: HistoryAttempt{ID: "attempt", Version: "v2", Source: "canonical", SHA256: "digest", Platform: AdoptPlatform(), Started: time.Now().UTC(), PromotedAt: time.Now().UTC()}, Settled: true, Failed: true}
60 if err := h.RetainRecovery(outcome); err != nil {
61 t.Fatal(err)
62 }
63 outcome.Failed, outcome.Verified = false, true
64 if err := h.RetainRecovery(outcome); err != nil {
65 t.Fatal(err)
66 }
67 if refs, err := h.Refusals(b); err != nil || len(refs) != 1 {
68 t.Fatalf("replay erased denial: %+v %v", refs, err)
69 }
70 outcome.Attempt.SHA256 = "another"
71 if err := h.RetainRecovery(outcome); err == nil {
72 t.Fatal("identity reused for different bytes")
73 }
74 s.fail = 1
75 if _, err := h.Refusals(b); err == nil {
76 t.Fatal("failed sync granted history")
77 }
78 s.fail = 0
79 if refs, err := h.Refusals(b); err != nil || len(refs) != 1 {
80 t.Fatalf("retry lost denial: %+v %v", refs, err)
81 }
82 if err := s.Write(historyRecoveryKey(outcome), []byte("{")); err != nil {
83 t.Fatal(err)
84 }
85 if _, err := h.Refusals(b); err == nil {
86 t.Fatal("malformed outcome granted history")
87 }
88}
89
90func TestPlan14HistoryReconstructionKeepsRecovery(t *testing.T) {
91 for _, state := range []string{"failed", "ambiguous"} {
92 t.Run(state, func(t *testing.T) {
93 h, _, b, pub, wire, now := reconstructionCrashFixture(t)
94 outcome := HistoryRecovery{Binding: b, Attempt: HistoryAttempt{ID: "recovery", Version: "failed-version", Source: "canonical", SHA256: "failed-digest", Platform: AdoptPlatform(), Started: now, PromotedAt: now}, Settled: true, Failed: state == "failed"}
95 if err := h.RetainRecovery(outcome); err != nil {
96 t.Fatal(err)
97 }
98 if err := h.Reconstruct(pub, wire, b.Account, b.App, now, func(HistoryBinding) (string, error) { return "canonical", nil }); err != nil {
99 t.Fatal(err)
100 }
101 refs, err := h.Refusals(b)
102 if state == "ambiguous" {
103 if err == nil {
104 t.Fatal("reconstruction cleared ambiguous recovery")
105 }
106 return
107 }
108 if err != nil {
109 t.Fatal(err)
110 }
111 found := false
112 for _, ref := range refs {
113 found = found || ref.SHA256 == "failed-digest"
114 }
115 if !found {
116 t.Fatal("reconstruction erased failed digest")
117 }
118 })
119 }
120}
121
122type outcomeSyncStore struct {
123 rootstate.Store
124 fail bool
125}
126
127func (s *outcomeSyncStore) Write(name string, raw []byte) error {
128 if err := s.Store.Write(name, raw); err != nil {
129 return err
130 }
131 if s.fail && strings.HasSuffix(name, ".outcome") {
132 return os.ErrPermission
133 }
134 return nil
135}
136func TestPlan14HistoryRecoveryVisibleSync(t *testing.T) {
137 h, s, b := rootHistoryFixture(t)
138 fault := &outcomeSyncStore{Store: s, fail: true}
139 h.Store = fault
140 now := time.Now().UTC()
141 r := HistoryRecovery{Binding: b, Attempt: HistoryAttempt{ID: "attempt", Source: "canonical", Version: "v2", SHA256: "digest", Platform: AdoptPlatform(), Started: now, PromotedAt: now}, Settled: true, Verified: true}
142 if err := h.RetainRecovery(r); err == nil {
143 t.Fatal("visible unsynced outcome reported durable")
144 }
145 raw, err := s.Read(historyRecoveryKey(r))
146 if err != nil {
147 t.Fatal(err)
148 }
149 if _, err := h.Refusals(b); err == nil {
150 t.Fatal("visible unsynced outcome granted permission")
151 }
152 fault.fail = false
153 if refs, err := h.Refusals(b); err != nil || len(refs) != 0 {
154 t.Fatalf("durable retry: %+v %v", refs, err)
155 }
156 after, err := s.Read(historyRecoveryKey(r))
157 if err != nil || !bytes.Equal(raw, after) {
158 t.Fatal("retry changed evidence", err)
159 }
160}
161