@jskit-ai/workspaces-web 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.descriptor.mjs +3 -3
- package/package.json +11 -7
- package/src/client/account-settings/useAccountSettingsInvitesSectionRuntime.js +2 -0
- package/src/client/components/WorkspaceMembersClientElement.vue +16 -0
- package/src/client/components/WorkspaceProfileClientElement.vue +3 -7
- package/src/client/components/WorkspaceSettingsFieldsClientElement.vue +3 -7
- package/src/client/components/WorkspacesClientElement.vue +6 -0
- package/src/client/providers/WorkspacesWebClientProvider.js +19 -2
- package/src/client/runtime/bootstrapPlacementRuntime.js +191 -136
- package/src/client/support/workspaceScopeSupport.js +4 -11
- package/test/bootstrapPlacementRuntime.test.js +340 -440
- package/test/profileSurfaceMenuLinks.test.js +34 -0
- package/test/workspaceScopeSupport.test.js +28 -39
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN,
|
|
7
7
|
WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND,
|
|
8
8
|
WORKSPACE_BOOTSTRAP_STATUS_RESOLVED,
|
|
9
|
+
WORKSPACE_BOOTSTRAP_STATUS_UNAUTHENTICATED,
|
|
9
10
|
createBootstrapPlacementRuntime
|
|
10
11
|
} from "../src/client/runtime/bootstrapPlacementRuntime.js";
|
|
11
12
|
|
|
@@ -171,6 +172,17 @@ function createSocketStub() {
|
|
|
171
172
|
};
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
function createBootstrapRuntimeStub() {
|
|
176
|
+
const calls = [];
|
|
177
|
+
return {
|
|
178
|
+
async refresh(reason) {
|
|
179
|
+
calls.push(String(reason || ""));
|
|
180
|
+
return null;
|
|
181
|
+
},
|
|
182
|
+
calls
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
174
186
|
function createAppStub(records = {}) {
|
|
175
187
|
const registry = new Map();
|
|
176
188
|
for (const key of Reflect.ownKeys(records)) {
|
|
@@ -231,61 +243,84 @@ function createVueAppWithThemeController(themeController) {
|
|
|
231
243
|
};
|
|
232
244
|
}
|
|
233
245
|
|
|
234
|
-
|
|
246
|
+
function createBootstrapRequest(path = "/w/acme/dashboard", workspaceSlug = "") {
|
|
247
|
+
const normalizedPath = resolvePathFromFullPath(path);
|
|
248
|
+
const normalizedWorkspaceSlug = String(workspaceSlug || "").trim().toLowerCase();
|
|
249
|
+
return Object.freeze({
|
|
250
|
+
path: "/api/bootstrap",
|
|
251
|
+
query: Object.freeze(normalizedWorkspaceSlug ? { workspaceSlug: normalizedWorkspaceSlug } : {}),
|
|
252
|
+
meta: Object.freeze({
|
|
253
|
+
path: normalizedPath,
|
|
254
|
+
workspaceSlug: normalizedWorkspaceSlug
|
|
255
|
+
})
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function createErrorWithStatus(status, message = "") {
|
|
260
|
+
const error = new Error(message || `HTTP ${status}`);
|
|
261
|
+
error.status = status;
|
|
262
|
+
return error;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
test("bootstrap placement runtime contributes workspace slug to shared bootstrap request and writes payload into placement context", async () => {
|
|
235
266
|
const placementRuntime = createPlacementRuntimeStub();
|
|
236
267
|
const router = createRouterStub("/w/acme/dashboard");
|
|
237
|
-
const fetchCalls = [];
|
|
238
268
|
const runtime = createBootstrapPlacementRuntime({
|
|
239
269
|
app: createAppStub({
|
|
240
270
|
["runtime.web-placement.client"]: placementRuntime,
|
|
271
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
241
272
|
["jskit.client.router"]: router
|
|
242
|
-
})
|
|
243
|
-
fetchBootstrap: async (workspaceSlug) => {
|
|
244
|
-
fetchCalls.push(workspaceSlug);
|
|
245
|
-
return {
|
|
246
|
-
session: {
|
|
247
|
-
authenticated: true,
|
|
248
|
-
userId: "7"
|
|
249
|
-
},
|
|
250
|
-
profile: {
|
|
251
|
-
displayName: "Ada Lovelace",
|
|
252
|
-
email: "ADA@EXAMPLE.COM",
|
|
253
|
-
avatar: {
|
|
254
|
-
effectiveUrl: "https://cdn.example.com/ada.png"
|
|
255
|
-
}
|
|
256
|
-
},
|
|
257
|
-
app: {
|
|
258
|
-
features: {
|
|
259
|
-
workspaceInvites: true
|
|
260
|
-
}
|
|
261
|
-
},
|
|
262
|
-
pendingInvites: [
|
|
263
|
-
{ id: "1", workspaceId: "1", token: "a" },
|
|
264
|
-
{ id: "2", workspaceId: "2", token: "b" }
|
|
265
|
-
],
|
|
266
|
-
workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
|
|
267
|
-
permissions: ["workspace.settings.view"]
|
|
268
|
-
};
|
|
269
|
-
}
|
|
273
|
+
})
|
|
270
274
|
});
|
|
271
275
|
|
|
272
276
|
await runtime.initialize();
|
|
273
|
-
const
|
|
277
|
+
const request = runtime.resolveBootstrapRequest();
|
|
278
|
+
assert.deepEqual(request, {
|
|
279
|
+
query: {
|
|
280
|
+
workspaceSlug: "acme"
|
|
281
|
+
},
|
|
282
|
+
meta: {
|
|
283
|
+
path: "/w/acme/dashboard",
|
|
284
|
+
workspaceSlug: "acme"
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
await runtime.applyBootstrapPayload({
|
|
289
|
+
request,
|
|
290
|
+
payload: {
|
|
291
|
+
session: {
|
|
292
|
+
authenticated: true,
|
|
293
|
+
userId: "7"
|
|
294
|
+
},
|
|
295
|
+
profile: {
|
|
296
|
+
displayName: "Ada Lovelace",
|
|
297
|
+
email: "ADA@EXAMPLE.COM",
|
|
298
|
+
avatar: {
|
|
299
|
+
effectiveUrl: "https://cdn.example.com/ada.png"
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
app: {
|
|
303
|
+
features: {
|
|
304
|
+
workspaceInvites: true
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
pendingInvites: [
|
|
308
|
+
{ id: "1", workspaceId: "1", token: "a" },
|
|
309
|
+
{ id: "2", workspaceId: "2", token: "b" }
|
|
310
|
+
],
|
|
311
|
+
workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
|
|
312
|
+
permissions: ["workspace.settings.view"]
|
|
313
|
+
},
|
|
314
|
+
source: "test.bootstrap"
|
|
315
|
+
});
|
|
274
316
|
|
|
275
|
-
|
|
317
|
+
const context = placementRuntime.getContext();
|
|
276
318
|
assert.equal(context.workspace?.slug, "acme");
|
|
277
319
|
assert.equal(Array.isArray(context.workspaces), true);
|
|
278
320
|
assert.equal(context.workspaces.length, 1);
|
|
279
321
|
assert.deepEqual(context.permissions, ["workspace.settings.view"]);
|
|
280
322
|
assert.equal(runtime.getWorkspaceBootstrapStatus("acme"), WORKSPACE_BOOTSTRAP_STATUS_RESOLVED);
|
|
281
323
|
assert.equal(context.workspaceBootstrapStatuses?.acme, WORKSPACE_BOOTSTRAP_STATUS_RESOLVED);
|
|
282
|
-
assert.deepEqual(context.user, {
|
|
283
|
-
id: "7",
|
|
284
|
-
displayName: "Ada Lovelace",
|
|
285
|
-
name: "Ada Lovelace",
|
|
286
|
-
email: "ada@example.com",
|
|
287
|
-
avatarUrl: "https://cdn.example.com/ada.png"
|
|
288
|
-
});
|
|
289
324
|
assert.equal(context.workspaceInvitesEnabled, true);
|
|
290
325
|
assert.equal(context.pendingInvitesCount, 2);
|
|
291
326
|
});
|
|
@@ -294,35 +329,39 @@ test("bootstrap placement runtime resolves workspace slug from pathname when sur
|
|
|
294
329
|
const placementRuntime = createPlacementRuntimeStub();
|
|
295
330
|
placementRuntime.setContext({}, { replace: true, source: "test.clear" });
|
|
296
331
|
const router = createRouterStub("/w/acme/admin");
|
|
297
|
-
const fetchCalls = [];
|
|
298
332
|
const runtime = createBootstrapPlacementRuntime({
|
|
299
333
|
app: createAppStub({
|
|
300
334
|
["runtime.web-placement.client"]: placementRuntime,
|
|
335
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
301
336
|
["jskit.client.router"]: router
|
|
302
|
-
})
|
|
303
|
-
fetchBootstrap: async (workspaceSlug) => {
|
|
304
|
-
fetchCalls.push(workspaceSlug);
|
|
305
|
-
return {
|
|
306
|
-
session: {
|
|
307
|
-
authenticated: true,
|
|
308
|
-
userId: "1"
|
|
309
|
-
},
|
|
310
|
-
profile: {
|
|
311
|
-
displayName: "User",
|
|
312
|
-
email: "user@example.com",
|
|
313
|
-
avatar: {
|
|
314
|
-
effectiveUrl: ""
|
|
315
|
-
}
|
|
316
|
-
},
|
|
317
|
-
workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
|
|
318
|
-
permissions: ["workspace.settings.view"]
|
|
319
|
-
};
|
|
320
|
-
}
|
|
337
|
+
})
|
|
321
338
|
});
|
|
322
339
|
|
|
323
340
|
await runtime.initialize();
|
|
341
|
+
const request = runtime.resolveBootstrapRequest();
|
|
342
|
+
assert.deepEqual(request.query, {
|
|
343
|
+
workspaceSlug: "acme"
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
await runtime.applyBootstrapPayload({
|
|
347
|
+
request,
|
|
348
|
+
payload: {
|
|
349
|
+
session: {
|
|
350
|
+
authenticated: true,
|
|
351
|
+
userId: "1"
|
|
352
|
+
},
|
|
353
|
+
profile: {
|
|
354
|
+
displayName: "User",
|
|
355
|
+
email: "user@example.com",
|
|
356
|
+
avatar: {
|
|
357
|
+
effectiveUrl: ""
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
|
|
361
|
+
permissions: ["workspace.settings.view"]
|
|
362
|
+
}
|
|
363
|
+
});
|
|
324
364
|
|
|
325
|
-
assert.deepEqual(fetchCalls, ["acme"]);
|
|
326
365
|
assert.deepEqual(placementRuntime.getContext().permissions, ["workspace.settings.view"]);
|
|
327
366
|
});
|
|
328
367
|
|
|
@@ -342,28 +381,31 @@ test("bootstrap placement runtime does not mutate placement auth context", async
|
|
|
342
381
|
const runtime = createBootstrapPlacementRuntime({
|
|
343
382
|
app: createAppStub({
|
|
344
383
|
["runtime.web-placement.client"]: placementRuntime,
|
|
384
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
345
385
|
["jskit.client.router"]: router
|
|
346
|
-
})
|
|
347
|
-
fetchBootstrap: async () => {
|
|
348
|
-
return {
|
|
349
|
-
session: {
|
|
350
|
-
authenticated: true,
|
|
351
|
-
userId: "9"
|
|
352
|
-
},
|
|
353
|
-
profile: {
|
|
354
|
-
displayName: "User",
|
|
355
|
-
email: "user@example.com",
|
|
356
|
-
avatar: {
|
|
357
|
-
effectiveUrl: ""
|
|
358
|
-
}
|
|
359
|
-
},
|
|
360
|
-
workspaces: [{ id: "1", slug: "acme", name: "Workspace" }],
|
|
361
|
-
permissions: []
|
|
362
|
-
};
|
|
363
|
-
}
|
|
386
|
+
})
|
|
364
387
|
});
|
|
365
388
|
|
|
366
389
|
await runtime.initialize();
|
|
390
|
+
await runtime.applyBootstrapPayload({
|
|
391
|
+
request: createBootstrapRequest("/w/acme/dashboard", "acme"),
|
|
392
|
+
payload: {
|
|
393
|
+
session: {
|
|
394
|
+
authenticated: true,
|
|
395
|
+
userId: "9"
|
|
396
|
+
},
|
|
397
|
+
profile: {
|
|
398
|
+
displayName: "User",
|
|
399
|
+
email: "user@example.com",
|
|
400
|
+
avatar: {
|
|
401
|
+
effectiveUrl: ""
|
|
402
|
+
}
|
|
403
|
+
},
|
|
404
|
+
workspaces: [{ id: "1", slug: "acme", name: "Workspace" }],
|
|
405
|
+
permissions: []
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
|
|
367
409
|
assert.deepEqual(placementRuntime.getContext().auth, {
|
|
368
410
|
authenticated: true,
|
|
369
411
|
oauthDefaultProvider: "github",
|
|
@@ -371,235 +413,130 @@ test("bootstrap placement runtime does not mutate placement auth context", async
|
|
|
371
413
|
});
|
|
372
414
|
});
|
|
373
415
|
|
|
374
|
-
test("bootstrap placement runtime
|
|
416
|
+
test("bootstrap placement runtime delegates route and realtime refreshes to the shared bootstrap runtime", async () => {
|
|
375
417
|
const placementRuntime = createPlacementRuntimeStub();
|
|
376
418
|
const router = createRouterStub("/w/acme/dashboard");
|
|
377
419
|
const socket = createSocketStub();
|
|
378
|
-
const
|
|
420
|
+
const bootstrapRuntime = createBootstrapRuntimeStub();
|
|
379
421
|
const runtime = createBootstrapPlacementRuntime({
|
|
380
422
|
app: createAppStub({
|
|
381
423
|
["runtime.web-placement.client"]: placementRuntime,
|
|
424
|
+
["runtime.web-bootstrap.client"]: bootstrapRuntime,
|
|
382
425
|
["jskit.client.router"]: router,
|
|
383
426
|
["runtime.realtime.client.socket"]: socket
|
|
384
|
-
})
|
|
385
|
-
fetchBootstrap: async (workspaceSlug) => {
|
|
386
|
-
fetchCalls.push(workspaceSlug);
|
|
387
|
-
return {
|
|
388
|
-
session: {
|
|
389
|
-
authenticated: true,
|
|
390
|
-
userId: "1"
|
|
391
|
-
},
|
|
392
|
-
profile: {
|
|
393
|
-
displayName: "User",
|
|
394
|
-
email: "user@example.com",
|
|
395
|
-
avatar: {
|
|
396
|
-
effectiveUrl: ""
|
|
397
|
-
}
|
|
398
|
-
},
|
|
399
|
-
workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
400
|
-
permissions: []
|
|
401
|
-
};
|
|
402
|
-
}
|
|
427
|
+
})
|
|
403
428
|
});
|
|
404
429
|
|
|
405
430
|
await runtime.initialize();
|
|
406
|
-
assert.deepEqual(
|
|
431
|
+
assert.deepEqual(bootstrapRuntime.calls, []);
|
|
407
432
|
|
|
408
433
|
router.currentRoute.value.path = "/w/acme/customers";
|
|
409
434
|
router.currentRoute.value.fullPath = "/w/acme/customers";
|
|
410
435
|
router.emitAfterEach();
|
|
411
436
|
await flushTasks();
|
|
412
|
-
assert.deepEqual(
|
|
437
|
+
assert.deepEqual(bootstrapRuntime.calls, []);
|
|
413
438
|
|
|
414
439
|
router.currentRoute.value.path = "/w/zen/dashboard";
|
|
415
440
|
router.currentRoute.value.fullPath = "/w/zen/dashboard";
|
|
416
441
|
router.emitAfterEach();
|
|
417
442
|
await flushTasks();
|
|
418
|
-
assert.deepEqual(
|
|
443
|
+
assert.deepEqual(bootstrapRuntime.calls, ["route"]);
|
|
419
444
|
|
|
420
445
|
socket.emit("users.bootstrap.changed", {});
|
|
421
446
|
await flushTasks();
|
|
422
|
-
assert.deepEqual(
|
|
447
|
+
assert.deepEqual(bootstrapRuntime.calls, ["route", "realtime"]);
|
|
423
448
|
});
|
|
424
449
|
|
|
425
|
-
test("bootstrap placement runtime
|
|
450
|
+
test("bootstrap placement runtime applies theme changes from bootstrap payloads", async () => {
|
|
426
451
|
const placementRuntime = createPlacementRuntimeStub();
|
|
427
452
|
const router = createRouterStub("/w/acme/dashboard");
|
|
428
|
-
const
|
|
453
|
+
const themeController = createVuetifyThemeController("light");
|
|
429
454
|
const runtime = createBootstrapPlacementRuntime({
|
|
430
455
|
app: createAppStub({
|
|
431
456
|
["runtime.web-placement.client"]: placementRuntime,
|
|
432
|
-
["
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
return {
|
|
437
|
-
session: {
|
|
438
|
-
authenticated: true,
|
|
439
|
-
userId: "1"
|
|
440
|
-
},
|
|
441
|
-
profile: {
|
|
442
|
-
displayName: "User",
|
|
443
|
-
email: "user@example.com",
|
|
444
|
-
avatar: {
|
|
445
|
-
effectiveUrl: ""
|
|
446
|
-
}
|
|
447
|
-
},
|
|
448
|
-
workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
449
|
-
permissions: []
|
|
450
|
-
};
|
|
451
|
-
}
|
|
457
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
458
|
+
["jskit.client.router"]: router,
|
|
459
|
+
["jskit.client.vue.app"]: createVueAppWithThemeController(themeController)
|
|
460
|
+
})
|
|
452
461
|
});
|
|
453
462
|
|
|
454
463
|
await runtime.initialize();
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
{
|
|
459
|
-
|
|
464
|
+
const request = createBootstrapRequest("/w/acme/dashboard", "acme");
|
|
465
|
+
await runtime.applyBootstrapPayload({
|
|
466
|
+
request,
|
|
467
|
+
payload: {
|
|
468
|
+
session: {
|
|
460
469
|
authenticated: true,
|
|
461
|
-
|
|
462
|
-
oauthProviders: []
|
|
463
|
-
}
|
|
464
|
-
},
|
|
465
|
-
{
|
|
466
|
-
source: "test.auth"
|
|
467
|
-
}
|
|
468
|
-
);
|
|
469
|
-
await flushTasks();
|
|
470
|
-
await flushTasks();
|
|
471
|
-
assert.deepEqual(fetchCalls, ["acme", "acme"]);
|
|
472
|
-
});
|
|
473
|
-
|
|
474
|
-
test("bootstrap placement runtime applies persisted theme preference for unauthenticated bootstrap payloads", async () => {
|
|
475
|
-
const placementRuntime = createPlacementRuntimeStub();
|
|
476
|
-
const router = createRouterStub("/auth/login");
|
|
477
|
-
const themeController = createVuetifyThemeController("dark");
|
|
478
|
-
const storage = new Map();
|
|
479
|
-
storage.set("jskit.themePreference", "dark");
|
|
480
|
-
const originalWindow = globalThis.window;
|
|
481
|
-
globalThis.window = {
|
|
482
|
-
localStorage: {
|
|
483
|
-
getItem(key) {
|
|
484
|
-
return storage.get(key) || null;
|
|
470
|
+
userId: "1"
|
|
485
471
|
},
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
const runtime = createBootstrapPlacementRuntime({
|
|
492
|
-
app: createAppStub({
|
|
493
|
-
["runtime.web-placement.client"]: placementRuntime,
|
|
494
|
-
["jskit.client.router"]: router,
|
|
495
|
-
["jskit.client.vue.app"]: createVueAppWithThemeController(themeController)
|
|
496
|
-
}),
|
|
497
|
-
fetchBootstrap: async () => {
|
|
498
|
-
return {
|
|
499
|
-
session: {
|
|
500
|
-
authenticated: false
|
|
501
|
-
},
|
|
502
|
-
workspaces: [],
|
|
503
|
-
permissions: []
|
|
504
|
-
};
|
|
472
|
+
userSettings: {
|
|
473
|
+
theme: "dark"
|
|
474
|
+
},
|
|
475
|
+
workspaces: [{ id: "1", slug: "acme", name: "Workspace" }],
|
|
476
|
+
permissions: []
|
|
505
477
|
}
|
|
506
478
|
});
|
|
479
|
+
assert.equal(themeController.global.name.value, "workspace-dark");
|
|
507
480
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
481
|
+
await runtime.applyBootstrapPayload({
|
|
482
|
+
request,
|
|
483
|
+
payload: {
|
|
484
|
+
session: {
|
|
485
|
+
authenticated: true,
|
|
486
|
+
userId: "1"
|
|
487
|
+
},
|
|
488
|
+
userSettings: {
|
|
489
|
+
theme: "light"
|
|
490
|
+
},
|
|
491
|
+
workspaces: [{ id: "1", slug: "acme", name: "Workspace" }],
|
|
492
|
+
permissions: []
|
|
516
493
|
}
|
|
517
|
-
}
|
|
494
|
+
});
|
|
495
|
+
assert.equal(themeController.global.name.value, "workspace-light");
|
|
518
496
|
});
|
|
519
497
|
|
|
520
|
-
test("bootstrap placement runtime
|
|
498
|
+
test("bootstrap placement runtime applies workspace palette and clears it when leaving workspace routes", async () => {
|
|
521
499
|
const placementRuntime = createPlacementRuntimeStub();
|
|
522
500
|
const router = createRouterStub("/w/acme/dashboard");
|
|
523
|
-
const socket = createSocketStub();
|
|
524
501
|
const themeController = createVuetifyThemeController("light");
|
|
525
|
-
let fetchCount = 0;
|
|
526
502
|
const runtime = createBootstrapPlacementRuntime({
|
|
527
503
|
app: createAppStub({
|
|
528
504
|
["runtime.web-placement.client"]: placementRuntime,
|
|
505
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
529
506
|
["jskit.client.router"]: router,
|
|
530
|
-
["runtime.realtime.client.socket"]: socket,
|
|
531
507
|
["jskit.client.vue.app"]: createVueAppWithThemeController(themeController)
|
|
532
|
-
})
|
|
533
|
-
fetchBootstrap: async (workspaceSlug) => {
|
|
534
|
-
fetchCount += 1;
|
|
535
|
-
return {
|
|
536
|
-
session: {
|
|
537
|
-
authenticated: true,
|
|
538
|
-
userId: "1"
|
|
539
|
-
},
|
|
540
|
-
profile: {
|
|
541
|
-
displayName: "User",
|
|
542
|
-
email: "user@example.com",
|
|
543
|
-
avatar: {
|
|
544
|
-
effectiveUrl: ""
|
|
545
|
-
}
|
|
546
|
-
},
|
|
547
|
-
userSettings: {
|
|
548
|
-
theme: fetchCount === 1 ? "dark" : "light"
|
|
549
|
-
},
|
|
550
|
-
workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
551
|
-
permissions: []
|
|
552
|
-
};
|
|
553
|
-
}
|
|
508
|
+
})
|
|
554
509
|
});
|
|
555
510
|
|
|
556
511
|
await runtime.initialize();
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
lightSecondaryColor: "#884455",
|
|
583
|
-
lightSurfaceColor: "#F4F4F4",
|
|
584
|
-
lightSurfaceVariantColor: "#444444",
|
|
585
|
-
darkPrimaryColor: "#BB2233",
|
|
586
|
-
darkSecondaryColor: "#557799",
|
|
587
|
-
darkSurfaceColor: "#202020",
|
|
588
|
-
darkSurfaceVariantColor: "#A0A0A0"
|
|
589
|
-
},
|
|
590
|
-
workspaces: [
|
|
591
|
-
{
|
|
592
|
-
id: "1",
|
|
593
|
-
slug: "acme",
|
|
594
|
-
name: "Acme Workspace"
|
|
595
|
-
}
|
|
596
|
-
],
|
|
597
|
-
permissions: []
|
|
598
|
-
};
|
|
512
|
+
await runtime.applyBootstrapPayload({
|
|
513
|
+
request: createBootstrapRequest("/w/acme/dashboard", "acme"),
|
|
514
|
+
payload: {
|
|
515
|
+
session: {
|
|
516
|
+
authenticated: true,
|
|
517
|
+
userId: "1"
|
|
518
|
+
},
|
|
519
|
+
workspaceSettings: {
|
|
520
|
+
lightPrimaryColor: "#CC3344",
|
|
521
|
+
lightSecondaryColor: "#884455",
|
|
522
|
+
lightSurfaceColor: "#F4F4F4",
|
|
523
|
+
lightSurfaceVariantColor: "#444444",
|
|
524
|
+
darkPrimaryColor: "#BB2233",
|
|
525
|
+
darkSecondaryColor: "#557799",
|
|
526
|
+
darkSurfaceColor: "#202020",
|
|
527
|
+
darkSurfaceVariantColor: "#A0A0A0"
|
|
528
|
+
},
|
|
529
|
+
workspaces: [
|
|
530
|
+
{
|
|
531
|
+
id: "1",
|
|
532
|
+
slug: "acme",
|
|
533
|
+
name: "Acme Workspace"
|
|
534
|
+
}
|
|
535
|
+
],
|
|
536
|
+
permissions: []
|
|
599
537
|
}
|
|
600
538
|
});
|
|
601
539
|
|
|
602
|
-
await runtime.initialize();
|
|
603
540
|
const palette = resolveWorkspaceThemePalette({
|
|
604
541
|
lightPrimaryColor: "#CC3344",
|
|
605
542
|
lightSecondaryColor: "#884455",
|
|
@@ -621,7 +558,6 @@ test("bootstrap placement runtime applies workspace palette via Vuetify workspac
|
|
|
621
558
|
router.currentRoute.value.fullPath = "/home";
|
|
622
559
|
router.emitAfterEach();
|
|
623
560
|
await flushTasks();
|
|
624
|
-
await flushTasks();
|
|
625
561
|
|
|
626
562
|
assert.equal(themeController.global.name.value, "light");
|
|
627
563
|
});
|
|
@@ -632,24 +568,26 @@ test("bootstrap placement runtime marks workspace slug as not_found and clears w
|
|
|
632
568
|
{
|
|
633
569
|
workspace: { id: "1", slug: "acme", name: "Acme Workspace" },
|
|
634
570
|
workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
|
|
635
|
-
permissions: ["workspace.settings.view"]
|
|
571
|
+
permissions: ["workspace.settings.view"],
|
|
572
|
+
surfaceAccess: {
|
|
573
|
+
consoleowner: true
|
|
574
|
+
}
|
|
636
575
|
},
|
|
637
576
|
{ source: "test.seed" }
|
|
638
577
|
);
|
|
639
|
-
|
|
640
578
|
const runtime = createBootstrapPlacementRuntime({
|
|
641
579
|
app: createAppStub({
|
|
642
580
|
["runtime.web-placement.client"]: placementRuntime,
|
|
581
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
643
582
|
["jskit.client.router"]: createRouterStub("/w/acme/dashboard")
|
|
644
|
-
})
|
|
645
|
-
fetchBootstrap: async () => {
|
|
646
|
-
const error = new Error("Workspace not found.");
|
|
647
|
-
error.status = 404;
|
|
648
|
-
throw error;
|
|
649
|
-
}
|
|
583
|
+
})
|
|
650
584
|
});
|
|
651
585
|
|
|
652
586
|
await runtime.initialize();
|
|
587
|
+
await runtime.handleBootstrapError({
|
|
588
|
+
request: createBootstrapRequest("/w/acme/dashboard", "acme"),
|
|
589
|
+
error: createErrorWithStatus(404, "Workspace not found.")
|
|
590
|
+
});
|
|
653
591
|
|
|
654
592
|
const context = placementRuntime.getContext();
|
|
655
593
|
assert.equal(runtime.getWorkspaceBootstrapStatus("acme"), WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND);
|
|
@@ -657,56 +595,57 @@ test("bootstrap placement runtime marks workspace slug as not_found and clears w
|
|
|
657
595
|
assert.equal(context.workspace, null);
|
|
658
596
|
assert.deepEqual(context.workspaces, []);
|
|
659
597
|
assert.deepEqual(context.permissions, []);
|
|
660
|
-
assert.equal(context.user, null);
|
|
661
598
|
assert.equal(context.pendingInvitesCount, 0);
|
|
662
599
|
assert.equal(context.workspaceInvitesEnabled, false);
|
|
600
|
+
assert.deepEqual(context.surfaceAccess, {
|
|
601
|
+
consoleowner: true
|
|
602
|
+
});
|
|
663
603
|
});
|
|
664
604
|
|
|
665
|
-
test("bootstrap placement runtime
|
|
605
|
+
test("bootstrap placement runtime tracks status per workspace slug across route changes", async () => {
|
|
666
606
|
const placementRuntime = createPlacementRuntimeStub();
|
|
667
607
|
const router = createRouterStub("/w/acme/dashboard");
|
|
608
|
+
const bootstrapRuntime = createBootstrapRuntimeStub();
|
|
668
609
|
const runtime = createBootstrapPlacementRuntime({
|
|
669
610
|
app: createAppStub({
|
|
670
611
|
["runtime.web-placement.client"]: placementRuntime,
|
|
612
|
+
["runtime.web-bootstrap.client"]: bootstrapRuntime,
|
|
671
613
|
["jskit.client.router"]: router
|
|
672
|
-
})
|
|
673
|
-
|
|
674
|
-
if (workspaceSlug === "zen") {
|
|
675
|
-
const error = new Error("Workspace not found.");
|
|
676
|
-
error.status = 404;
|
|
677
|
-
throw error;
|
|
678
|
-
}
|
|
614
|
+
})
|
|
615
|
+
});
|
|
679
616
|
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
}
|
|
691
|
-
},
|
|
692
|
-
workspaces: [{ id: "1", slug: workspaceSlug || "acme", name: "Workspace" }],
|
|
693
|
-
permissions: []
|
|
694
|
-
};
|
|
617
|
+
await runtime.initialize();
|
|
618
|
+
await runtime.applyBootstrapPayload({
|
|
619
|
+
request: createBootstrapRequest("/w/acme/dashboard", "acme"),
|
|
620
|
+
payload: {
|
|
621
|
+
session: {
|
|
622
|
+
authenticated: true,
|
|
623
|
+
userId: "1"
|
|
624
|
+
},
|
|
625
|
+
workspaces: [{ id: "1", slug: "acme", name: "Workspace" }],
|
|
626
|
+
permissions: []
|
|
695
627
|
}
|
|
696
628
|
});
|
|
697
629
|
|
|
698
|
-
await runtime.initialize();
|
|
699
630
|
assert.equal(runtime.getWorkspaceBootstrapStatus("acme"), WORKSPACE_BOOTSTRAP_STATUS_RESOLVED);
|
|
700
631
|
|
|
701
632
|
router.currentRoute.value.path = "/w/zen/dashboard";
|
|
702
633
|
router.currentRoute.value.fullPath = "/w/zen/dashboard";
|
|
703
634
|
router.emitAfterEach();
|
|
704
635
|
await flushTasks();
|
|
636
|
+
assert.deepEqual(bootstrapRuntime.calls, ["route"]);
|
|
637
|
+
|
|
638
|
+
await runtime.handleBootstrapError({
|
|
639
|
+
request: createBootstrapRequest("/w/zen", "zen"),
|
|
640
|
+
error: createErrorWithStatus(404, "Workspace not found.")
|
|
641
|
+
});
|
|
705
642
|
|
|
706
643
|
const context = placementRuntime.getContext();
|
|
644
|
+
assert.equal(runtime.getWorkspaceBootstrapStatus("acme"), WORKSPACE_BOOTSTRAP_STATUS_RESOLVED);
|
|
707
645
|
assert.equal(runtime.getWorkspaceBootstrapStatus("zen"), WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND);
|
|
646
|
+
assert.equal(context.workspaceBootstrapStatuses?.acme, WORKSPACE_BOOTSTRAP_STATUS_RESOLVED);
|
|
708
647
|
assert.equal(context.workspaceBootstrapStatuses?.zen, WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND);
|
|
709
|
-
assert.
|
|
648
|
+
assert.deepEqual(router.replaceCalls, ["/w/zen"]);
|
|
710
649
|
});
|
|
711
650
|
|
|
712
651
|
test("bootstrap placement runtime uses requestedWorkspace status and keeps global workspace list on inaccessible slug", async () => {
|
|
@@ -715,32 +654,34 @@ test("bootstrap placement runtime uses requestedWorkspace status and keeps globa
|
|
|
715
654
|
const runtime = createBootstrapPlacementRuntime({
|
|
716
655
|
app: createAppStub({
|
|
717
656
|
["runtime.web-placement.client"]: placementRuntime,
|
|
657
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
718
658
|
["jskit.client.router"]: router
|
|
719
|
-
})
|
|
720
|
-
fetchBootstrap: async () => {
|
|
721
|
-
return {
|
|
722
|
-
session: {
|
|
723
|
-
authenticated: true,
|
|
724
|
-
userId: "4"
|
|
725
|
-
},
|
|
726
|
-
profile: {
|
|
727
|
-
displayName: "Chiara",
|
|
728
|
-
email: "chiara@example.com",
|
|
729
|
-
avatar: {
|
|
730
|
-
effectiveUrl: ""
|
|
731
|
-
}
|
|
732
|
-
},
|
|
733
|
-
workspaces: [{ id: "3", slug: "chiaramobily", name: "Chiara Workspace" }],
|
|
734
|
-
requestedWorkspace: {
|
|
735
|
-
slug: "tonymobily",
|
|
736
|
-
status: "forbidden"
|
|
737
|
-
},
|
|
738
|
-
permissions: []
|
|
739
|
-
};
|
|
740
|
-
}
|
|
659
|
+
})
|
|
741
660
|
});
|
|
742
661
|
|
|
743
662
|
await runtime.initialize();
|
|
663
|
+
await runtime.applyBootstrapPayload({
|
|
664
|
+
request: createBootstrapRequest("/w/tonymobily", "tonymobily"),
|
|
665
|
+
payload: {
|
|
666
|
+
session: {
|
|
667
|
+
authenticated: true,
|
|
668
|
+
userId: "4"
|
|
669
|
+
},
|
|
670
|
+
profile: {
|
|
671
|
+
displayName: "Chiara",
|
|
672
|
+
email: "chiara@example.com",
|
|
673
|
+
avatar: {
|
|
674
|
+
effectiveUrl: ""
|
|
675
|
+
}
|
|
676
|
+
},
|
|
677
|
+
workspaces: [{ id: "3", slug: "chiaramobily", name: "Chiara Workspace" }],
|
|
678
|
+
requestedWorkspace: {
|
|
679
|
+
slug: "tonymobily",
|
|
680
|
+
status: "forbidden"
|
|
681
|
+
},
|
|
682
|
+
permissions: []
|
|
683
|
+
}
|
|
684
|
+
});
|
|
744
685
|
|
|
745
686
|
const context = placementRuntime.getContext();
|
|
746
687
|
assert.equal(runtime.getWorkspaceBootstrapStatus("tonymobily"), WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN);
|
|
@@ -757,32 +698,34 @@ test("bootstrap placement runtime uses requestedWorkspace=not_found without forc
|
|
|
757
698
|
const runtime = createBootstrapPlacementRuntime({
|
|
758
699
|
app: createAppStub({
|
|
759
700
|
["runtime.web-placement.client"]: placementRuntime,
|
|
701
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
760
702
|
["jskit.client.router"]: router
|
|
761
|
-
})
|
|
762
|
-
fetchBootstrap: async () => {
|
|
763
|
-
return {
|
|
764
|
-
session: {
|
|
765
|
-
authenticated: true,
|
|
766
|
-
userId: "1"
|
|
767
|
-
},
|
|
768
|
-
profile: {
|
|
769
|
-
displayName: "User",
|
|
770
|
-
email: "user@example.com",
|
|
771
|
-
avatar: {
|
|
772
|
-
effectiveUrl: ""
|
|
773
|
-
}
|
|
774
|
-
},
|
|
775
|
-
workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
|
|
776
|
-
requestedWorkspace: {
|
|
777
|
-
slug: "missing",
|
|
778
|
-
status: "not_found"
|
|
779
|
-
},
|
|
780
|
-
permissions: []
|
|
781
|
-
};
|
|
782
|
-
}
|
|
703
|
+
})
|
|
783
704
|
});
|
|
784
705
|
|
|
785
706
|
await runtime.initialize();
|
|
707
|
+
await runtime.applyBootstrapPayload({
|
|
708
|
+
request: createBootstrapRequest("/w/missing", "missing"),
|
|
709
|
+
payload: {
|
|
710
|
+
session: {
|
|
711
|
+
authenticated: true,
|
|
712
|
+
userId: "1"
|
|
713
|
+
},
|
|
714
|
+
profile: {
|
|
715
|
+
displayName: "User",
|
|
716
|
+
email: "user@example.com",
|
|
717
|
+
avatar: {
|
|
718
|
+
effectiveUrl: ""
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
workspaces: [{ id: "1", slug: "acme", name: "Acme Workspace" }],
|
|
722
|
+
requestedWorkspace: {
|
|
723
|
+
slug: "missing",
|
|
724
|
+
status: "not_found"
|
|
725
|
+
},
|
|
726
|
+
permissions: []
|
|
727
|
+
}
|
|
728
|
+
});
|
|
786
729
|
|
|
787
730
|
const context = placementRuntime.getContext();
|
|
788
731
|
assert.equal(runtime.getWorkspaceBootstrapStatus("missing"), WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND);
|
|
@@ -805,21 +748,24 @@ test("bootstrap placement runtime guard wrapper preserves delegated deny outcome
|
|
|
805
748
|
const runtime = createBootstrapPlacementRuntime({
|
|
806
749
|
app: createAppStub({
|
|
807
750
|
["runtime.web-placement.client"]: placementRuntime,
|
|
751
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
808
752
|
["jskit.client.router"]: router
|
|
809
|
-
})
|
|
810
|
-
fetchBootstrap: async () => {
|
|
811
|
-
return {
|
|
812
|
-
session: {
|
|
813
|
-
authenticated: true,
|
|
814
|
-
userId: "1"
|
|
815
|
-
},
|
|
816
|
-
workspaces: [{ id: "1", slug: "acme", name: "Acme" }],
|
|
817
|
-
permissions: []
|
|
818
|
-
};
|
|
819
|
-
}
|
|
753
|
+
})
|
|
820
754
|
});
|
|
821
755
|
|
|
822
756
|
await runtime.initialize();
|
|
757
|
+
await runtime.applyBootstrapPayload({
|
|
758
|
+
request: createBootstrapRequest("/w/acme/dashboard", "acme"),
|
|
759
|
+
payload: {
|
|
760
|
+
session: {
|
|
761
|
+
authenticated: true,
|
|
762
|
+
userId: "1"
|
|
763
|
+
},
|
|
764
|
+
workspaces: [{ id: "1", slug: "acme", name: "Acme" }],
|
|
765
|
+
permissions: []
|
|
766
|
+
}
|
|
767
|
+
});
|
|
768
|
+
|
|
823
769
|
const evaluator = globalThis[SHELL_GUARD_EVALUATOR_KEY];
|
|
824
770
|
const outcome = evaluator({
|
|
825
771
|
guard: {
|
|
@@ -840,30 +786,25 @@ test("bootstrap placement runtime guard wrapper preserves delegated deny outcome
|
|
|
840
786
|
assert.deepEqual(outcome, delegatedOutcome);
|
|
841
787
|
});
|
|
842
788
|
|
|
843
|
-
test("bootstrap placement runtime guard wrapper blocks forbidden workspace routes", async () => {
|
|
789
|
+
test("bootstrap placement runtime guard wrapper blocks forbidden workspace routes and redirects nested workspace paths", async () => {
|
|
844
790
|
const placementRuntime = createPlacementRuntimeStub();
|
|
845
|
-
const router = createRouterStub("/w/acme/
|
|
846
|
-
globalThis[SHELL_GUARD_EVALUATOR_KEY] = () => ({ allow: true, redirectTo: "", reason: "" });
|
|
847
|
-
|
|
791
|
+
const router = createRouterStub("/w/acme/admin/workspace/settings?tab=general");
|
|
848
792
|
const runtime = createBootstrapPlacementRuntime({
|
|
849
793
|
app: createAppStub({
|
|
850
794
|
["runtime.web-placement.client"]: placementRuntime,
|
|
795
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
851
796
|
["jskit.client.router"]: router
|
|
852
|
-
})
|
|
853
|
-
fetchBootstrap: async () => {
|
|
854
|
-
return {
|
|
855
|
-
session: {
|
|
856
|
-
authenticated: true,
|
|
857
|
-
userId: "1"
|
|
858
|
-
},
|
|
859
|
-
workspaces: [],
|
|
860
|
-
permissions: []
|
|
861
|
-
};
|
|
862
|
-
}
|
|
797
|
+
})
|
|
863
798
|
});
|
|
864
799
|
|
|
865
800
|
await runtime.initialize();
|
|
801
|
+
await runtime.handleBootstrapError({
|
|
802
|
+
request: createBootstrapRequest("/w/acme/admin/workspace/settings?tab=general", "acme"),
|
|
803
|
+
error: createErrorWithStatus(403, "Forbidden")
|
|
804
|
+
});
|
|
805
|
+
|
|
866
806
|
assert.equal(runtime.getWorkspaceBootstrapStatus("acme"), WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN);
|
|
807
|
+
assert.deepEqual(router.replaceCalls, ["/w/acme/admin"]);
|
|
867
808
|
|
|
868
809
|
const evaluator = globalThis[SHELL_GUARD_EVALUATOR_KEY];
|
|
869
810
|
const outcome = evaluator({
|
|
@@ -889,21 +830,23 @@ test("bootstrap placement runtime guard wrapper blocks forbidden workspace route
|
|
|
889
830
|
|
|
890
831
|
test("bootstrap placement runtime guard wrapper redirects nested not_found routes to workspace surface root", async () => {
|
|
891
832
|
const placementRuntime = createPlacementRuntimeStub();
|
|
892
|
-
const router = createRouterStub("/w/acme/
|
|
833
|
+
const router = createRouterStub("/w/acme/projects");
|
|
893
834
|
const runtime = createBootstrapPlacementRuntime({
|
|
894
835
|
app: createAppStub({
|
|
895
836
|
["runtime.web-placement.client"]: placementRuntime,
|
|
837
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
896
838
|
["jskit.client.router"]: router
|
|
897
|
-
})
|
|
898
|
-
fetchBootstrap: async () => {
|
|
899
|
-
const error = new Error("Not found");
|
|
900
|
-
error.status = 404;
|
|
901
|
-
throw error;
|
|
902
|
-
}
|
|
839
|
+
})
|
|
903
840
|
});
|
|
904
841
|
|
|
905
842
|
await runtime.initialize();
|
|
843
|
+
await runtime.handleBootstrapError({
|
|
844
|
+
request: createBootstrapRequest("/w/acme/projects", "acme"),
|
|
845
|
+
error: createErrorWithStatus(404, "Not found")
|
|
846
|
+
});
|
|
847
|
+
|
|
906
848
|
assert.equal(runtime.getWorkspaceBootstrapStatus("acme"), WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND);
|
|
849
|
+
assert.deepEqual(router.replaceCalls, ["/w/acme"]);
|
|
907
850
|
|
|
908
851
|
const evaluator = globalThis[SHELL_GUARD_EVALUATOR_KEY];
|
|
909
852
|
const nestedOutcome = evaluator({
|
|
@@ -951,46 +894,30 @@ test("bootstrap placement runtime redirects admin nested route to admin root whe
|
|
|
951
894
|
const runtime = createBootstrapPlacementRuntime({
|
|
952
895
|
app: createAppStub({
|
|
953
896
|
["runtime.web-placement.client"]: placementRuntime,
|
|
897
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
954
898
|
["jskit.client.router"]: router
|
|
955
|
-
})
|
|
956
|
-
fetchBootstrap: async () => {
|
|
957
|
-
const error = new Error("Not found");
|
|
958
|
-
error.status = 404;
|
|
959
|
-
throw error;
|
|
960
|
-
}
|
|
899
|
+
})
|
|
961
900
|
});
|
|
962
901
|
|
|
963
902
|
await runtime.initialize();
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
test("bootstrap placement runtime redirects forbidden workspace route to workspace surface root", async () => {
|
|
969
|
-
const placementRuntime = createPlacementRuntimeStub();
|
|
970
|
-
const router = createRouterStub("/w/acme/admin/workspace/settings?tab=general");
|
|
971
|
-
const runtime = createBootstrapPlacementRuntime({
|
|
972
|
-
app: createAppStub({
|
|
973
|
-
["runtime.web-placement.client"]: placementRuntime,
|
|
974
|
-
["jskit.client.router"]: router
|
|
975
|
-
}),
|
|
976
|
-
fetchBootstrap: async () => {
|
|
977
|
-
const error = new Error("Forbidden");
|
|
978
|
-
error.status = 403;
|
|
979
|
-
throw error;
|
|
980
|
-
}
|
|
903
|
+
await runtime.handleBootstrapError({
|
|
904
|
+
request: createBootstrapRequest("/w/acme/admin/workspace/settings", "acme"),
|
|
905
|
+
error: createErrorWithStatus(404, "Not found")
|
|
981
906
|
});
|
|
982
907
|
|
|
983
|
-
|
|
984
|
-
assert.equal(runtime.getWorkspaceBootstrapStatus("acme"), WORKSPACE_BOOTSTRAP_STATUS_FORBIDDEN);
|
|
908
|
+
assert.equal(runtime.getWorkspaceBootstrapStatus("acme"), WORKSPACE_BOOTSTRAP_STATUS_NOT_FOUND);
|
|
985
909
|
assert.deepEqual(router.replaceCalls, ["/w/acme/admin"]);
|
|
986
910
|
});
|
|
987
911
|
|
|
988
|
-
test("bootstrap placement runtime enforces surface access policies after bootstrap
|
|
912
|
+
test("bootstrap placement runtime enforces surface access policies after bootstrap payloads", async () => {
|
|
989
913
|
const placementRuntime = createPlacementRuntimeStub();
|
|
990
914
|
placementRuntime.setContext({
|
|
991
915
|
auth: {
|
|
992
916
|
authenticated: true
|
|
993
917
|
},
|
|
918
|
+
surfaceAccess: {
|
|
919
|
+
opsowner: false
|
|
920
|
+
},
|
|
994
921
|
surfaceAccessPolicies: {
|
|
995
922
|
public: {},
|
|
996
923
|
ops_owner: {
|
|
@@ -1026,70 +953,43 @@ test("bootstrap placement runtime enforces surface access policies after bootstr
|
|
|
1026
953
|
const runtime = createBootstrapPlacementRuntime({
|
|
1027
954
|
app: createAppStub({
|
|
1028
955
|
["runtime.web-placement.client"]: placementRuntime,
|
|
956
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
1029
957
|
["jskit.client.router"]: router
|
|
1030
|
-
})
|
|
1031
|
-
fetchBootstrap: async () => {
|
|
1032
|
-
return {
|
|
1033
|
-
session: {
|
|
1034
|
-
authenticated: true,
|
|
1035
|
-
userId: "1"
|
|
1036
|
-
},
|
|
1037
|
-
workspaces: [],
|
|
1038
|
-
permissions: [],
|
|
1039
|
-
surfaceAccess: {
|
|
1040
|
-
opsowner: false
|
|
1041
|
-
}
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
958
|
+
})
|
|
1044
959
|
});
|
|
1045
960
|
|
|
1046
961
|
await runtime.initialize();
|
|
962
|
+
await runtime.applyBootstrapPayload({
|
|
963
|
+
request: createBootstrapRequest("/ops"),
|
|
964
|
+
payload: {
|
|
965
|
+
session: {
|
|
966
|
+
authenticated: true,
|
|
967
|
+
userId: "1"
|
|
968
|
+
},
|
|
969
|
+
workspaces: [],
|
|
970
|
+
permissions: []
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
|
|
1047
974
|
assert.deepEqual(router.replaceCalls, ["/home"]);
|
|
1048
975
|
});
|
|
1049
976
|
|
|
1050
|
-
test("bootstrap placement runtime
|
|
977
|
+
test("bootstrap placement runtime handles unauthenticated errors and marks workspace status", async () => {
|
|
1051
978
|
const placementRuntime = createPlacementRuntimeStub();
|
|
1052
|
-
const router = createRouterStub("/w/acme/dashboard");
|
|
1053
979
|
const runtime = createBootstrapPlacementRuntime({
|
|
1054
980
|
app: createAppStub({
|
|
1055
981
|
["runtime.web-placement.client"]: placementRuntime,
|
|
1056
|
-
["
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
return {
|
|
1060
|
-
session: {
|
|
1061
|
-
authenticated: true,
|
|
1062
|
-
userId: "1"
|
|
1063
|
-
},
|
|
1064
|
-
workspaces: [{ id: "1", slug: "acme", name: "Acme" }],
|
|
1065
|
-
permissions: []
|
|
1066
|
-
};
|
|
1067
|
-
}
|
|
982
|
+
["runtime.web-bootstrap.client"]: createBootstrapRuntimeStub(),
|
|
983
|
+
["jskit.client.router"]: createRouterStub("/w/acme/dashboard")
|
|
984
|
+
})
|
|
1068
985
|
});
|
|
1069
986
|
|
|
1070
987
|
await runtime.initialize();
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
reason: "auth-required"
|
|
1075
|
-
};
|
|
1076
|
-
globalThis[SHELL_GUARD_EVALUATOR_KEY] = () => delegatedOutcome;
|
|
1077
|
-
|
|
1078
|
-
const evaluator = globalThis[SHELL_GUARD_EVALUATOR_KEY];
|
|
1079
|
-
const outcome = evaluator({
|
|
1080
|
-
guard: {
|
|
1081
|
-
policy: "authenticated"
|
|
1082
|
-
},
|
|
1083
|
-
context: {
|
|
1084
|
-
to: {
|
|
1085
|
-
path: "/w/acme/dashboard",
|
|
1086
|
-
fullPath: "/w/acme/dashboard"
|
|
1087
|
-
},
|
|
1088
|
-
location: {
|
|
1089
|
-
pathname: "/w/acme/dashboard",
|
|
1090
|
-
search: ""
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
988
|
+
await runtime.handleBootstrapError({
|
|
989
|
+
request: createBootstrapRequest("/w/acme/dashboard", "acme"),
|
|
990
|
+
error: createErrorWithStatus(401, "Unauthenticated")
|
|
1093
991
|
});
|
|
1094
|
-
|
|
992
|
+
|
|
993
|
+
assert.equal(runtime.getWorkspaceBootstrapStatus("acme"), WORKSPACE_BOOTSTRAP_STATUS_UNAUTHENTICATED);
|
|
994
|
+
assert.equal(placementRuntime.getContext().workspace, null);
|
|
1095
995
|
});
|