1package brokerconfig
2
3import (
4 "context"
5 "errors"
6 "path/filepath"
7 "testing"
8
9 "github.com/stretchr/testify/require"
10
11 "github.com/bnema/vev/internal/ports"
12)
13
14const (
15 localTestIdentity = "offline-local-daemon"
16 localTestSocket = "/tmp/vev-brokerconfig-test/local-mux.sock"
17)
18
19// localDocument returns the raw local object provisioned by the fixture. The
20// caller may override or delete fields through the returned map.
21func localTestDocument() map[string]any {
22 policy := testPolicy()
23 return map[string]any{
24 "identity": localTestIdentity,
25 "route": localTestSocket,
26 "policy": map[string]any{
27 "protocolVersion": policy.ProtocolVersion,
28 "catalogSchemaVersion": policy.CatalogSchemaVersion,
29 "environmentPolicy": "client-owned",
30 "transport": policy.Transport,
31 "trust": policy.Trust,
32 "launch": policy.Launch,
33 "isolation": policy.Isolation,
34 },
35 }
36}
37
38// documentWithLocal returns a valid registration document carrying one local
39// binding, with any field overrides applied.
40func documentWithLocal(t *testing.T, local map[string]any) map[string]any {
41 t.Helper()
42 document := registrationDocument(nil)
43 document["local"] = local
44 return document
45}
46
47// loadDocument writes one document into a fresh offline root and loads it.
48func loadDocument(t *testing.T, document any, reserved []string) (*Config, error) {
49 t.Helper()
50 root := t.TempDir()
51 writeConfig(t, root, document, 0o600)
52 layout, err := ResolveLayout(root, reserved)
53 require.NoError(t, err)
54 return Load(layout)
55}
56
57// localRequest builds one local open-stream request under the fixture policy.
58func localRequest() ports.BrokerOpenStreamRequest {
59 return ports.BrokerOpenStreamRequest{
60 Purpose: ports.BrokerStreamControl,
61 Local: true,
62 Policy: testPolicy(),
63 StartMode: ports.BrokerDaemonStartIfNeeded,
64 }
65}
66
67// TestLoadLocalBinding pins the provisioned local binding: the identity,
68// policy, sanitized display origin, and local Unix route are configuration
69// authority, and the local route is resolvable by its opaque pool address.
70func TestLoadLocalBinding(t *testing.T) {
71 config, err := loadDocument(t, documentWithLocal(t, localTestDocument()), nil)
72 require.NoError(t, err)
73
74 binding, ok := config.LocalBinding()
75 require.True(t, ok)
76 require.Equal(t, ports.BrokerDaemonIdentity(localTestIdentity), binding.Identity)
77 require.Equal(t, testPolicy(), binding.Policy)
78 require.Equal(t, LocalDisplayOrigin, binding.DisplayOrigin)
79 require.True(t, binding.Route.IsLocal())
80 require.Equal(t, localTestSocket, binding.Route.Path())
81
82 resolved, err := config.Resolver().ResolveDialTarget(context.Background(), localRequest())
83 require.NoError(t, err)
84 require.Equal(t, binding.Identity, resolved.ExpectedIdentity.Identity)
85 require.Equal(t, binding.Policy, resolved.Policy)
86 require.Equal(t, binding.Route.Address(), resolved.Address)
87
88 route, ok := config.RouteByAddress(resolved.Address)
89 require.True(t, ok)
90 require.Equal(t, localTestSocket, route.Path())
91}
92
93// TestLoadLocalBindingDefaultsDisplayOrigin pins the broker-owned default and
94// the sanitizer: an absent origin becomes "local", and a hostile origin is
95// sanitized rather than published raw.
96func TestLoadLocalBindingDefaultsDisplayOrigin(t *testing.T) {
97 tests := []struct {
98 name string
99 origin any
100 want string
101 }{
102 {name: "absent defaults to the broker-owned origin", origin: nil, want: LocalDisplayOrigin},
103 {name: "explicit origin is kept", origin: "this-host", want: "this-host"},
104 {name: "bidi and control runes are stripped", origin: "lo\u202ecal", want: "local"},
105 {name: "wholly undisplayable defaults", origin: "\u202e\u2028", want: LocalDisplayOrigin},
106 }
107 for _, tt := range tests {
108 t.Run(tt.name, func(t *testing.T) {
109 local := localTestDocument()
110 if tt.origin == nil {
111 delete(local, "displayOrigin")
112 } else {
113 local["displayOrigin"] = tt.origin
114 }
115 config, err := loadDocument(t, documentWithLocal(t, local), nil)
116 require.NoError(t, err)
117 binding, ok := config.LocalBinding()
118 require.True(t, ok)
119 require.Equal(t, tt.want, binding.DisplayOrigin)
120 })
121 }
122}
123
124// TestLoadRejectsInvalidLocalBinding pins every local-binding refusal at the
125// load boundary. A binding the broker could not fence is never provisioned.
126func TestLoadRejectsInvalidLocalBinding(t *testing.T) {
127 tests := []struct {
128 name string
129 mutate func(t *testing.T, local map[string]any)
130 wantErr string
131 }{
132 {
133 name: "missing identity",
134 mutate: func(_ *testing.T, local map[string]any) { delete(local, "identity") },
135 wantErr: "identity",
136 },
137 {
138 name: "missing policy",
139 mutate: func(_ *testing.T, local map[string]any) { delete(local, "policy") },
140 wantErr: "policy",
141 },
142 {
143 name: "missing route",
144 mutate: func(_ *testing.T, local map[string]any) { delete(local, "route") },
145 wantErr: "route",
146 },
147 {
148 name: "ssh route is refused",
149 mutate: func(_ *testing.T, local map[string]any) {
150 local["route"] = map[string]any{"kind": "ssh-stdio", "target": "user@host", "argv": []string{"vev"}}
151 },
152 wantErr: "Unix daemonmux",
153 },
154 {
155 name: "relative route is refused",
156 mutate: func(_ *testing.T, local map[string]any) { local["route"] = "relative/mux.sock" },
157 wantErr: "not absolute",
158 },
159 {
160 name: "unknown field is refused",
161 mutate: func(_ *testing.T, local map[string]any) { local["extra"] = 1 },
162 wantErr: "unknown field",
163 },
164 }
165 for _, tt := range tests {
166 t.Run(tt.name, func(t *testing.T) {
167 local := localTestDocument()
168 tt.mutate(t, local)
169 _, err := loadDocument(t, documentWithLocal(t, local), nil)
170 require.Error(t, err)
171 require.Contains(t, err.Error(), tt.wantErr)
172 })
173 }
174}
175
176// TestLoadLocalRouteFencesProduction proves the local route is fenced against
177// the reserved production runtime and state directories exactly like a remote
178// Unix route: an isolated sandbox can never reach a production endpoint
179// through its own local binding.
180func TestLoadLocalRouteFencesProduction(t *testing.T) {
181 reservedDir := t.TempDir()
182 local := localTestDocument()
183 local["route"] = filepath.Join(reservedDir, "mux.sock")
184 _, err := loadDocument(t, documentWithLocal(t, local), []string{reservedDir})
185 require.Error(t, err)
186 require.Contains(t, err.Error(), "overlaps production")
187}
188
189// TestLoadLocalRouteAliasesRegistrationRoute proves the local binding may
190// reuse the exact Unix mux route a registration already provisions: the pool
191// address is identical only when the route is identical.
192func TestLoadLocalRouteAliasesRegistrationRoute(t *testing.T) {
193 local := localTestDocument()
194 local["route"] = "/tmp/vev-brokerconfig-test/mux.sock" // the fixture registration route
195 config, err := loadDocument(t, documentWithLocal(t, local), nil)
196 require.NoError(t, err)
197 binding, ok := config.LocalBinding()
198 require.True(t, ok)
199 require.Equal(t, "/tmp/vev-brokerconfig-test/mux.sock", binding.Route.Path())
200}
201
202// TestLoadRefusesLocalIdentityPoolConflict proves the pool's (identity,
203// policy) keying is honored across the local and remote provisionings: a local
204// binding that shares a registration's exact identity and policy but names a
205// different route is refused rather than silently dialed for one and ignored
206// for the other.
207func TestLoadRefusesLocalIdentityPoolConflict(t *testing.T) {
208 local := localTestDocument()
209 local["identity"] = testRegistrationIdentity()
210 local["route"] = "/tmp/vev-brokerconfig-test/other-mux.sock"
211 _, err := loadDocument(t, documentWithLocal(t, local), nil)
212 require.Error(t, err)
213 require.Contains(t, err.Error(), "already provisioned on a different route")
214}
215
216// testRegistrationIdentity reads the identity the fixture registration
217// provisions, so the collision test is anchored to the real fixture.
218func testRegistrationIdentity() string { return "offline-daemon" }
219
220// TestResolveLocalSuccessAndRefusals pins the local resolution contract:
221// success returns the configured authority and address, a configuration with
222// no local binding refuses as unavailable, a conflicting policy is refused as
223// such, and a cancelled context is reported as cancellation. Every refusal is
224// typed as a ports.BrokerError so the pool classifies it without text matching.
225func TestResolveLocalSuccessAndRefusals(t *testing.T) {
226 config, err := loadDocument(t, documentWithLocal(t, localTestDocument()), nil)
227 require.NoError(t, err)
228 binding, ok := config.LocalBinding()
229 require.True(t, ok)
230 resolver := config.Resolver()
231
232 noLocalConfig, err := loadDocument(t, registrationDocument(nil), nil)
233 require.NoError(t, err)
234
235 tests := []struct {
236 name string
237 resolver *Resolver
238 request ports.BrokerOpenStreamRequest
239 wantCode ports.BrokerErrorCode
240 wantErr error
241 }{
242 {
243 name: "success returns configured authority",
244 resolver: resolver,
245 request: localRequest(),
246 },
247 {
248 name: "no local binding is unavailable",
249 resolver: noLocalConfig.Resolver(),
250 request: localRequest(),
251 wantCode: ports.BrokerErrorUnavailable,
252 wantErr: errNoLocalRoute,
253 },
254 {
255 name: "conflicting policy is refused",
256 resolver: resolver,
257 request: func() ports.BrokerOpenStreamRequest {
258 request := localRequest()
259 request.Policy.Transport = "other-transport"
260 return request
261 }(),
262 wantCode: ports.BrokerErrorConflictingPolicy,
263 wantErr: errLocalPolicyConflict,
264 },
265 {
266 name: "cancelled context is cancellation",
267 resolver: resolver,
268 request: localRequest(),
269 wantErr: context.Canceled,
270 },
271 }
272 for _, tt := range tests {
273 t.Run(tt.name, func(t *testing.T) {
274 ctx := context.Background()
275 if errors.Is(tt.wantErr, context.Canceled) {
276 cancelled, cancel := context.WithCancel(ctx)
277 cancel()
278 ctx = cancelled
279 }
280 resolved, err := tt.resolver.ResolveDialTarget(ctx, tt.request)
281 if tt.wantErr == nil {
282 require.NoError(t, err)
283 require.Equal(t, binding.Identity, resolved.ExpectedIdentity.Identity)
284 require.Equal(t, binding.Policy, resolved.Policy)
285 require.Equal(t, binding.Route.Address(), resolved.Address)
286 return
287 }
288 require.Error(t, err)
289 if tt.wantCode != 0 {
290 var typed ports.BrokerError
291 require.ErrorAs(t, err, &typed)
292 require.Equal(t, tt.wantCode, typed.Code)
293 }
294 require.ErrorIs(t, err, tt.wantErr)
295 })
296 }
297}
298
299// TestLocalMuxRouteExcludesLocalBinding proves Config.LocalMuxRoute serves the
300// remote-side mux helper's route only. The broker-owned local binding's own
301// carriage is deliberately excluded: no helper ever bridges it, so a
302// configuration provisioning only the local object has no helper route, and one
303// provisioning both returns the registration's route, never the local binding's.
304func TestLocalMuxRouteExcludesLocalBinding(t *testing.T) {
305 t.Run("local-only configuration provisions no helper route", func(t *testing.T) {
306 document := map[string]any{
307 "marker": Marker,
308 "registrations": []any{},
309 "local": localTestDocument(),
310 }
311 config, err := loadDocument(t, document, nil)
312 require.NoError(t, err)
313 _, ok := config.LocalBinding()
314 require.True(t, ok, "the fixture provisions a broker-owned local binding")
315 _, err = config.LocalMuxRoute()
316 require.Error(t, err)
317 require.Contains(t, err.Error(), "no Unix daemon-mux route is provisioned")
318 })
319
320 t.Run("registration route wins over the local binding route", func(t *testing.T) {
321 config, err := loadDocument(t, documentWithLocal(t, localTestDocument()), nil)
322 require.NoError(t, err)
323 binding, ok := config.LocalBinding()
324 require.True(t, ok)
325
326 route, err := config.LocalMuxRoute()
327 require.NoError(t, err)
328 require.True(t, route.IsLocal())
329 require.Equal(t, "/tmp/vev-brokerconfig-test/mux.sock", route.Path(), "the helper bridges the registration route")
330 require.NotEqual(t, binding.Route.Path(), route.Path(), "the local binding route is not the helper route")
331 require.NotEqual(t, binding.Route.Address(), route.Address(), "the two routes carry distinct pool addresses")
332 })
333}