feat(housekeeping): personalize command deck
This commit is contained in:
@@ -30,13 +30,19 @@ import {
|
||||
anyCapability,
|
||||
type CanonicalHousekeepingHref,
|
||||
type CapabilityRequirement,
|
||||
type HousekeepingCapabilityContext,
|
||||
type HousekeepingDomainManifest,
|
||||
type HousekeepingInboxSource,
|
||||
type HousekeepingSearchProvider,
|
||||
type HousekeepingWidgetDefinition,
|
||||
ok,
|
||||
} from "./contracts";
|
||||
import { createHousekeepingRegistry } from "./registry";
|
||||
import { defaultHousekeepingPreferences } from "./preferences/schema";
|
||||
import {
|
||||
createHousekeepingRegistry,
|
||||
loadHousekeepingWidgets,
|
||||
WIDGET_POLICY,
|
||||
} from "./registry";
|
||||
|
||||
const manifest = (
|
||||
id: HousekeepingDomainId,
|
||||
@@ -565,6 +571,88 @@ describe("housekeeping registry", () => {
|
||||
).toThrow("invalid widget kind: people.queue");
|
||||
});
|
||||
|
||||
it("loads only authorized mandatory and enabled optional widgets with isolated timeouts", async () => {
|
||||
const context: HousekeepingCapabilityContext = {
|
||||
actor: { id: 42, username: "operator", rank: 7 },
|
||||
isSuperAdmin: false,
|
||||
has: (slug) => slug === PERMS.USERS_VIEW,
|
||||
hasAny: (...slugs) => slugs.includes(PERMS.USERS_VIEW),
|
||||
hasAll: (...slugs) => slugs.every((slug) => slug === PERMS.USERS_VIEW),
|
||||
};
|
||||
let deniedCalls = 0;
|
||||
const base = {
|
||||
...manifest("people"),
|
||||
capability: anyCapability(PERMS.USERS_VIEW),
|
||||
};
|
||||
const registry = createHousekeepingRegistry([
|
||||
{
|
||||
...base,
|
||||
widgets: [
|
||||
{
|
||||
...widget("people.required"),
|
||||
kind: "mandatory",
|
||||
load: async () => ok("required", "required"),
|
||||
},
|
||||
{
|
||||
...widget("people.optional"),
|
||||
load: async () => ok("optional", "optional"),
|
||||
},
|
||||
{
|
||||
...widget(
|
||||
"people.denied",
|
||||
"people",
|
||||
anyCapability(PERMS.BANS_VIEW),
|
||||
),
|
||||
load: async () => {
|
||||
deniedCalls += 1;
|
||||
return ok("denied", "denied");
|
||||
},
|
||||
},
|
||||
{
|
||||
...widget("people.slow"),
|
||||
load: async (_context, signal) =>
|
||||
new Promise((resolve) => {
|
||||
signal.addEventListener("abort", () =>
|
||||
resolve(ok("late", "late")),
|
||||
);
|
||||
}),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const preferences = {
|
||||
...defaultHousekeepingPreferences(),
|
||||
widgetOrder: [
|
||||
"stale.widget",
|
||||
"people.optional",
|
||||
"people.required",
|
||||
"people.slow",
|
||||
],
|
||||
enabledOptionalWidgetIds: [
|
||||
"people.optional",
|
||||
"people.denied",
|
||||
"people.slow",
|
||||
],
|
||||
};
|
||||
|
||||
const result = await loadHousekeepingWidgets(
|
||||
registry,
|
||||
preferences,
|
||||
context,
|
||||
5,
|
||||
);
|
||||
|
||||
expect(WIDGET_POLICY.timeoutMs).toBe(2_000);
|
||||
expect(result.widgets).toEqual([
|
||||
{ id: "people.optional", data: "optional" },
|
||||
{ id: "people.required", data: "required" },
|
||||
]);
|
||||
expect(result.errors).toEqual([
|
||||
{ widgetId: "people.slow", code: "TIMEOUT" },
|
||||
]);
|
||||
expect(deniedCalls).toBe(0);
|
||||
});
|
||||
|
||||
it("rejects empty domain and route capability requirements", () => {
|
||||
expect(() =>
|
||||
createHousekeepingRegistry([
|
||||
|
||||
Reference in New Issue
Block a user