package release import ( "encoding/json" "os" "testing" ) func TestPlan14HistoryReconstructionCompletion(t *testing.T) { for _, boundary := range []string{"before", "intent", "complete", "unsynced"} { t.Run(boundary, func(t *testing.T) { h, s, b, pub, wire, now := reconstructionCrashFixture(t) var g HistoryReconstruction if err := VerifyRootEnvelope(pub, wire, &g); err != nil { t.Fatal(err) } raw, _ := json.Marshal(g) resolve := func(HistoryBinding) (string, error) { return "canonical", nil } if boundary != "before" { if boundary == "intent" { h.Store = &reconstructionCrashStore{healthyRetryStore: s, stage: "intent", fail: true} } err := h.Reconstruct(pub, wire, b.Account, b.App, now, resolve) if (boundary == "intent") != (err != nil) { t.Fatalf("reconstruct: %v", err) } } h = &RootHistory{Store: s} if boundary == "unsynced" { s.fail = 1 } view, err := h.MigrationStatus(b, resolve) if boundary == "unsynced" { if err == nil { t.Fatal("unsynced completion published") } view, err = h.MigrationStatus(b, resolve) } if err != nil { t.Fatal(err) } want := "" if boundary == "complete" || boundary == "unsynced" { want = reconstructionDigest(raw) } if view.LastReconstructionDigest != want { t.Fatalf("completion=%q want %q", view.LastReconstructionDigest, want) } }) } } func TestPlan14HistoryRetirementCompletion(t *testing.T) { for _, fault := range []string{"valid", "missing", "unsynced", "changed outcome", "pending original", "changed source"} { t.Run(fault, func(t *testing.T) { h, s, old, _, g, pub, key := retirementFixture(t) if fault != "missing" { if err := retireForTest(h, g, pub, key); err != nil { t.Fatal(err) } } raw, _ := json.Marshal(g) if fault == "changed outcome" { old.Verified = true if err := h.RetainRecovery(old); err != nil { t.Fatal(err) } } if fault == "unsynced" { h.Store = &retirementSyncStore{Store: s, fail: true} } source := "canonical" if fault == "changed source" { source = "changed" } sources := HistoryRecoverySources{Known: true} if fault == "pending original" { old.Settled = false sources.Unresolved = []HistoryRecovery{old} } view, err := h.MigrationStatusWithSources(old.Binding, func(HistoryBinding) (string, error) { return source, nil }, sources) if fault == "unsynced" { if err == nil { t.Fatal("unsynced retirement published") } h.Store = s view, err = h.MigrationStatusWithSources(old.Binding, func(HistoryBinding) (string, error) { return source, nil }, sources) } if err != nil { t.Fatal(err) } want := "" if fault == "valid" || fault == "unsynced" { want = reconstructionDigest(raw) } if len(view.Hosts) != 1 || view.Hosts[0].RetirementDigests[old.Attempt.ID] != want { t.Fatalf("status=%+v want %q", view, want) } if fault == "pending original" { if len(view.RecoverySources.Unresolved) != 1 { t.Fatal("lost original") } } if _, err := s.Read(historyRecoveryKey(old)); os.IsNotExist(err) { t.Fatal("deleted retained outcome") } }) } }