mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-19 22:17:42 +02:00
Three ways the installation could still end up with its source, its binary and the operator's content describing different things. Rollback copied aside only *tracked* protected files, so a role upstream had deleted and the operator had recreated at the same path was untracked - and `git reset --hard` wrote the old commit's copy over it. The stash now also covers untracked protected paths the target commit writes to, and names them in keptContent like any other file it saved. The pending-build marker said only "a compile is owed". An update that found new commits therefore merged them and built the *new* tree, leaving `.prev` holding a binary for a commit the state file no longer named - a rollback then paired old source with the original binary. And a marker left behind after a successful swap rebuilt and overwrote that same `.prev`. The marker now names its target commit and the hashes on both sides of the swap, the state records which commit the live binary came from, an owed build is finished before the tree moves again, and a retry that finds the swap already done clears the marker instead of repeating it. Rollback verifies that pairing before touching anything and refuses (binary_changed) rather than reset the source beside a binary it cannot account for; an update whose build failed never swapped one, so its rollback moves the source only and no longer demands a `.prev`. The version pair is recorded before the config is written, so a crash in between still leaves a rollback that can put the old number back.
115 lines
4.7 KiB
Go
115 lines
4.7 KiB
Go
package update
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// TestRollbackKeepsOperatorEditsToProtectedFiles pins the promise docs make - a rollback
|
|
// undoes the update, not the operator's content. reset --hard discards every local change
|
|
// to tracked files, not just the update's, so both an edit made before the update and one
|
|
// made after it have to be put back on top of the old commit.
|
|
func TestRollbackKeepsOperatorEditsToProtectedFiles(t *testing.T) {
|
|
requireGit(t)
|
|
tr := newTree(t)
|
|
|
|
// Edited before the update: Apply itself keeps this one (its stash covers it), which is
|
|
// exactly the shape the rollback then used to wipe.
|
|
writeFile(t, filepath.Join(tr.install, "roles", "shipped.yaml"), "name: shipped\ndescription: mine before\n")
|
|
tr.upstreamCommit(t, "upstream moves code and rewrites roles", map[string]string{
|
|
"internal_service.go": "package service\n\nconst Version = \"2\"\n",
|
|
"roles/shipped.yaml": "name: shipped\ndescription: upstream rewrite\n",
|
|
"roles/extra.yaml": "name: extra\ndescription: upstream copy\n",
|
|
})
|
|
if _, err := Apply(context.Background(), opts(tr), nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Edited after the update: no stash ever saw this one.
|
|
writeFile(t, filepath.Join(tr.install, "roles", "extra.yaml"), "name: extra\ndescription: mine after\n")
|
|
|
|
// The binary pair a swap leaves behind, so the rollback has one to restore.
|
|
bin := filepath.Join(tr.install, Binary)
|
|
writeFile(t, bin, "new binary bytes")
|
|
writeFile(t, bin+".prev", "old binary bytes")
|
|
|
|
res, err := Rollback(context.Background(), Options{Root: tr.install})
|
|
if err != nil {
|
|
t.Fatalf("rollback failed: %v", err)
|
|
}
|
|
if got := readFile(t, filepath.Join(tr.install, "internal_service.go")); !strings.Contains(got, "Version = \"1\"") {
|
|
t.Errorf("code after rollback = %q, want the old version back", got)
|
|
}
|
|
if got := readFile(t, filepath.Join(tr.install, "roles", "shipped.yaml")); !strings.Contains(got, "mine before") {
|
|
t.Errorf("an edit made before the update was wiped by the rollback: %q", got)
|
|
}
|
|
if got := readFile(t, filepath.Join(tr.install, "roles", "extra.yaml")); !strings.Contains(got, "mine after") {
|
|
t.Errorf("an edit made after the update was wiped by the rollback: %q", got)
|
|
}
|
|
want := "roles/extra.yaml,roles/shipped.yaml"
|
|
if got := strings.Join(res.KeptContent, ","); got != want {
|
|
t.Errorf("keptContent = %v, want %s", res.KeptContent, want)
|
|
}
|
|
}
|
|
|
|
// TestRollbackKeepsAHandDeletionOfAProtectedFile: a deletion is a local change like any
|
|
// other. reset --hard would bring the old commit's copy back; the rollback has to re-apply
|
|
// the deletion instead.
|
|
func TestRollbackKeepsAHandDeletionOfAProtectedFile(t *testing.T) {
|
|
requireGit(t)
|
|
tr := newTree(t)
|
|
tr.upstreamCommit(t, "upstream moves", map[string]string{"internal_service.go": "package service\n\nconst Version = \"2\"\n"})
|
|
if _, err := Apply(context.Background(), opts(tr), nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
gone := filepath.Join(tr.install, "roles", "shipped.yaml")
|
|
if err := os.Remove(gone); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
bin := filepath.Join(tr.install, Binary)
|
|
writeFile(t, bin, "new binary bytes")
|
|
writeFile(t, bin+".prev", "old binary bytes")
|
|
|
|
res, err := Rollback(context.Background(), Options{Root: tr.install})
|
|
if err != nil {
|
|
t.Fatalf("rollback failed: %v", err)
|
|
}
|
|
if _, err := os.Stat(gone); !os.IsNotExist(err) {
|
|
t.Errorf("a file deleted by hand was resurrected by the rollback (%v); result %+v", err, res)
|
|
}
|
|
}
|
|
|
|
func TestRollbackKeepsProtectedFileRecreatedAfterUpstreamDeletedIt(t *testing.T) {
|
|
requireGit(t)
|
|
tr := newTree(t)
|
|
if err := os.Remove(filepath.Join(tr.upstream, "roles", "shipped.yaml")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
writeFile(t, filepath.Join(tr.upstream, "internal_service.go"), "package service\n\nconst Version = \"2\"\n")
|
|
mustGit(t, tr.upstream, "add", "-A")
|
|
mustGit(t, tr.upstream, "-c", "user.email=t@example.com", "-c", "user.name=T", "commit", "-q", "-m", "upstream deletes the shipped role")
|
|
if _, err := Apply(context.Background(), opts(tr), nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
recreated := filepath.Join(tr.install, "roles", "shipped.yaml")
|
|
writeFile(t, recreated, "name: shipped\ndescription: mine after upstream deleted it\n")
|
|
bin := filepath.Join(tr.install, Binary)
|
|
writeFile(t, bin, "new binary bytes")
|
|
writeFile(t, bin+".prev", "old binary bytes")
|
|
|
|
res, err := Rollback(context.Background(), Options{Root: tr.install})
|
|
if err != nil {
|
|
t.Fatalf("rollback failed: %v", err)
|
|
}
|
|
if got := readFile(t, recreated); !strings.Contains(got, "mine after upstream deleted it") {
|
|
t.Fatalf("rollback overwrote the operator's recreated role: %q", got)
|
|
}
|
|
if got := strings.Join(res.KeptContent, ","); got != "roles/shipped.yaml" {
|
|
t.Fatalf("keptContent = %v, want the recreated role named", res.KeptContent)
|
|
}
|
|
}
|