carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package historyreadback
2
3import (
4 "errors"
5 "os"
6 "strings"
7 "testing"
8 "time"
9
10 "github.com/carlosframework/platform/internal/release"
11)
12
13func TestPlan14ReadReconstructionCompletion(t *testing.T) {
14 for _, fault := range []string{"valid", "invalid serialization", "replacement", "delivery only", "different operation", "different replacement", "different bounds", "namespace", "revision", "pending", "source", "scope", "unknown app", "unknown host", "unknown bake", "missing digest", "signature", "old challenge"} {
15 t.Run(fault, func(t *testing.T) {
16 f := newFixture(t)
17 g := release.HistoryReconstruction{Schema: 1, Kind: "carlos-release-history", Identity: f.status.Identity, Revision: 1, Operation: "reconstruct", IssuedAt: f.now, Account: f.scope.Binding.Account, App: f.scope.Binding.App, Hosts: []release.HistoryReconstructionHost{f.status.Hosts[0].HistoryReconstructionHost}}
18 if fault == "replacement" {
19 g.Replaces = strings.Repeat("d", 64)
20 }
21 f.status.Revision = 2
22 f.status.AppComplete, f.status.AppRevision, f.status.BakeComplete, f.status.BakeRevision = true, 1, 1, 1
23 f.status.Hosts[0].Revision = 1
24 f.status.Hosts[0].RefusalStatus.Complete = true
25 f.status.LastReconstructionDigest = digest(g)
26 switch fault {
27 case "invalid serialization":
28 g.IssuedAt = time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC)
29 f.status.LastReconstructionDigest = ""
30 f.status.Hosts[0].RetirementDigests = nil
31 case "different operation":
32 g.Operation = "other"
33 case "different replacement":
34 g.Replaces = strings.Repeat("e", 64)
35 case "different bounds":
36 g.Production = true
37 case "namespace":
38 f.status.Identity = strings.Repeat("f", 64)
39 case "revision":
40 f.status.Revision++
41 case "pending":
42 f.status.Pending = true
43 case "source":
44 f.status.Hosts[0].Source = "changed"
45 case "scope":
46 g.Account = "xyz"
47 f.status.LastReconstructionDigest = digest(g)
48 case "unknown app":
49 f.status.AppComplete = false
50 case "unknown host":
51 f.status.Hosts[0].RefusalStatus.Complete = false
52 case "unknown bake":
53 f.status.BakeComplete = 0
54 case "missing digest":
55 f.status.LastReconstructionDigest = ""
56 }
57 q, err := RequestStatus(t.Context(), f.directory, f.signer, f.scope, f.now)
58 if err != nil {
59 t.Fatal(err)
60 }
61 if fault != "delivery only" {
62 if err := Respond(t.Context(), f.directory, f.scope, f.key, f.now, f.snapshot); err != nil {
63 t.Fatal(err)
64 }
65 }
66 if fault == "signature" {
67 r := Receipt{Schema: 1, Kind: "carlos-release-history-receipt", RequestDigest: digest(q), ObservedAt: f.now, Status: f.status}
68 wire, err := release.SignRootEnvelope(f.signer, r)
69 if err != nil {
70 t.Fatal(err)
71 }
72 if err := publish(t.Context(), f.directory.Store, ResponseKey(q), wire); err != nil {
73 t.Fatal(err)
74 }
75 }
76 if fault == "old challenge" {
77 q, err = RequestStatus(t.Context(), f.directory, f.signer, f.scope, f.now)
78 if err != nil {
79 t.Fatal(err)
80 }
81 }
82 _, err = ReadReconstruction(t.Context(), f.directory, q, g, f.now)
83 if fault == "valid" || fault == "replacement" {
84 if err != nil {
85 t.Fatal(err)
86 }
87 } else if err == nil {
88 t.Fatal("unproven reconstruction reported complete")
89 }
90 })
91 }
92}
93
94func TestPlan14ReadRetirementCompletion(t *testing.T) {
95 for _, fault := range []string{"valid", "invalid serialization", "different operation", "different outcome", "different witness", "missing digest", "missing retired", "unknown originals", "pending original", "unrelated original", "pending reconstruction", "source", "namespace", "scope", "delivery only"} {
96 t.Run(fault, func(t *testing.T) {
97 f := newFixture(t)
98 g := release.HistoryRecoveryRetirement{Schema: 1, Kind: "carlos-release-recovery-retirement", Identity: f.status.Identity, Operation: "retire", IssuedAt: f.now, Binding: f.scope.Binding, Source: "canonical", AttemptID: "failed", OutcomeDigest: strings.Repeat("d", 64), VerifiedID: "good", VerifiedDigest: strings.Repeat("e", 64)}
99 f.status.RecoverySources = &release.HistoryRecoverySources{Known: true}
100 f.status.Hosts[0].RetiredRecoveries = []string{g.AttemptID}
101 f.status.Hosts[0].RetirementDigests = map[string]string{g.AttemptID: digest(g)}
102 f.status.Hosts[0].RecoveryDigests = map[string]string{g.AttemptID: g.OutcomeDigest, g.VerifiedID: g.VerifiedDigest}
103 switch fault {
104 case "invalid serialization":
105 g.IssuedAt = time.Date(10000, 1, 1, 0, 0, 0, 0, time.UTC)
106 f.status.LastReconstructionDigest = ""
107 f.status.Hosts[0].RetirementDigests = nil
108 case "different operation":
109 g.Operation = "other"
110 case "different outcome":
111 g.OutcomeDigest = strings.Repeat("f", 64)
112 case "different witness":
113 g.VerifiedDigest = strings.Repeat("f", 64)
114 case "missing digest":
115 f.status.Hosts[0].RetirementDigests = nil
116 case "missing retired":
117 f.status.Hosts[0].RetiredRecoveries = nil
118 case "unknown originals":
119 f.status.RecoverySources.Known = false
120 case "pending original", "unrelated original":
121 id := g.AttemptID
122 if fault == "unrelated original" {
123 id = "unrelated"
124 }
125 f.status.RecoverySources.Unresolved = []release.HistoryRecovery{{Binding: g.Binding, Attempt: release.HistoryAttempt{ID: id}}}
126 case "pending reconstruction":
127 f.status.Pending = true
128 case "source":
129 f.status.Hosts[0].Source = "changed"
130 case "namespace":
131 f.status.Identity = strings.Repeat("f", 64)
132 case "scope":
133 g.Binding.Host = "other.example"
134 f.status.Hosts[0].RetirementDigests[g.AttemptID] = digest(g)
135 }
136 q, err := RequestStatus(t.Context(), f.directory, f.signer, f.scope, f.now)
137 if err != nil {
138 t.Fatal(err)
139 }
140 if fault != "delivery only" {
141 if err := Respond(t.Context(), f.directory, f.scope, f.key, f.now, f.snapshot); err != nil {
142 t.Fatal(err)
143 }
144 }
145 receipt, err := ReadRetirement(t.Context(), f.directory, q, g, f.now)
146 if fault == "valid" || fault == "unrelated original" {
147 if err != nil {
148 t.Fatal(err)
149 }
150 if fault == "unrelated original" && len(receipt.Status.RecoverySources.Unresolved) != 1 {
151 t.Fatal("lost unrelated obligation")
152 }
153 } else if err == nil {
154 t.Fatal("unproven retirement reported complete")
155 }
156 if fault == "delivery only" && !errors.Is(err, os.ErrNotExist) {
157 t.Fatalf("unexpected missing response: %v", err)
158 }
159 })
160 }
161}
162