carlos / platform Public

test: the re-render test proves SyncExec rendered and checks its errors, the moves tests print lengths, and a flat row's Start is pinned end to end (end sweep, tasks 3 and 7)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev c283e1b2abee004fa9c4dc892b3ac8e6464e1fde parent 69fd647
4 files changed, +66 −9
  • internal/activator/instancekey_test.go +31 −3
  • internal/appconfig/syncexec_instancekey_test.go +31 −4
  • internal/moves/archive/instancekey_test.go +1 −1
  • internal/moves/envreport_instancekey_test.go +3 −1
diff --git a/internal/activator/instancekey_test.go b/internal/activator/instancekey_test.go
index 649c13c23..aab0d36a0 100644
--- a/internal/activator/instancekey_test.go
+++ b/internal/activator/instancekey_test.go
@@ -646,11 +646,17 @@ func TestEnsureInstanceKeySymlinkErrorNamesThePathOnce(t *testing.T) {
}
// startRecorded starts host on b with the recording binary and returns the
-// environment the child saw, or nil when no child ran within a second.
+// environment the child saw, or nil when no child ran within a second. The
+// route's socket is a temp path, so the row classifies as foreign;
+// startRecordedRoute takes the row as given.
func startRecorded(t *testing.T, b *ExecBackend, host string) (map[string]string, error) {
t.Helper()
- _, envPath := stageRecordingLiveFullEnv(t, b, host, "v1")
- rt := registry.Route{Host: host, Kind: "instance", Socket: tmpSock(t)}
+ return startRecordedRoute(t, b, registry.Route{Host: host, Kind: "instance", Socket: tmpSock(t)})
+}
+
+func startRecordedRoute(t *testing.T, b *ExecBackend, rt registry.Route) (map[string]string, error) {
+ t.Helper()
+ _, envPath := stageRecordingLiveFullEnv(t, b, rt.Host, "v1")
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
proc, _, err := b.Start(ctx, rt) // the fake binary never binds: the health wait fails, as in the sibling tests
@@ -691,6 +697,28 @@ func TestExecBackendStartHandsTheChildTheMintedInstanceKey(t *testing.T) {
}
}
+// Task 3 review (M4): the tests above launch through the foreign arm, since
+// a temp socket is neither default. A row that classifies as flat goes
+// through the same spawn body, and this pins it end to end: Start mints
+// into <DataDir>/<host>.env and hands the child that key.
+func TestExecBackendStartHandsAFlatRowsChildTheMintedInstanceKey(t *testing.T) {
+ b := testExecBackend(t)
+ b.RunDirOverride = shortRunDir(t)
+ host := "shop.example.com"
+ rt := flatGenRoute(b, host)
+ if l, why := b.Layout(rt); l != LayoutFlat {
+ t.Fatalf("fixture classifies %q (%s), want flat", l, why)
+ }
+ child, _ := startRecordedRoute(t, b, rt)
+ minted, err := readEnvFile(filepath.Join(b.DataDir, host+".env"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ if child == nil || !wellFormedInstanceKey(minted[InstanceKeyVar]) || child[InstanceKeyVar] != minted[InstanceKeyVar] {
+ t.Fatal("a flat row's child was not handed the key minted into <host>.env")
+ }
+}
+
// Finding 2 through a launch: while the key's sync fails, Start fails and
// no child runs. Once the sync succeeds, the child is handed the key the
// failed attempt wrote, never a second one.
diff --git a/internal/appconfig/syncexec_instancekey_test.go b/internal/appconfig/syncexec_instancekey_test.go
index cabb16f72..99f496c4f 100644
--- a/internal/appconfig/syncexec_instancekey_test.go
+++ b/internal/appconfig/syncexec_instancekey_test.go
@@ -3,6 +3,7 @@ package appconfig
import (
"os"
"path/filepath"
+ "strings"
"testing"
"github.com/carlosframework/platform/internal/registry"
@@ -13,13 +14,17 @@ import (
// name in the app's configuration (L21, never overwrite: re-render).
func TestSyncExecLeavesTheInstanceKeyAlone(t *testing.T) {
ctx, s, reg, dd, priv, _ := execKit(t)
- pk, _ := ParsePrivateKey(priv, "test")
+ pk, err := ParsePrivateKey(priv, "test")
+ if err != nil {
+ t.Fatal(err)
+ }
own := filepath.Join(dd, "shop.example.com.env")
body := []byte("CARLOS_ADMIN_TOKEN=tok\nCARLOS_INSTANCE_KEY=q+/Zk3vJ0aW1Yx9sR7tUe2oPl4mN8bC6dF5gH0iJ1kA=\n")
if err := os.WriteFile(own, body, 0o600); err != nil {
t.Fatal(err)
}
- if err := reg.Upsert(registry.Route{Host: "shop.example.com", Kind: "instance", App: "shop", Account: "acct", Status: "ready", Socket: "/run/carlos/shop.sock"}); err != nil {
+ rt := registry.Route{Host: "shop.example.com", Kind: "instance", App: "shop", Account: "acct", Status: "ready", Socket: "/run/carlos/shop.sock"}
+ if err := reg.Upsert(rt); err != nil {
t.Fatal(err)
}
for _, vars := range []map[string]string{{"A": "1"}, {"A": "2", "CARLOS_INSTANCE_KEY": "from-config"}} {
@@ -29,8 +34,30 @@ func TestSyncExecLeavesTheInstanceKeyAlone(t *testing.T) {
if err := SyncExec(ctx, reg, s, pk, dd, func(string) error { return nil }, "", nil); err != nil {
t.Fatal(err)
}
- if got, _ := os.ReadFile(own); string(got) != string(body) {
- t.Fatalf("a re-render changed the instance's own env file: %q", got)
+ // The render happened: <host>.config.env carries this pass's
+ // variables, the configured key included. Without this the test
+ // would pass against a SyncExec that skipped the route.
+ rendered, err := os.ReadFile(ConfigEnvPath(dd, rt))
+ if err != nil {
+ t.Fatalf("no rendered configuration after SyncExec: %v", err)
+ }
+ got := map[string]string{}
+ for _, line := range strings.Split(string(rendered), "\n") {
+ if k, v, ok := strings.Cut(line, "="); ok {
+ got[k] = v
+ }
+ }
+ for k, v := range vars {
+ if got[k] != v {
+ t.Fatalf("rendered configuration lacks %s=%s (%d bytes, %d variables)", k, v, len(rendered), len(got))
+ }
+ }
+ after, err := os.ReadFile(own)
+ if err != nil {
+ t.Fatalf("own env file after the render: %v", err)
+ }
+ if string(after) != string(body) {
+ t.Fatalf("a re-render changed the instance's own env file: %d bytes, want %d", len(after), len(body))
}
}
}
diff --git a/internal/moves/archive/instancekey_test.go b/internal/moves/archive/instancekey_test.go
index 1f7abb192..2d5114593 100644
--- a/internal/moves/archive/instancekey_test.go
+++ b/internal/moves/archive/instancekey_test.go
@@ -25,6 +25,6 @@ func TestArchiveCarriesTheInstanceKeyByteForByte(t *testing.T) {
}
got, err := os.ReadFile(outEnv)
if err != nil || !bytes.Equal(got, body) {
- t.Fatalf("extracted env differs from the source's (err %v)", err)
+ t.Fatalf("extracted env differs from the source's: %d bytes, want %d (err %v)", len(got), len(body), err)
}
}
diff --git a/internal/moves/envreport_instancekey_test.go b/internal/moves/envreport_instancekey_test.go
index 141a19e0d..f38e07f42 100644
--- a/internal/moves/envreport_instancekey_test.go
+++ b/internal/moves/envreport_instancekey_test.go
@@ -19,8 +19,10 @@ func TestEnvReportComparesTheInstanceKey(t *testing.T) {
{map[string]string{name: "a2V5LXR3bw==", "CARLOS_REGION": "uk"}, "differs"},
{map[string]string{"CARLOS_REGION": "uk"}, "missing"},
} {
+ // The kind is the whole check: T3 (movesreport.go) fails on every
+ // kind but advisory-differs, which only an advisory variable gets.
diffs := CompareEnv(src, ReportEnv(key, tc.target), false)
- if len(diffs) != 1 || diffs[0].Name != name || diffs[0].Kind != tc.want || diffs[0].Note() {
+ if len(diffs) != 1 || diffs[0].Name != name || diffs[0].Kind != tc.want {
t.Fatalf("diffs = %+v, want one %s on %s", diffs, tc.want, name)
}
}