| index 5071e7b82..97539e2c4 100644 |
| --- a/e2e_test.go |
| +++ b/e2e_test.go |
| @@ -1353,6 +1353,80 @@ func TestAddUnitBackingRequiresDBForHibernate(t *testing.T) { |
| } |
| } |
| |
| +// A relative -db on an agent-exec route must be refused at add time. The |
| +// activation contract's "relative -db resolves inside $STATE_DIRECTORY" |
| +// convention is real for unit tenants (systemd sets it) but agent-exec |
| +// children are spawned without it and the activator consumes the path |
| +// verbatim (Restore, Seed, Start --db, Replicate) — so a relative path |
| +// resolves against an unwritable cwd and every wake dies as an edge 503, |
| +// discoverable only by correlating 503s with child stderr in the journal |
| +// (issue #70, vitogo's first deploy). |
| +func TestAddRefusesRelativeDBOnExecRoutes(t *testing.T) { |
| + goBin, err := exec.LookPath("go") |
| + if err != nil { |
| + t.Skip("go toolchain not found") |
| + } |
| + tmp := t.TempDir() |
| + carlosBin := filepath.Join(tmp, "carlos") |
| + build := exec.Command(goBin, "build", "-o", carlosBin, ".") |
| + build.Env = os.Environ() |
| + if out, err := build.CombinedOutput(); err != nil { |
| + t.Fatalf("build carlos: %v\n%s", err, out) |
| + } |
| + regPath := filepath.Join(tmp, "registry.db") |
| + |
| + run := func(args ...string) (string, error) { |
| + cmd := exec.Command(carlosBin, args...) |
| + cmd.Env = carlosTestEnv(t) |
| + cmd.Dir = tmp |
| + out, err := cmd.CombinedOutput() |
| + return string(out), err |
| + } |
| + |
| + // The vitogo add, verbatim shape: exec backing (the default), relative -db. |
| + out, err := run("add", "-registry", regPath, "-app", "vitogo", |
| + "-kind", "instance", "-db", "vito.db", "-hibernate", |
| + "-channel", "canary/vitogo", "vito.example.com") |
| + if err == nil { |
| + t.Fatalf("add with relative -db on an exec route: want non-zero exit, got 0\n%s", out) |
| + } |
| + // The refusal must carry the diagnosis and the way out — not just "no". |
| + if !strings.Contains(out, "STATE_DIRECTORY") || !strings.Contains(out, "absolute") { |
| + t.Errorf("refusal = %q, want it to explain STATE_DIRECTORY and require an absolute path", out) |
| + } |
| + // And it must refuse BEFORE writing: no half-registered route. |
| + reg, err := registry.Open(regPath) |
| + if err != nil { |
| + t.Fatalf("open registry: %v", err) |
| + } |
| + defer reg.Close() |
| + rt, err := reg.Get("vito.example.com") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if rt != nil { |
| + t.Fatalf("refused add still wrote a route: %+v", rt) |
| + } |
| + |
| + // A unit tenant keeps the contract: StateDirectory= is real there, a |
| + // relative -db is the documented convention, and this guard must not |
| + // take it away. |
| + out, err = run("add", "-registry", regPath, "-app", "vitogo", |
| + "-kind", "instance", "-backing", "unit", "-db", "vito.db", |
| + "unit.example.com") |
| + if err != nil { |
| + t.Fatalf("add -backing unit with relative -db: %v\n%s", err, out) |
| + } |
| + |
| + // An absolute -db on an exec route is untouched by the guard. |
| + out, err = run("add", "-registry", regPath, "-app", "vitogo", |
| + "-kind", "instance", "-db", "/data/vito.example.com.db", |
| + "vito.example.com") |
| + if err != nil { |
| + t.Fatalf("add with absolute -db: %v\n%s", err, out) |
| + } |
| +} |
| + |
| // TestAddAccountIDRefusesAName pins the whole -account-id gate end to end, |
| // on the verb where getting it wrong is quietest. `carlos add -account-id |
| // ops` used to succeed: "ops" passes release.ValidAccount (^[a-z]{3,16}$), |