| 1 | package host |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "path/filepath" |
| 6 | "testing" |
| 7 | |
| 8 | "github.com/carlosframework/platform/internal/release" |
| 9 | "github.com/carlosframework/platform/internal/rootstate" |
| 10 | ) |
| 11 | |
| 12 | func TestPlan14IdenticalVersionKeepsRollbackEvidence(t *testing.T) { |
| 13 | if !plan14RootFixture(t) { |
| 14 | return |
| 15 | } |
| 16 | e, rt, u, c, _ := plan14AdoptionFixture(t) |
| 17 | if err := os.MkdirAll("/var/lib", 0755); err != nil { |
| 18 | t.Fatal(err) |
| 19 | } |
| 20 | if err := rootstate.EnsurePublicDir(release.RootHealthyDir); err != nil { |
| 21 | t.Fatal(err) |
| 22 | } |
| 23 | evidence := release.ReadHealthy(e.bake, rt.Account, rt.App)[0] |
| 24 | if err := release.RecordHealthy(release.RootHealthyDir, rt.Account, rt.App, evidence); err != nil { |
| 25 | t.Fatal(err) |
| 26 | } |
| 27 | original, err := os.ReadFile(filepath.Join(release.RootHealthyDir, "healthy", rt.Account, rt.App+".json")) |
| 28 | if err != nil { |
| 29 | t.Fatal(err) |
| 30 | } |
| 31 | raw, err := os.ReadFile(u.exe) |
| 32 | if err != nil { |
| 33 | t.Fatal(err) |
| 34 | } |
| 35 | v2 := release.VersionPath(u.opt, rt.Account, "v2", rt.App) |
| 36 | if err := os.MkdirAll(filepath.Dir(v2), 0755); err != nil { |
| 37 | t.Fatal(err) |
| 38 | } |
| 39 | if err := os.WriteFile(v2, raw, 0755); err != nil { |
| 40 | t.Fatal(err) |
| 41 | } |
| 42 | if err := release.PointLive(u.opt, rt.Host, rt.Account, "v2", rt.App); err != nil { |
| 43 | t.Fatal(err) |
| 44 | } |
| 45 | u.exe = v2 |
| 46 | makeProofAncestorsPublic(t, u.exe) |
| 47 | makeProofAncestorsPublic(t, u.props.FragmentPath) |
| 48 | s := productionProofBindingSource(e.store, u, u.opt) |
| 49 | s.unitDirs = []string{filepath.Dir(u.props.FragmentPath)} |
| 50 | c.resolve, c.host, c.rollback, c.prepareRollback, c.targetFile = s.resolve, s.forHost, s.rollback, s.prepareRollback, rootstate.OpenTrustedFile |
| 51 | c.attempts = productionRootRestartChecks(u, u.opt).attempts |
| 52 | e.root.obs.resolve = s.resolve |
| 53 | e.bake = release.RootHealthyDir |
| 54 | if err := e.root.obs.recordSettled(e.bake)(rt); err != nil { |
| 55 | t.Fatal(err) |
| 56 | } |
| 57 | b, err := s.resolve(t.Context(), rt) |
| 58 | if err != nil { |
| 59 | t.Fatal(err) |
| 60 | } |
| 61 | if b.Version != "v2" || !b.Historical || b.History == nil || *b.History != evidence { |
| 62 | t.Fatalf("binding: %+v", b) |
| 63 | } |
| 64 | captured := false |
| 65 | u.afterRestart = func() { |
| 66 | state, err := c.attempts() |
| 67 | if err != nil { |
| 68 | t.Fatal(err) |
| 69 | } |
| 70 | defer state.Close() |
| 71 | _, b, err := loadRollback(state, rt.Host, c.now()) |
| 72 | if err != nil { |
| 73 | t.Fatal(err) |
| 74 | } |
| 75 | if b.Version != "v2" || b.History.Version != "v1" { |
| 76 | t.Fatal("evidence rewritten", b) |
| 77 | } |
| 78 | if _, err := s.prepareRollback(t.Context(), b); err != nil { |
| 79 | t.Fatal("v2 rollback not authorized", err) |
| 80 | } |
| 81 | captured = true |
| 82 | } |
| 83 | if err := checkedUnitRestart(u, u.opt, func(string) string { return "5s" }, e.root.obs.recordSettled(e.bake), c)(rt); err != nil { |
| 84 | t.Fatal(err) |
| 85 | } |
| 86 | if !captured || len(u.effects) != 1 { |
| 87 | t.Fatal("restart not captured", u.effects) |
| 88 | } |
| 89 | after, err := os.ReadFile(filepath.Join(release.RootHealthyDir, "healthy", rt.Account, rt.App+".json")) |
| 90 | if err != nil { |
| 91 | t.Fatal(err) |
| 92 | } |
| 93 | if string(after) != string(original) { |
| 94 | t.Fatal("B4 evidence changed") |
| 95 | } |
| 96 | } |
| 97 | |