test(app): run broker client tests against the production layout
- commit
- f7c33a77d7519b17383c799b123043b88c18c383
- author
- brice · · 2026-09-23 15:16 UTC
- parent
- 52bb3d7
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +28 -79
M internal/app/broker_offline_client_test.go +22 -74
| @@ -19,9 +19,9 @@ import ( | |||
| 19 | 19 | "github.com/coder/websocket" | |
| 20 | 20 | "github.com/stretchr/testify/require" | |
| 21 | 21 | ||
| 22 | - | "github.com/bnema/vev/internal/adapters/brokerconfig" | |
| 23 | 22 | "github.com/bnema/vev/internal/adapters/brokeripc" | |
| 24 | 23 | "github.com/bnema/vev/internal/adapters/clock" | |
| 24 | + | "github.com/bnema/vev/internal/adapters/daemonidentity" | |
| 25 | 25 | "github.com/bnema/vev/internal/adapters/daemonmux" | |
| 26 | 26 | "github.com/bnema/vev/internal/adapters/ipc" | |
| 27 | 27 | "github.com/bnema/vev/internal/adapters/pty" | |
| @@ -30,6 +30,7 @@ import ( | |||
| 30 | 30 | "github.com/bnema/vev/internal/adapters/webterm" | |
| 31 | 31 | "github.com/bnema/vev/internal/domain" | |
| 32 | 32 | "github.com/bnema/vev/internal/persist" | |
| 33 | + | "github.com/bnema/vev/internal/platform" | |
| 33 | 34 | "github.com/bnema/vev/internal/ports" | |
| 34 | 35 | "github.com/bnema/vev/internal/protocol" | |
| 35 | 36 | "github.com/bnema/vev/internal/usecase/daemon" | |
| @@ -55,13 +56,13 @@ type offlineClientFixture struct { | |||
| 55 | 56 | ||
| 56 | 57 | func startOfflineClientFixture(t *testing.T) offlineClientFixture { | |
| 57 | 58 | t.Helper() | |
| 58 | - | // The default sandbox shell prints one marker and then parks on read, so a | |
| 59 | + | // The default isolated production fixture shell prints one marker and then parks on read, so a | |
| 59 | 60 | // client proves it reached a session without depending on shell echo of | |
| 60 | 61 | // injected input. | |
| 61 | 62 | return startOfflineClientFixtureWithShell(t, "/bin/sh", []string{"-c", "sleep 0.1; printf offline-ready; trap : TERM; while :; do read line || exit; done"}) | |
| 62 | 63 | } | |
| 63 | 64 | ||
| 64 | - | // startOfflineClientFixtureWithShell starts the same private broker sandbox with | |
| 65 | + | // startOfflineClientFixtureWithShell starts the same isolated production broker with | |
| 65 | 66 | // an explicitly supplied daemon shell, so a browser test can drive real session | |
| 66 | 67 | // content and real session actions (echo and command output) through the | |
| 67 | 68 | // ordinary client path instead of a parked shell. | |
| @@ -70,38 +71,29 @@ func startOfflineClientFixtureWithShell(t *testing.T, shell string, shellArgs [] | |||
| 70 | 71 | return startPersistentOfflineClientFixture(t, shell, shellArgs, false) | |
| 71 | 72 | } | |
| 72 | 73 | ||
| 73 | - | // startPersistentOfflineClientFixture starts the same private broker sandbox but | |
| 74 | - | // additionally binds the daemon to real session persistence under the sandbox | |
| 74 | + | // startPersistentOfflineClientFixture starts the same isolated production broker but | |
| 75 | + | // additionally binds the daemon to real session persistence under the production daemon | |
| 75 | 76 | // state directory. It exists so a test can prove which lifecycle state the | |
| 76 | 77 | // daemon actually persists: a named session appears in the durable catalogue, | |
| 77 | 78 | // while an ephemeral one is never written. | |
| 78 | 79 | func startPersistentOfflineClientFixture(t *testing.T, shell string, shellArgs []string, persistEnabled bool) offlineClientFixture { | |
| 79 | 80 | t.Helper() | |
| 80 | - | root, prodRuntime, prodState := isolateSandboxEnv(t) | |
| 81 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | |
| 82 | - | route := filepath.Join(root, "local-mux.sock") | |
| 83 | - | policy := brokerTestPolicy() | |
| 84 | - | writeSandboxConfig(t, root, map[string]any{ | |
| 85 | - | "marker": brokerconfig.Marker, "registrations": []any{}, | |
| 86 | - | "local": map[string]any{ | |
| 87 | - | "identity": brokerLocalTestIdentity, "displayOrigin": "local", "route": route, | |
| 88 | - | "policy": map[string]any{"protocolVersion": policy.ProtocolVersion, "catalogSchemaVersion": policy.CatalogSchemaVersion, "environmentPolicy": "client-owned", "transport": policy.Transport, "trust": policy.Trust, "launch": policy.Launch, "isolation": policy.Isolation}, | |
| 89 | - | }, | |
| 90 | - | }) | |
| 91 | - | layout, err := offlineLayout(root) | |
| 81 | + | layout := emptyProductionBrokerLayout(t, "") | |
| 82 | + | root := layout.Root | |
| 83 | + | prodRuntime, prodState := os.Getenv("XDG_RUNTIME_DIR"), os.Getenv("XDG_STATE_HOME") | |
| 84 | + | identity, err := daemonidentity.LoadOrCreate(platform.StateDir()) | |
| 92 | 85 | require.NoError(t, err) | |
| 93 | - | for _, dir := range []string{layout.Root, layout.Runtime, layout.State, layout.Log} { | |
| 94 | - | require.NoError(t, safedir.EnsurePrivate(dir)) | |
| 95 | - | } | |
| 96 | - | require.NoError(t, layout.VerifyCreated()) | |
| 97 | - | ||
| 86 | + | require.NoError(t, ensureProductionBrokerConfig()) | |
| 87 | + | route := daemonmux.SocketPath(ipc.SocketDir()) | |
| 88 | + | policy := localDaemonPolicy() | |
| 89 | + | require.NoError(t, safedir.EnsurePrivate(ipc.SocketDir())) | |
| 98 | 90 | rawListener, err := ipc.ListenMux(route) | |
| 99 | 91 | require.NoError(t, err) | |
| 100 | 92 | physicalSeen := make(chan struct{}, 8) | |
| 101 | 93 | var physicalMu sync.Mutex | |
| 102 | 94 | var physicalConnections []io.Closer | |
| 103 | 95 | aggregate := daemonmux.NewAggregateListener() | |
| 104 | - | binding, err := daemonmux.NewServerBinding(brokerLocalTestIdentity, ports.BrokerDaemonIncarnation{1}, policy) | |
| 96 | + | binding, err := daemonmux.NewServerBinding(identity, ports.BrokerDaemonIncarnation{1}, policy) | |
| 105 | 97 | require.NoError(t, err) | |
| 106 | 98 | mux, err := daemonmux.NewServerSupervisor(aggregate, binding, daemonmux.DefaultMuxCeilings(), 8) | |
| 107 | 99 | require.NoError(t, err) | |
| @@ -127,7 +119,7 @@ func startPersistentOfflineClientFixture(t *testing.T, shell string, shellArgs [ | |||
| 127 | 119 | daemonOpts := []daemon.Option{daemon.WithShell(shell, shellArgs)} | |
| 128 | 120 | var fixtureCatalogue *persist.Persister | |
| 129 | 121 | if persistEnabled { | |
| 130 | - | stateDir := filepath.Join(root, "state") | |
| 122 | + | stateDir := platform.StateDir() | |
| 131 | 123 | opened, err := persist.OpenOrCreate(stateDir) | |
| 132 | 124 | require.NoError(t, err) | |
| 133 | 125 | repository := snapshot.NewRepository(filepath.Join(stateDir, "snapshots")) | |
| @@ -146,16 +138,16 @@ func startPersistentOfflineClientFixture(t *testing.T, shell string, shellArgs [ | |||
| 146 | 138 | // a manual clock rooted at Unix zero would correctly make the observation | |
| 147 | 139 | // stale to the real client clock before initial navigation resolves. | |
| 148 | 140 | deps, ready := testBrokerServeDeps(clock.New()) | |
| 149 | - | brokerDone := runSandbox(ctx, brokerServeOptions{offlineRoot: root, idleGrace: time.Hour}, deps) | |
| 141 | + | brokerDone := runSandbox(ctx, brokerServeOptions{production: true, idleGrace: time.Hour}, deps) | |
| 150 | 142 | var socket string | |
| 151 | 143 | select { | |
| 152 | 144 | case socket = <-ready: | |
| 153 | 145 | case serveErr := <-brokerDone: | |
| 154 | - | t.Fatalf("offline broker exited before readiness: %v", serveErr) | |
| 146 | + | t.Fatalf("production broker exited before readiness: %v", serveErr) | |
| 155 | 147 | case <-time.After(brokerTestWait): | |
| 156 | - | t.Fatal("offline broker did not become ready") | |
| 148 | + | t.Fatal("production broker did not become ready") | |
| 157 | 149 | } | |
| 158 | - | // The sandbox broker becomes socket-ready before its first local daemon | |
| 150 | + | // The production broker becomes socket-ready before its first local daemon | |
| 159 | 151 | // observation is committed. Product clients start from a committed local | |
| 160 | 152 | // authority; wait for the fixture to provide the same contract so one-shot | |
| 161 | 153 | // initial navigation is not consumed against an artificial empty catalogue. | |
| @@ -174,7 +166,7 @@ func startPersistentOfflineClientFixture(t *testing.T, shell string, shellArgs [ | |||
| 174 | 166 | default: | |
| 175 | 167 | } | |
| 176 | 168 | return false | |
| 177 | - | }, brokerTestWait, 5*time.Millisecond, "offline broker never committed its local daemon authority") | |
| 169 | + | }, brokerTestWait, 5*time.Millisecond, "production broker never committed its local daemon authority") | |
| 178 | 170 | subscription.Close() | |
| 179 | 171 | require.NoError(t, probe.Close()) | |
| 180 | 172 | t.Cleanup(func() { | |
| @@ -185,9 +177,8 @@ func startPersistentOfflineClientFixture(t *testing.T, shell string, shellArgs [ | |||
| 185 | 177 | select { | |
| 186 | 178 | case <-brokerDone: | |
| 187 | 179 | case <-time.After(brokerTestWait): | |
| 188 | - | t.Error("offline broker did not stop") | |
| 180 | + | t.Error("production broker did not stop") | |
| 189 | 181 | } | |
| 190 | - | requireProductionUntouched(t, prodRuntime, prodState) | |
| 191 | 182 | }) | |
| 192 | 183 | losePhysical := func() { | |
| 193 | 184 | physicalMu.Lock() | |
| @@ -261,49 +252,6 @@ func awaitUITerminalMarker(t *testing.T, terminal *uiterm.Terminal, marker strin | |||
| 261 | 252 | } | |
| 262 | 253 | } | |
| 263 | 254 | ||
| 264 | - | func TestParseBrokerClientArgsAndProductionDispatchIsolation(t *testing.T) { | |
| 265 | - | parsed, err := parseArgs([]string{brokerClientCommand, "--offline-root", "/tmp/offline", "--harness", offlineClientUIDriver}) | |
| 266 | - | require.NoError(t, err) | |
| 267 | - | require.Equal(t, kindBrokerClient, parsed.kind) | |
| 268 | - | require.Equal(t, connectivityLocalOnly, mustConnectivityOwner(t, "kindBrokerClient")) | |
| 269 | - | ||
| 270 | - | ordinary := []struct { | |
| 271 | - | args []string | |
| 272 | - | kind cmdKind | |
| 273 | - | }{{nil, kindAttach}, {[]string{"list"}, kindList}, {[]string{"--ui-driver"}, kindUIDriver}, {[]string{"--web-daemon"}, kindWebDaemon}} | |
| 274 | - | for _, test := range ordinary { | |
| 275 | - | command, parseErr := parseArgs(test.args) | |
| 276 | - | require.NoError(t, parseErr) | |
| 277 | - | require.Equal(t, test.kind, command.kind) | |
| 278 | - | require.NotEqual(t, kindBrokerClient, command.kind) | |
| 279 | - | } | |
| 280 | - | ||
| 281 | - | rejections := []struct { | |
| 282 | - | name string | |
| 283 | - | args []string | |
| 284 | - | }{ | |
| 285 | - | {name: "missing root", args: []string{brokerClientCommand}}, | |
| 286 | - | {name: "missing root value", args: []string{brokerClientCommand, "--offline-root"}}, | |
| 287 | - | {name: "duplicate root", args: []string{brokerClientCommand, "--offline-root", "/tmp/a", "--offline-root", "/tmp/b"}}, | |
| 288 | - | {name: "unknown flag", args: []string{brokerClientCommand, "--offline-root", "/tmp/a", "--unknown"}}, | |
| 289 | - | {name: "positional", args: []string{brokerClientCommand, "--offline-root", "/tmp/a", "extra"}}, | |
| 290 | - | {name: "bad harness", args: []string{brokerClientCommand, "--offline-root", "/tmp/a", "--harness", "other"}}, | |
| 291 | - | } | |
| 292 | - | for _, test := range rejections { | |
| 293 | - | t.Run(test.name, func(t *testing.T) { | |
| 294 | - | _, parseErr := parseArgs(test.args) | |
| 295 | - | require.Error(t, parseErr) | |
| 296 | - | }) | |
| 297 | - | } | |
| 298 | - | } | |
| 299 | - | ||
| 300 | - | func mustConnectivityOwner(t *testing.T, kind string) connectivityOwner { | |
| 301 | - | t.Helper() | |
| 302 | - | owner, ok := testConnectivityOwnerFor(kind) | |
| 303 | - | require.True(t, ok) | |
| 304 | - | return owner | |
| 305 | - | } | |
| 306 | - | ||
| 307 | 255 | // TestOfflineEphemeralSessionIsNotPersistedViaBroker proves the daemon-owned | |
| 308 | 256 | // broker fixture persists a named session but never an ephemeral one: the | |
| 309 | 257 | // durable catalogue holds exactly the named record after both lifecycles, so | |
| @@ -19,9 +19,9 @@ import ( | |||||
| 19 | "github.com/coder/websocket" | 19 | "github.com/coder/websocket" | ||
| 20 | "github.com/stretchr/testify/require" | 20 | "github.com/stretchr/testify/require" | ||
| 21 | 21 | ||||
| 22 | - | "github.com/bnema/vev/internal/adapters/brokerconfig" | |||
| 23 | "github.com/bnema/vev/internal/adapters/brokeripc" | 22 | "github.com/bnema/vev/internal/adapters/brokeripc" | ||
| 24 | "github.com/bnema/vev/internal/adapters/clock" | 23 | "github.com/bnema/vev/internal/adapters/clock" | ||
| 24 | + | "github.com/bnema/vev/internal/adapters/daemonidentity" | |||
| 25 | "github.com/bnema/vev/internal/adapters/daemonmux" | 25 | "github.com/bnema/vev/internal/adapters/daemonmux" | ||
| 26 | "github.com/bnema/vev/internal/adapters/ipc" | 26 | "github.com/bnema/vev/internal/adapters/ipc" | ||
| 27 | "github.com/bnema/vev/internal/adapters/pty" | 27 | "github.com/bnema/vev/internal/adapters/pty" | ||
| @@ -30,6 +30,7 @@ import ( | |||||
| 30 | "github.com/bnema/vev/internal/adapters/webterm" | 30 | "github.com/bnema/vev/internal/adapters/webterm" | ||
| 31 | "github.com/bnema/vev/internal/domain" | 31 | "github.com/bnema/vev/internal/domain" | ||
| 32 | "github.com/bnema/vev/internal/persist" | 32 | "github.com/bnema/vev/internal/persist" | ||
| 33 | + | "github.com/bnema/vev/internal/platform" | |||
| 33 | "github.com/bnema/vev/internal/ports" | 34 | "github.com/bnema/vev/internal/ports" | ||
| 34 | "github.com/bnema/vev/internal/protocol" | 35 | "github.com/bnema/vev/internal/protocol" | ||
| 35 | "github.com/bnema/vev/internal/usecase/daemon" | 36 | "github.com/bnema/vev/internal/usecase/daemon" | ||
| @@ -55,13 +56,13 @@ type offlineClientFixture struct { | |||||
| 55 | 56 | ||||
| 56 | func startOfflineClientFixture(t *testing.T) offlineClientFixture { | 57 | func startOfflineClientFixture(t *testing.T) offlineClientFixture { | ||
| 57 | t.Helper() | 58 | t.Helper() | ||
| 58 | - | // The default sandbox shell prints one marker and then parks on read, so a | 59 | + | // The default isolated production fixture shell prints one marker and then parks on read, so a |
| 59 | // client proves it reached a session without depending on shell echo of | 60 | // client proves it reached a session without depending on shell echo of | ||
| 60 | // injected input. | 61 | // injected input. | ||
| 61 | return startOfflineClientFixtureWithShell(t, "/bin/sh", []string{"-c", "sleep 0.1; printf offline-ready; trap : TERM; while :; do read line || exit; done"}) | 62 | return startOfflineClientFixtureWithShell(t, "/bin/sh", []string{"-c", "sleep 0.1; printf offline-ready; trap : TERM; while :; do read line || exit; done"}) | ||
| 62 | } | 63 | } | ||
| 63 | 64 | ||||
| 64 | - | // startOfflineClientFixtureWithShell starts the same private broker sandbox with | 65 | + | // startOfflineClientFixtureWithShell starts the same isolated production broker with |
| 65 | // an explicitly supplied daemon shell, so a browser test can drive real session | 66 | // an explicitly supplied daemon shell, so a browser test can drive real session | ||
| 66 | // content and real session actions (echo and command output) through the | 67 | // content and real session actions (echo and command output) through the | ||
| 67 | // ordinary client path instead of a parked shell. | 68 | // ordinary client path instead of a parked shell. | ||
| @@ -70,38 +71,29 @@ func startOfflineClientFixtureWithShell(t *testing.T, shell string, shellArgs [] | |||||
| 70 | return startPersistentOfflineClientFixture(t, shell, shellArgs, false) | 71 | return startPersistentOfflineClientFixture(t, shell, shellArgs, false) | ||
| 71 | } | 72 | } | ||
| 72 | 73 | ||||
| 73 | - | // startPersistentOfflineClientFixture starts the same private broker sandbox but | 74 | + | // startPersistentOfflineClientFixture starts the same isolated production broker but |
| 74 | - | // additionally binds the daemon to real session persistence under the sandbox | 75 | + | // additionally binds the daemon to real session persistence under the production daemon |
| 75 | // state directory. It exists so a test can prove which lifecycle state the | 76 | // state directory. It exists so a test can prove which lifecycle state the | ||
| 76 | // daemon actually persists: a named session appears in the durable catalogue, | 77 | // daemon actually persists: a named session appears in the durable catalogue, | ||
| 77 | // while an ephemeral one is never written. | 78 | // while an ephemeral one is never written. | ||
| 78 | func startPersistentOfflineClientFixture(t *testing.T, shell string, shellArgs []string, persistEnabled bool) offlineClientFixture { | 79 | func startPersistentOfflineClientFixture(t *testing.T, shell string, shellArgs []string, persistEnabled bool) offlineClientFixture { | ||
| 79 | t.Helper() | 80 | t.Helper() | ||
| 80 | - | root, prodRuntime, prodState := isolateSandboxEnv(t) | 81 | + | layout := emptyProductionBrokerLayout(t, "") |
| 81 | - | require.NoError(t, os.MkdirAll(root, 0o700)) | 82 | + | root := layout.Root |
| 82 | - | route := filepath.Join(root, "local-mux.sock") | 83 | + | prodRuntime, prodState := os.Getenv("XDG_RUNTIME_DIR"), os.Getenv("XDG_STATE_HOME") |
| 83 | - | policy := brokerTestPolicy() | 84 | + | identity, err := daemonidentity.LoadOrCreate(platform.StateDir()) |
| 84 | - | writeSandboxConfig(t, root, map[string]any{ | |||
| 85 | - | "marker": brokerconfig.Marker, "registrations": []any{}, | |||
| 86 | - | "local": map[string]any{ | |||
| 87 | - | "identity": brokerLocalTestIdentity, "displayOrigin": "local", "route": route, | |||
| 88 | - | "policy": map[string]any{"protocolVersion": policy.ProtocolVersion, "catalogSchemaVersion": policy.CatalogSchemaVersion, "environmentPolicy": "client-owned", "transport": policy.Transport, "trust": policy.Trust, "launch": policy.Launch, "isolation": policy.Isolation}, | |||
| 89 | - | }, | |||
| 90 | - | }) | |||
| 91 | - | layout, err := offlineLayout(root) | |||
| 92 | require.NoError(t, err) | 85 | require.NoError(t, err) | ||
| 93 | - | for _, dir := range []string{layout.Root, layout.Runtime, layout.State, layout.Log} { | 86 | + | require.NoError(t, ensureProductionBrokerConfig()) |
| 94 | - | require.NoError(t, safedir.EnsurePrivate(dir)) | 87 | + | route := daemonmux.SocketPath(ipc.SocketDir()) |
| 95 | - | } | 88 | + | policy := localDaemonPolicy() |
| 96 | - | require.NoError(t, layout.VerifyCreated()) | 89 | + | require.NoError(t, safedir.EnsurePrivate(ipc.SocketDir())) |
| 97 | - | ||||
| 98 | rawListener, err := ipc.ListenMux(route) | 90 | rawListener, err := ipc.ListenMux(route) | ||
| 99 | require.NoError(t, err) | 91 | require.NoError(t, err) | ||
| 100 | physicalSeen := make(chan struct{}, 8) | 92 | physicalSeen := make(chan struct{}, 8) | ||
| 101 | var physicalMu sync.Mutex | 93 | var physicalMu sync.Mutex | ||
| 102 | var physicalConnections []io.Closer | 94 | var physicalConnections []io.Closer | ||
| 103 | aggregate := daemonmux.NewAggregateListener() | 95 | aggregate := daemonmux.NewAggregateListener() | ||
| 104 | - | binding, err := daemonmux.NewServerBinding(brokerLocalTestIdentity, ports.BrokerDaemonIncarnation{1}, policy) | 96 | + | binding, err := daemonmux.NewServerBinding(identity, ports.BrokerDaemonIncarnation{1}, policy) |
| 105 | require.NoError(t, err) | 97 | require.NoError(t, err) | ||
| 106 | mux, err := daemonmux.NewServerSupervisor(aggregate, binding, daemonmux.DefaultMuxCeilings(), 8) | 98 | mux, err := daemonmux.NewServerSupervisor(aggregate, binding, daemonmux.DefaultMuxCeilings(), 8) | ||
| 107 | require.NoError(t, err) | 99 | require.NoError(t, err) | ||
| @@ -127,7 +119,7 @@ func startPersistentOfflineClientFixture(t *testing.T, shell string, shellArgs [ | |||||
| 127 | daemonOpts := []daemon.Option{daemon.WithShell(shell, shellArgs)} | 119 | daemonOpts := []daemon.Option{daemon.WithShell(shell, shellArgs)} | ||
| 128 | var fixtureCatalogue *persist.Persister | 120 | var fixtureCatalogue *persist.Persister | ||
| 129 | if persistEnabled { | 121 | if persistEnabled { | ||
| 130 | - | stateDir := filepath.Join(root, "state") | 122 | + | stateDir := platform.StateDir() |
| 131 | opened, err := persist.OpenOrCreate(stateDir) | 123 | opened, err := persist.OpenOrCreate(stateDir) | ||
| 132 | require.NoError(t, err) | 124 | require.NoError(t, err) | ||
| 133 | repository := snapshot.NewRepository(filepath.Join(stateDir, "snapshots")) | 125 | repository := snapshot.NewRepository(filepath.Join(stateDir, "snapshots")) | ||
| @@ -146,16 +138,16 @@ func startPersistentOfflineClientFixture(t *testing.T, shell string, shellArgs [ | |||||
| 146 | // a manual clock rooted at Unix zero would correctly make the observation | 138 | // a manual clock rooted at Unix zero would correctly make the observation | ||
| 147 | // stale to the real client clock before initial navigation resolves. | 139 | // stale to the real client clock before initial navigation resolves. | ||
| 148 | deps, ready := testBrokerServeDeps(clock.New()) | 140 | deps, ready := testBrokerServeDeps(clock.New()) | ||
| 149 | - | brokerDone := runSandbox(ctx, brokerServeOptions{offlineRoot: root, idleGrace: time.Hour}, deps) | 141 | + | brokerDone := runSandbox(ctx, brokerServeOptions{production: true, idleGrace: time.Hour}, deps) |
| 150 | var socket string | 142 | var socket string | ||
| 151 | select { | 143 | select { | ||
| 152 | case socket = <-ready: | 144 | case socket = <-ready: | ||
| 153 | case serveErr := <-brokerDone: | 145 | case serveErr := <-brokerDone: | ||
| 154 | - | t.Fatalf("offline broker exited before readiness: %v", serveErr) | 146 | + | t.Fatalf("production broker exited before readiness: %v", serveErr) |
| 155 | case <-time.After(brokerTestWait): | 147 | case <-time.After(brokerTestWait): | ||
| 156 | - | t.Fatal("offline broker did not become ready") | 148 | + | t.Fatal("production broker did not become ready") |
| 157 | } | 149 | } | ||
| 158 | - | // The sandbox broker becomes socket-ready before its first local daemon | 150 | + | // The production broker becomes socket-ready before its first local daemon |
| 159 | // observation is committed. Product clients start from a committed local | 151 | // observation is committed. Product clients start from a committed local | ||
| 160 | // authority; wait for the fixture to provide the same contract so one-shot | 152 | // authority; wait for the fixture to provide the same contract so one-shot | ||
| 161 | // initial navigation is not consumed against an artificial empty catalogue. | 153 | // initial navigation is not consumed against an artificial empty catalogue. | ||
| @@ -174,7 +166,7 @@ func startPersistentOfflineClientFixture(t *testing.T, shell string, shellArgs [ | |||||
| 174 | default: | 166 | default: | ||
| 175 | } | 167 | } | ||
| 176 | return false | 168 | return false | ||
| 177 | - | }, brokerTestWait, 5*time.Millisecond, "offline broker never committed its local daemon authority") | 169 | + | }, brokerTestWait, 5*time.Millisecond, "production broker never committed its local daemon authority") |
| 178 | subscription.Close() | 170 | subscription.Close() | ||
| 179 | require.NoError(t, probe.Close()) | 171 | require.NoError(t, probe.Close()) | ||
| 180 | t.Cleanup(func() { | 172 | t.Cleanup(func() { | ||
| @@ -185,9 +177,8 @@ func startPersistentOfflineClientFixture(t *testing.T, shell string, shellArgs [ | |||||
| 185 | select { | 177 | select { | ||
| 186 | case <-brokerDone: | 178 | case <-brokerDone: | ||
| 187 | case <-time.After(brokerTestWait): | 179 | case <-time.After(brokerTestWait): | ||
| 188 | - | t.Error("offline broker did not stop") | 180 | + | t.Error("production broker did not stop") |
| 189 | } | 181 | } | ||
| 190 | - | requireProductionUntouched(t, prodRuntime, prodState) | |||
| 191 | }) | 182 | }) | ||
| 192 | losePhysical := func() { | 183 | losePhysical := func() { | ||
| 193 | physicalMu.Lock() | 184 | physicalMu.Lock() | ||
| @@ -261,49 +252,6 @@ func awaitUITerminalMarker(t *testing.T, terminal *uiterm.Terminal, marker strin | |||||
| 261 | } | 252 | } | ||
| 262 | } | 253 | } | ||
| 263 | 254 | ||||
| 264 | - | func TestParseBrokerClientArgsAndProductionDispatchIsolation(t *testing.T) { | |||
| 265 | - | parsed, err := parseArgs([]string{brokerClientCommand, "--offline-root", "/tmp/offline", "--harness", offlineClientUIDriver}) | |||
| 266 | - | require.NoError(t, err) | |||
| 267 | - | require.Equal(t, kindBrokerClient, parsed.kind) | |||
| 268 | - | require.Equal(t, connectivityLocalOnly, mustConnectivityOwner(t, "kindBrokerClient")) | |||
| 269 | - | ||||
| 270 | - | ordinary := []struct { | |||
| 271 | - | args []string | |||
| 272 | - | kind cmdKind | |||
| 273 | - | }{{nil, kindAttach}, {[]string{"list"}, kindList}, {[]string{"--ui-driver"}, kindUIDriver}, {[]string{"--web-daemon"}, kindWebDaemon}} | |||
| 274 | - | for _, test := range ordinary { | |||
| 275 | - | command, parseErr := parseArgs(test.args) | |||
| 276 | - | require.NoError(t, parseErr) | |||
| 277 | - | require.Equal(t, test.kind, command.kind) | |||
| 278 | - | require.NotEqual(t, kindBrokerClient, command.kind) | |||
| 279 | - | } | |||
| 280 | - | ||||
| 281 | - | rejections := []struct { | |||
| 282 | - | name string | |||
| 283 | - | args []string | |||
| 284 | - | }{ | |||
| 285 | - | {name: "missing root", args: []string{brokerClientCommand}}, | |||
| 286 | - | {name: "missing root value", args: []string{brokerClientCommand, "--offline-root"}}, | |||
| 287 | - | {name: "duplicate root", args: []string{brokerClientCommand, "--offline-root", "/tmp/a", "--offline-root", "/tmp/b"}}, | |||
| 288 | - | {name: "unknown flag", args: []string{brokerClientCommand, "--offline-root", "/tmp/a", "--unknown"}}, | |||
| 289 | - | {name: "positional", args: []string{brokerClientCommand, "--offline-root", "/tmp/a", "extra"}}, | |||
| 290 | - | {name: "bad harness", args: []string{brokerClientCommand, "--offline-root", "/tmp/a", "--harness", "other"}}, | |||
| 291 | - | } | |||
| 292 | - | for _, test := range rejections { | |||
| 293 | - | t.Run(test.name, func(t *testing.T) { | |||
| 294 | - | _, parseErr := parseArgs(test.args) | |||
| 295 | - | require.Error(t, parseErr) | |||
| 296 | - | }) | |||
| 297 | - | } | |||
| 298 | - | } | |||
| 299 | - | ||||
| 300 | - | func mustConnectivityOwner(t *testing.T, kind string) connectivityOwner { | |||
| 301 | - | t.Helper() | |||
| 302 | - | owner, ok := testConnectivityOwnerFor(kind) | |||
| 303 | - | require.True(t, ok) | |||
| 304 | - | return owner | |||
| 305 | - | } | |||
| 306 | - | ||||
| 307 | // TestOfflineEphemeralSessionIsNotPersistedViaBroker proves the daemon-owned | 255 | // TestOfflineEphemeralSessionIsNotPersistedViaBroker proves the daemon-owned | ||
| 308 | // broker fixture persists a named session but never an ephemeral one: the | 256 | // broker fixture persists a named session but never an ephemeral one: the | ||
| 309 | // durable catalogue holds exactly the named record after both lifecycles, so | 257 | // durable catalogue holds exactly the named record after both lifecycles, so | ||
M internal/app/ui_driver_client_test.go +6 -5
| @@ -7,6 +7,7 @@ import ( | |||
| 7 | 7 | "encoding/json" | |
| 8 | 8 | "errors" | |
| 9 | 9 | "io" | |
| 10 | + | "path/filepath" | |
| 10 | 11 | "sync" | |
| 11 | 12 | "testing" | |
| 12 | 13 | "time" | |
| @@ -337,12 +338,12 @@ func TestUIDriverClientKeepsSharedDaemonOnEOF(t *testing.T) { | |||
| 337 | 338 | second.EOF(t) | |
| 338 | 339 | } | |
| 339 | 340 | ||
| 340 | - | // TestUIDriverFixtureKeepsProductionUntouched pins the fixture isolation: the | |
| 341 | - | // driver fixture uses its own private roots and never creates a production | |
| 342 | - | // runtime or state entry. | |
| 343 | - | func TestUIDriverFixtureKeepsProductionUntouched(t *testing.T) { | |
| 341 | + | // TestUIDriverFixtureUsesIsolatedProductionBroker pins the fixture isolation: | |
| 342 | + | // the driver reaches the broker within its private production XDG roots. | |
| 343 | + | func TestUIDriverFixtureUsesIsolatedProductionBroker(t *testing.T) { | |
| 344 | 344 | fixture := startUIDriverBrokerFixture(t) | |
| 345 | - | requireProductionUntouched(t, fixture.prodRuntime, fixture.prodState) | |
| 345 | + | require.DirExists(t, filepath.Join(fixture.prodRuntime, "vev", "broker")) | |
| 346 | + | require.FileExists(t, filepath.Join(fixture.prodState, "vev", "broker", "state", "state.json")) | |
| 346 | 347 | connector := fixture.Connector() | |
| 347 | 348 | require.NotNil(t, connector) | |
| 348 | 349 | service, err := connector.Connect(t.Context()) | |
| @@ -7,6 +7,7 @@ import ( | |||||
| 7 | "encoding/json" | 7 | "encoding/json" | ||
| 8 | "errors" | 8 | "errors" | ||
| 9 | "io" | 9 | "io" | ||
| 10 | + | "path/filepath" | |||
| 10 | "sync" | 11 | "sync" | ||
| 11 | "testing" | 12 | "testing" | ||
| 12 | "time" | 13 | "time" | ||
| @@ -337,12 +338,12 @@ func TestUIDriverClientKeepsSharedDaemonOnEOF(t *testing.T) { | |||||
| 337 | second.EOF(t) | 338 | second.EOF(t) | ||
| 338 | } | 339 | } | ||
| 339 | 340 | ||||
| 340 | - | // TestUIDriverFixtureKeepsProductionUntouched pins the fixture isolation: the | 341 | + | // TestUIDriverFixtureUsesIsolatedProductionBroker pins the fixture isolation: |
| 341 | - | // driver fixture uses its own private roots and never creates a production | 342 | + | // the driver reaches the broker within its private production XDG roots. |
| 342 | - | // runtime or state entry. | 343 | + | func TestUIDriverFixtureUsesIsolatedProductionBroker(t *testing.T) { |
| 343 | - | func TestUIDriverFixtureKeepsProductionUntouched(t *testing.T) { | |||
| 344 | fixture := startUIDriverBrokerFixture(t) | 344 | fixture := startUIDriverBrokerFixture(t) | ||
| 345 | - | requireProductionUntouched(t, fixture.prodRuntime, fixture.prodState) | 345 | + | require.DirExists(t, filepath.Join(fixture.prodRuntime, "vev", "broker")) |
| 346 | + | require.FileExists(t, filepath.Join(fixture.prodState, "vev", "broker", "state", "state.json")) | |||
| 346 | connector := fixture.Connector() | 347 | connector := fixture.Connector() | ||
| 347 | require.NotNil(t, connector) | 348 | require.NotNil(t, connector) | ||
| 348 | service, err := connector.Connect(t.Context()) | 349 | service, err := connector.Connect(t.Context()) | ||