@stratum-hq/control-plane 0.4.0 → 1.0.0

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.
Files changed (44) hide show
  1. package/dist/middleware/authorize.d.ts.map +1 -1
  2. package/dist/middleware/authorize.js +3 -1
  3. package/dist/middleware/authorize.js.map +1 -1
  4. package/package.json +14 -1
  5. package/dist/__tests__/admin-scope-query-string.test.d.ts +0 -2
  6. package/dist/__tests__/admin-scope-query-string.test.d.ts.map +0 -1
  7. package/dist/__tests__/admin-scope-query-string.test.js +0 -69
  8. package/dist/__tests__/admin-scope-query-string.test.js.map +0 -1
  9. package/dist/__tests__/api-keys.test.d.ts +0 -2
  10. package/dist/__tests__/api-keys.test.d.ts.map +0 -1
  11. package/dist/__tests__/api-keys.test.js +0 -240
  12. package/dist/__tests__/api-keys.test.js.map +0 -1
  13. package/dist/__tests__/auth.test.d.ts +0 -2
  14. package/dist/__tests__/auth.test.d.ts.map +0 -1
  15. package/dist/__tests__/auth.test.js +0 -229
  16. package/dist/__tests__/auth.test.js.map +0 -1
  17. package/dist/__tests__/config.test.d.ts +0 -2
  18. package/dist/__tests__/config.test.d.ts.map +0 -1
  19. package/dist/__tests__/config.test.js +0 -256
  20. package/dist/__tests__/config.test.js.map +0 -1
  21. package/dist/__tests__/configdiff-roles-scope.test.d.ts +0 -2
  22. package/dist/__tests__/configdiff-roles-scope.test.d.ts.map +0 -1
  23. package/dist/__tests__/configdiff-roles-scope.test.js +0 -246
  24. package/dist/__tests__/configdiff-roles-scope.test.js.map +0 -1
  25. package/dist/__tests__/default-deny.test.d.ts +0 -2
  26. package/dist/__tests__/default-deny.test.d.ts.map +0 -1
  27. package/dist/__tests__/default-deny.test.js +0 -92
  28. package/dist/__tests__/default-deny.test.js.map +0 -1
  29. package/dist/__tests__/tenant-create-scope.test.d.ts +0 -2
  30. package/dist/__tests__/tenant-create-scope.test.d.ts.map +0 -1
  31. package/dist/__tests__/tenant-create-scope.test.js +0 -129
  32. package/dist/__tests__/tenant-create-scope.test.js.map +0 -1
  33. package/dist/__tests__/tenant-move-scope.test.d.ts +0 -2
  34. package/dist/__tests__/tenant-move-scope.test.d.ts.map +0 -1
  35. package/dist/__tests__/tenant-move-scope.test.js +0 -113
  36. package/dist/__tests__/tenant-move-scope.test.js.map +0 -1
  37. package/dist/__tests__/tenants.test.d.ts +0 -2
  38. package/dist/__tests__/tenants.test.d.ts.map +0 -1
  39. package/dist/__tests__/tenants.test.js +0 -214
  40. package/dist/__tests__/tenants.test.js.map +0 -1
  41. package/dist/__tests__/test-helpers.d.ts +0 -71
  42. package/dist/__tests__/test-helpers.d.ts.map +0 -1
  43. package/dist/__tests__/test-helpers.js +0 -189
  44. package/dist/__tests__/test-helpers.js.map +0 -1
@@ -1,92 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const vitest_1 = require("vitest");
7
- const fastify_1 = __importDefault(require("fastify"));
8
- const auth_js_1 = require("../middleware/auth.js");
9
- const authorize_js_1 = require("../middleware/authorize.js");
10
- const tenant_scope_js_1 = require("../middleware/tenant-scope.js");
11
- const error_handler_js_1 = require("../middleware/error-handler.js");
12
- const test_helpers_js_1 = require("./test-helpers.js");
13
- /**
14
- * Control-plane authorization must be default-deny: a route that does not
15
- * declare a tenant scope is refused, so a route added without a guard fails
16
- * closed instead of silently serving data.
17
- */
18
- const SCOPED_TENANT = "11111111-1111-4111-8111-111111111111";
19
- const OTHER_TENANT = "99999999-9999-4999-8999-999999999999";
20
- let stratum;
21
- (0, vitest_1.beforeEach)(() => {
22
- stratum = (0, test_helpers_js_1.createMockStratum)();
23
- stratum.validateApiKey.mockResolvedValue({
24
- key_id: "scoped-key",
25
- tenant_id: SCOPED_TENANT,
26
- scopes: ["read", "write", "admin"],
27
- rate_limit_max: null,
28
- rate_limit_window: null,
29
- });
30
- // Both tenants are roots (empty ancestry), so neither is a descendant of the
31
- // other: a scoped key may reach only its own.
32
- stratum.getTenant.mockImplementation(async (id) => ({ id, ancestry_path: "", status: "active" }));
33
- });
34
- /**
35
- * Build an app wired exactly like the production control plane: authenticate,
36
- * authorize by scope, then enforce tenant scope. The caller registers routes.
37
- */
38
- async function buildApp(register) {
39
- const app = (0, fastify_1.default)({ logger: false });
40
- app.addHook("preHandler", (0, auth_js_1.createAuthMiddleware)(stratum));
41
- app.addHook("preHandler", (0, authorize_js_1.createAuthorizeMiddleware)());
42
- app.addHook("preHandler", (0, tenant_scope_js_1.createTenantScopeEnforcer)(stratum));
43
- app.setErrorHandler(error_handler_js_1.errorHandler);
44
- await register(app);
45
- await app.ready();
46
- return app;
47
- }
48
- (0, vitest_1.describe)("default-deny authorization", () => {
49
- (0, vitest_1.it)("denies a route that declares no tenant scope", async () => {
50
- const app = await buildApp((app) => {
51
- // A control-plane route that ships without any tenant-scope declaration.
52
- app.get("/api/v1/unguarded/:id", async (_req, reply) => reply.status(200).send({ reached: true }));
53
- });
54
- const res = await app.inject({
55
- method: "GET",
56
- url: `/api/v1/unguarded/${OTHER_TENANT}`,
57
- headers: (0, test_helpers_js_1.authHeaders)(),
58
- });
59
- (0, vitest_1.expect)(res.statusCode).toBe(403);
60
- });
61
- (0, vitest_1.it)("admits a route explicitly declared as global", async () => {
62
- const app = await buildApp((app) => {
63
- (0, tenant_scope_js_1.declareTenantScope)(app, "global");
64
- app.get("/api/v1/ops", async (_req, reply) => reply.status(200).send({ ok: true }));
65
- });
66
- const res = await app.inject({
67
- method: "GET",
68
- url: "/api/v1/ops",
69
- headers: (0, test_helpers_js_1.authHeaders)(),
70
- });
71
- (0, vitest_1.expect)(res.statusCode).toBe(200);
72
- });
73
- (0, vitest_1.it)("enforces a declared extractor: own tenant allowed, others denied", async () => {
74
- const app = await buildApp((app) => {
75
- (0, tenant_scope_js_1.declareTenantScope)(app, tenant_scope_js_1.fromParamId);
76
- app.get("/api/v1/tenants/:id/thing", async (_req, reply) => reply.status(200).send({ ok: true }));
77
- });
78
- const own = await app.inject({
79
- method: "GET",
80
- url: `/api/v1/tenants/${SCOPED_TENANT}/thing`,
81
- headers: (0, test_helpers_js_1.authHeaders)(),
82
- });
83
- const other = await app.inject({
84
- method: "GET",
85
- url: `/api/v1/tenants/${OTHER_TENANT}/thing`,
86
- headers: (0, test_helpers_js_1.authHeaders)(),
87
- });
88
- (0, vitest_1.expect)(own.statusCode).toBe(200);
89
- (0, vitest_1.expect)(other.statusCode).toBe(403);
90
- });
91
- });
92
- //# sourceMappingURL=default-deny.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"default-deny.test.js","sourceRoot":"","sources":["../../src/__tests__/default-deny.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=tenant-create-scope.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tenant-create-scope.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tenant-create-scope.test.ts"],"names":[],"mappings":""}
@@ -1,129 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const vitest_1 = require("vitest");
4
- const test_helpers_js_1 = require("./test-helpers.js");
5
- /**
6
- * Tenant creation must respect the caller's key scope.
7
- *
8
- * A tenant-scoped key may only create tenants WITHIN its own subtree: the new
9
- * tenant's parent must be the key's own tenant or a descendant of it. A scoped
10
- * key may not create a new root (a tenant with no parent). Only global
11
- * (operator) keys may create roots or create anywhere in the tree.
12
- */
13
- const OWNER = "11111111-1111-4111-8111-111111111111";
14
- const OWNER_CHILD = "22222222-2222-4222-8222-222222222222";
15
- const OUTSIDER = "99999999-9999-4999-8999-999999999999";
16
- // ancestry_path holds a tenant's ANCESTORS and excludes self, so a root is "".
17
- const TENANTS = {
18
- [OWNER]: { id: OWNER, ancestry_path: "", status: "active" },
19
- [OWNER_CHILD]: { id: OWNER_CHILD, ancestry_path: OWNER, status: "active" },
20
- [OUTSIDER]: { id: OUTSIDER, ancestry_path: "", status: "active" },
21
- };
22
- let app;
23
- let stratum;
24
- function useScopedKey() {
25
- stratum.validateApiKey.mockResolvedValue({
26
- key_id: "scoped-key",
27
- tenant_id: OWNER,
28
- scopes: ["read", "write", "admin"],
29
- rate_limit_max: null,
30
- rate_limit_window: null,
31
- });
32
- }
33
- function useGlobalKey() {
34
- stratum.validateApiKey.mockResolvedValue({
35
- key_id: "global-key",
36
- tenant_id: null,
37
- scopes: ["read", "write", "admin"],
38
- rate_limit_max: null,
39
- rate_limit_window: null,
40
- });
41
- }
42
- (0, vitest_1.beforeEach)(async () => {
43
- stratum = (0, test_helpers_js_1.createMockStratum)();
44
- stratum.getTenant.mockImplementation(async (id) => {
45
- const row = TENANTS[id];
46
- if (!row)
47
- throw new Error(`Tenant ${id} not found`);
48
- return row;
49
- });
50
- stratum.createTenant.mockResolvedValue({
51
- id: "created-id",
52
- slug: "newco",
53
- isolation_strategy: "SHARED_RLS",
54
- });
55
- stratum.batchCreateTenants.mockResolvedValue({
56
- created: [],
57
- });
58
- app = await (0, test_helpers_js_1.buildTestApp)(stratum);
59
- });
60
- async function create(body) {
61
- return app.inject({
62
- method: "POST",
63
- url: "/api/v1/tenants",
64
- headers: { ...(0, test_helpers_js_1.authHeaders)(), "content-type": "application/json" },
65
- payload: body,
66
- });
67
- }
68
- (0, vitest_1.describe)("scoped key create is confined to its own subtree", () => {
69
- (0, vitest_1.beforeEach)(useScopedKey);
70
- (0, vitest_1.it)("refuses creating a tenant under a parent outside the key subtree", async () => {
71
- const res = await create({ slug: "newco", name: "New Co", parent_id: OUTSIDER });
72
- (0, vitest_1.expect)(res.statusCode).toBe(403);
73
- (0, vitest_1.expect)(stratum.createTenant).not.toHaveBeenCalled();
74
- });
75
- (0, vitest_1.it)("refuses creating a new root tenant (no parent)", async () => {
76
- const res = await create({ slug: "newco", name: "New Co" });
77
- (0, vitest_1.expect)(res.statusCode).toBe(403);
78
- (0, vitest_1.expect)(stratum.createTenant).not.toHaveBeenCalled();
79
- });
80
- (0, vitest_1.it)("refuses an explicit null parent (root)", async () => {
81
- const res = await create({ slug: "newco", name: "New Co", parent_id: null });
82
- (0, vitest_1.expect)(res.statusCode).toBe(403);
83
- (0, vitest_1.expect)(stratum.createTenant).not.toHaveBeenCalled();
84
- });
85
- (0, vitest_1.it)("allows creating directly under the key's own tenant", async () => {
86
- const res = await create({ slug: "newco", name: "New Co", parent_id: OWNER });
87
- (0, vitest_1.expect)(res.statusCode).toBe(201);
88
- (0, vitest_1.expect)(stratum.createTenant).toHaveBeenCalled();
89
- });
90
- (0, vitest_1.it)("allows creating under a descendant of the key's tenant", async () => {
91
- const res = await create({ slug: "newco", name: "New Co", parent_id: OWNER_CHILD });
92
- (0, vitest_1.expect)(res.statusCode).toBe(201);
93
- (0, vitest_1.expect)(stratum.createTenant).toHaveBeenCalled();
94
- });
95
- (0, vitest_1.it)("refuses a batch create that targets a parent outside the subtree", async () => {
96
- const res = await app.inject({
97
- method: "POST",
98
- url: "/api/v1/tenants/batch",
99
- headers: { ...(0, test_helpers_js_1.authHeaders)(), "content-type": "application/json" },
100
- payload: { tenants: [{ slug: "newco", name: "New Co", parent_id: OUTSIDER }] },
101
- });
102
- (0, vitest_1.expect)(res.statusCode).toBe(403);
103
- (0, vitest_1.expect)(stratum.batchCreateTenants).not.toHaveBeenCalled();
104
- });
105
- (0, vitest_1.it)("refuses a batch create that includes a new root", async () => {
106
- const res = await app.inject({
107
- method: "POST",
108
- url: "/api/v1/tenants/batch",
109
- headers: { ...(0, test_helpers_js_1.authHeaders)(), "content-type": "application/json" },
110
- payload: { tenants: [{ slug: "newco", name: "New Co", parent_id: OWNER }, { slug: "rootco", name: "Root Co" }] },
111
- });
112
- (0, vitest_1.expect)(res.statusCode).toBe(403);
113
- (0, vitest_1.expect)(stratum.batchCreateTenants).not.toHaveBeenCalled();
114
- });
115
- });
116
- (0, vitest_1.describe)("global key create is unrestricted", () => {
117
- (0, vitest_1.beforeEach)(useGlobalKey);
118
- (0, vitest_1.it)("allows creating a new root tenant", async () => {
119
- const res = await create({ slug: "rootco", name: "Root Co" });
120
- (0, vitest_1.expect)(res.statusCode).toBe(201);
121
- (0, vitest_1.expect)(stratum.createTenant).toHaveBeenCalled();
122
- });
123
- (0, vitest_1.it)("allows creating anywhere in the tree", async () => {
124
- const res = await create({ slug: "newco", name: "New Co", parent_id: OUTSIDER });
125
- (0, vitest_1.expect)(res.statusCode).toBe(201);
126
- (0, vitest_1.expect)(stratum.createTenant).toHaveBeenCalled();
127
- });
128
- });
129
- //# sourceMappingURL=tenant-create-scope.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tenant-create-scope.test.js","sourceRoot":"","sources":["../../src/__tests__/tenant-create-scope.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=tenant-move-scope.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tenant-move-scope.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tenant-move-scope.test.ts"],"names":[],"mappings":""}
@@ -1,113 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const vitest_1 = require("vitest");
4
- const test_helpers_js_1 = require("./test-helpers.js");
5
- /**
6
- * Regression tests for a critical cross-tenant authorization flaw in the move route.
7
- *
8
- * The bug: createTenantScopeGuard was registered with the `fromParamId` extractor,
9
- * which reads only `:id`, the tenant being MOVED. Nothing authorized
10
- * `body.new_parent_id`, the move DESTINATION.
11
- *
12
- * A key scoped to its own tenant therefore passed the guard on the fast path
13
- * (`apiKey.tenant_id === targetTenantId`) and could graft itself under any tenant
14
- * in the system. Because Stratum resolves config by walking UP the ancestry path,
15
- * the attacker's tenant then inherits the victim's config, including `sensitive`
16
- * values, which are decrypted on resolve, plus the victim's delegated permissions.
17
- *
18
- * The library's moveTenant is not at fault: it locks both rows, rejects archived
19
- * tenants and detects cycles. Authorization is the control plane's job.
20
- */
21
- const ATTACKER = "11111111-1111-4111-8111-111111111111";
22
- const ATTACKER_CHILD = "22222222-2222-4222-8222-222222222222";
23
- const ATTACKER_GRANDCHILD = "33333333-3333-4333-8333-333333333333";
24
- const VICTIM = "99999999-9999-4999-8999-999999999999";
25
- // ancestry_path holds a tenant's ANCESTORS and excludes self, so a root is "".
26
- const TENANTS = {
27
- [ATTACKER]: { id: ATTACKER, ancestry_path: "", status: "active" },
28
- [ATTACKER_CHILD]: {
29
- id: ATTACKER_CHILD,
30
- ancestry_path: ATTACKER,
31
- status: "active",
32
- },
33
- [ATTACKER_GRANDCHILD]: {
34
- id: ATTACKER_GRANDCHILD,
35
- ancestry_path: `${ATTACKER}/${ATTACKER_CHILD}`,
36
- status: "active",
37
- },
38
- [VICTIM]: { id: VICTIM, ancestry_path: "", status: "active" },
39
- };
40
- let app;
41
- let stratum;
42
- (0, vitest_1.beforeEach)(async () => {
43
- stratum = (0, test_helpers_js_1.createMockStratum)();
44
- // A key scoped to the attacker's own tenant. Not a global key.
45
- stratum.validateApiKey.mockResolvedValue({
46
- key_id: "attacker-key",
47
- tenant_id: ATTACKER,
48
- scopes: ["read", "write", "admin"],
49
- rate_limit_max: null,
50
- rate_limit_window: null,
51
- });
52
- stratum.getTenant.mockImplementation(async (id) => {
53
- const row = TENANTS[id];
54
- if (!row)
55
- throw new Error(`Tenant ${id} not found`);
56
- return row;
57
- });
58
- stratum.moveTenant.mockImplementation(async (id) => TENANTS[id]);
59
- app = await (0, test_helpers_js_1.buildTestApp)(stratum);
60
- });
61
- async function move(id, newParentId) {
62
- return app.inject({
63
- method: "POST",
64
- url: `/api/v1/tenants/${id}/move`,
65
- headers: { ...(0, test_helpers_js_1.authHeaders)(), "content-type": "application/json" },
66
- payload: { new_parent_id: newParentId },
67
- });
68
- }
69
- (0, vitest_1.describe)("move route authorizes the destination parent, not just the moved tenant", () => {
70
- (0, vitest_1.it)("refuses grafting your own tenant under an unrelated tenant", async () => {
71
- const res = await move(ATTACKER, VICTIM);
72
- (0, vitest_1.expect)(res.statusCode).toBe(403);
73
- (0, vitest_1.expect)(stratum.moveTenant).not.toHaveBeenCalled();
74
- });
75
- (0, vitest_1.it)("refuses grafting a tenant you do own under a tenant you do not", async () => {
76
- const res = await move(ATTACKER_CHILD, VICTIM);
77
- (0, vitest_1.expect)(res.statusCode).toBe(403);
78
- (0, vitest_1.expect)(stratum.moveTenant).not.toHaveBeenCalled();
79
- });
80
- (0, vitest_1.it)("allows reorganizing entirely inside your own subtree", async () => {
81
- const res = await move(ATTACKER_GRANDCHILD, ATTACKER_CHILD);
82
- (0, vitest_1.expect)(res.statusCode).toBe(200);
83
- (0, vitest_1.expect)(stratum.moveTenant).toHaveBeenCalledWith(ATTACKER_GRANDCHILD, ATTACKER_CHILD, vitest_1.expect.anything());
84
- });
85
- (0, vitest_1.it)("allows moving a descendant up to your own root", async () => {
86
- const res = await move(ATTACKER_GRANDCHILD, ATTACKER);
87
- (0, vitest_1.expect)(res.statusCode).toBe(200);
88
- (0, vitest_1.expect)(stratum.moveTenant).toHaveBeenCalled();
89
- });
90
- (0, vitest_1.it)("fails closed when the destination tenant cannot be loaded", async () => {
91
- const res = await move(ATTACKER, "44444444-4444-4444-8444-444444444444");
92
- (0, vitest_1.expect)(res.statusCode).toBe(403);
93
- (0, vitest_1.expect)(stratum.moveTenant).not.toHaveBeenCalled();
94
- });
95
- });
96
- (0, vitest_1.describe)("global keys are unaffected", () => {
97
- (0, vitest_1.beforeEach)(async () => {
98
- stratum.validateApiKey.mockResolvedValue({
99
- key_id: "global-key",
100
- tenant_id: null,
101
- scopes: ["read", "write", "admin"],
102
- rate_limit_max: null,
103
- rate_limit_window: null,
104
- });
105
- app = await (0, test_helpers_js_1.buildTestApp)(stratum);
106
- });
107
- (0, vitest_1.it)("still permits an operator key to move a tenant anywhere", async () => {
108
- const res = await move(ATTACKER, VICTIM);
109
- (0, vitest_1.expect)(res.statusCode).toBe(200);
110
- (0, vitest_1.expect)(stratum.moveTenant).toHaveBeenCalled();
111
- });
112
- });
113
- //# sourceMappingURL=tenant-move-scope.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tenant-move-scope.test.js","sourceRoot":"","sources":["../../src/__tests__/tenant-move-scope.test.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=tenants.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tenants.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tenants.test.ts"],"names":[],"mappings":""}
@@ -1,214 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const vitest_1 = require("vitest");
4
- const test_helpers_js_1 = require("./test-helpers.js");
5
- (0, vitest_1.describe)("Tenant Routes", () => {
6
- let app;
7
- let stratum;
8
- (0, vitest_1.beforeEach)(async () => {
9
- stratum = (0, test_helpers_js_1.createMockStratum)();
10
- (0, test_helpers_js_1.setupAdminApiKey)(stratum);
11
- app = await (0, test_helpers_js_1.buildTestApp)(stratum);
12
- });
13
- (0, vitest_1.afterEach)(async () => {
14
- await app.close();
15
- });
16
- // ── POST /api/v1/tenants ────────────────────────────────────────────
17
- (0, vitest_1.describe)("POST /api/v1/tenants", () => {
18
- (0, vitest_1.it)("creates a tenant and returns 201", async () => {
19
- const input = { slug: "acme_corp", name: "Acme Corp" };
20
- stratum.createTenant.mockResolvedValue(test_helpers_js_1.SAMPLE_TENANT);
21
- const response = await app.inject({
22
- method: "POST",
23
- url: "/api/v1/tenants",
24
- headers: (0, test_helpers_js_1.authHeaders)(),
25
- payload: input,
26
- });
27
- (0, vitest_1.expect)(response.statusCode).toBe(201);
28
- const body = response.json();
29
- (0, vitest_1.expect)(body.id).toBe(test_helpers_js_1.SAMPLE_TENANT.id);
30
- (0, vitest_1.expect)(body.slug).toBe("acme_corp");
31
- (0, vitest_1.expect)(stratum.createTenant).toHaveBeenCalledOnce();
32
- (0, vitest_1.expect)(stratum.createTenant.mock.calls[0][0]).toMatchObject({ slug: "acme_corp" });
33
- });
34
- (0, vitest_1.it)("returns 400 for invalid input (missing slug)", async () => {
35
- const response = await app.inject({
36
- method: "POST",
37
- url: "/api/v1/tenants",
38
- headers: (0, test_helpers_js_1.authHeaders)(),
39
- payload: { name: "No Slug" },
40
- });
41
- // Zod validation triggers 400
42
- (0, vitest_1.expect)(response.statusCode).toBe(400);
43
- const body = response.json();
44
- (0, vitest_1.expect)(body.error.code).toBe("VALIDATION_ERROR");
45
- });
46
- });
47
- // ── POST /api/v1/tenants/batch ──────────────────────────────────────
48
- (0, vitest_1.describe)("POST /api/v1/tenants/batch", () => {
49
- (0, vitest_1.it)("batch creates tenants and returns 201", async () => {
50
- const batchResult = {
51
- created: [test_helpers_js_1.SAMPLE_TENANT, test_helpers_js_1.SAMPLE_CHILD_TENANT],
52
- errors: [],
53
- };
54
- stratum.batchCreateTenants.mockResolvedValue(batchResult);
55
- const response = await app.inject({
56
- method: "POST",
57
- url: "/api/v1/tenants/batch",
58
- headers: (0, test_helpers_js_1.authHeaders)(),
59
- payload: {
60
- tenants: [
61
- { slug: "acme_corp", name: "Acme Corp" },
62
- { slug: "acme_division", name: "Acme Division", parent_id: test_helpers_js_1.SAMPLE_TENANT.id },
63
- ],
64
- },
65
- });
66
- (0, vitest_1.expect)(response.statusCode).toBe(201);
67
- const body = response.json();
68
- (0, vitest_1.expect)(body.created).toHaveLength(2);
69
- (0, vitest_1.expect)(stratum.batchCreateTenants).toHaveBeenCalledOnce();
70
- });
71
- (0, vitest_1.it)("returns 400 when tenants array is empty", async () => {
72
- const response = await app.inject({
73
- method: "POST",
74
- url: "/api/v1/tenants/batch",
75
- headers: (0, test_helpers_js_1.authHeaders)(),
76
- payload: { tenants: [] },
77
- });
78
- (0, vitest_1.expect)(response.statusCode).toBe(400);
79
- (0, vitest_1.expect)(response.json().error.code).toBe("VALIDATION_ERROR");
80
- });
81
- (0, vitest_1.it)("returns 400 when tenants field is missing", async () => {
82
- const response = await app.inject({
83
- method: "POST",
84
- url: "/api/v1/tenants/batch",
85
- headers: (0, test_helpers_js_1.authHeaders)(),
86
- payload: {},
87
- });
88
- (0, vitest_1.expect)(response.statusCode).toBe(400);
89
- });
90
- });
91
- // ── GET /api/v1/tenants/:id ─────────────────────────────────────────
92
- (0, vitest_1.describe)("GET /api/v1/tenants/:id", () => {
93
- (0, vitest_1.it)("returns the tenant", async () => {
94
- stratum.getTenant.mockResolvedValue(test_helpers_js_1.SAMPLE_TENANT);
95
- const response = await app.inject({
96
- method: "GET",
97
- url: `/api/v1/tenants/${test_helpers_js_1.SAMPLE_TENANT.id}`,
98
- headers: (0, test_helpers_js_1.authHeaders)(),
99
- });
100
- (0, vitest_1.expect)(response.statusCode).toBe(200);
101
- const body = response.json();
102
- (0, vitest_1.expect)(body.id).toBe(test_helpers_js_1.SAMPLE_TENANT.id);
103
- (0, vitest_1.expect)(body.slug).toBe("acme_corp");
104
- });
105
- (0, vitest_1.it)("returns 404 when tenant does not exist", async () => {
106
- const { StratumError, ErrorCode } = await import("@stratum-hq/core");
107
- stratum.getTenant.mockRejectedValue(new StratumError(ErrorCode.TENANT_NOT_FOUND, "Tenant not found", 404));
108
- const response = await app.inject({
109
- method: "GET",
110
- url: "/api/v1/tenants/nonexistent-id",
111
- headers: (0, test_helpers_js_1.authHeaders)(),
112
- });
113
- (0, vitest_1.expect)(response.statusCode).toBe(404);
114
- const body = response.json();
115
- (0, vitest_1.expect)(body.error.code).toBe("TENANT_NOT_FOUND");
116
- });
117
- });
118
- // ── PATCH /api/v1/tenants/:id ───────────────────────────────────────
119
- (0, vitest_1.describe)("PATCH /api/v1/tenants/:id", () => {
120
- (0, vitest_1.it)("updates a tenant and returns 200", async () => {
121
- const updatedTenant = { ...test_helpers_js_1.SAMPLE_TENANT, name: "Acme Corp Updated" };
122
- stratum.updateTenant.mockResolvedValue(updatedTenant);
123
- const response = await app.inject({
124
- method: "PATCH",
125
- url: `/api/v1/tenants/${test_helpers_js_1.SAMPLE_TENANT.id}`,
126
- headers: (0, test_helpers_js_1.authHeaders)(),
127
- payload: { name: "Acme Corp Updated" },
128
- });
129
- (0, vitest_1.expect)(response.statusCode).toBe(200);
130
- (0, vitest_1.expect)(response.json().name).toBe("Acme Corp Updated");
131
- (0, vitest_1.expect)(stratum.updateTenant).toHaveBeenCalledOnce();
132
- });
133
- });
134
- // ── DELETE /api/v1/tenants/:id ──────────────────────────────────────
135
- (0, vitest_1.describe)("DELETE /api/v1/tenants/:id", () => {
136
- (0, vitest_1.it)("soft deletes and returns 204", async () => {
137
- stratum.deleteTenant.mockResolvedValue(undefined);
138
- const response = await app.inject({
139
- method: "DELETE",
140
- url: `/api/v1/tenants/${test_helpers_js_1.SAMPLE_TENANT.id}`,
141
- headers: (0, test_helpers_js_1.authHeaders)(),
142
- });
143
- (0, vitest_1.expect)(response.statusCode).toBe(204);
144
- (0, vitest_1.expect)(response.body).toBe("");
145
- (0, vitest_1.expect)(stratum.deleteTenant).toHaveBeenCalledOnce();
146
- (0, vitest_1.expect)(stratum.deleteTenant.mock.calls[0][0]).toBe(test_helpers_js_1.SAMPLE_TENANT.id);
147
- });
148
- });
149
- // ── GET /api/v1/tenants/:id/descendants ─────────────────────────────
150
- (0, vitest_1.describe)("GET /api/v1/tenants/:id/descendants", () => {
151
- (0, vitest_1.it)("returns the tenant subtree", async () => {
152
- const descendants = [test_helpers_js_1.SAMPLE_CHILD_TENANT];
153
- stratum.getDescendants.mockResolvedValue(descendants);
154
- const response = await app.inject({
155
- method: "GET",
156
- url: `/api/v1/tenants/${test_helpers_js_1.SAMPLE_TENANT.id}/descendants`,
157
- headers: (0, test_helpers_js_1.authHeaders)(),
158
- });
159
- (0, vitest_1.expect)(response.statusCode).toBe(200);
160
- const body = response.json();
161
- (0, vitest_1.expect)(body).toHaveLength(1);
162
- (0, vitest_1.expect)(body[0].id).toBe(test_helpers_js_1.SAMPLE_CHILD_TENANT.id);
163
- (0, vitest_1.expect)(stratum.getDescendants).toHaveBeenCalledWith(test_helpers_js_1.SAMPLE_TENANT.id);
164
- });
165
- });
166
- // ── Authentication required ─────────────────────────────────────────
167
- (0, vitest_1.describe)("Authentication required", () => {
168
- (0, vitest_1.it)("returns 401 without an API key or Bearer token", async () => {
169
- const response = await app.inject({
170
- method: "GET",
171
- url: `/api/v1/tenants/${test_helpers_js_1.SAMPLE_TENANT.id}`,
172
- // No auth headers
173
- });
174
- (0, vitest_1.expect)(response.statusCode).toBe(401);
175
- const body = response.json();
176
- (0, vitest_1.expect)(body.error.code).toBe("UNAUTHORIZED");
177
- });
178
- (0, vitest_1.it)("returns 401 with an invalid API key", async () => {
179
- stratum.validateApiKey.mockResolvedValue(null);
180
- const response = await app.inject({
181
- method: "GET",
182
- url: `/api/v1/tenants/${test_helpers_js_1.SAMPLE_TENANT.id}`,
183
- headers: { "x-api-key": "sk_test_invalid" },
184
- });
185
- (0, vitest_1.expect)(response.statusCode).toBe(401);
186
- });
187
- });
188
- // ── Authorization ───────────────────────────────────────────────────
189
- (0, vitest_1.describe)("Authorization", () => {
190
- (0, vitest_1.it)("read-scope key cannot POST (write endpoint)", async () => {
191
- (0, test_helpers_js_1.setupReadOnlyApiKey)(stratum);
192
- const response = await app.inject({
193
- method: "POST",
194
- url: "/api/v1/tenants",
195
- headers: (0, test_helpers_js_1.authHeaders)(),
196
- payload: { slug: "test_tenant", name: "Test" },
197
- });
198
- (0, vitest_1.expect)(response.statusCode).toBe(403);
199
- const body = response.json();
200
- (0, vitest_1.expect)(body.error.code).toBe("FORBIDDEN");
201
- });
202
- (0, vitest_1.it)("read-scope key can GET (read endpoint)", async () => {
203
- (0, test_helpers_js_1.setupReadOnlyApiKey)(stratum);
204
- stratum.getTenant.mockResolvedValue(test_helpers_js_1.SAMPLE_TENANT);
205
- const response = await app.inject({
206
- method: "GET",
207
- url: `/api/v1/tenants/${test_helpers_js_1.SAMPLE_TENANT.id}`,
208
- headers: (0, test_helpers_js_1.authHeaders)(),
209
- });
210
- (0, vitest_1.expect)(response.statusCode).toBe(200);
211
- });
212
- });
213
- });
214
- //# sourceMappingURL=tenants.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tenants.test.js","sourceRoot":"","sources":["../../src/__tests__/tenants.test.ts"],"names":[],"mappings":";;AAAA,mCAAqE;AAErE,uDAQ2B;AAG3B,IAAA,iBAAQ,EAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,IAAI,GAAoB,CAAC;IACzB,IAAI,OAAgB,CAAC;IAErB,IAAA,mBAAU,EAAC,KAAK,IAAI,EAAE;QACpB,OAAO,GAAG,IAAA,mCAAiB,GAAE,CAAC;QAC9B,IAAA,kCAAgB,EAAC,OAAO,CAAC,CAAC;QAC1B,GAAG,GAAG,MAAM,IAAA,8BAAY,EAAC,OAAO,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,IAAA,kBAAS,EAAC,KAAK,IAAI,EAAE;QACnB,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,uEAAuE;IAEvE,IAAA,iBAAQ,EAAC,sBAAsB,EAAE,GAAG,EAAE;QACpC,IAAA,WAAE,EAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;YAChD,MAAM,KAAK,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;YACtD,OAAO,CAAC,YAAoB,CAAC,iBAAiB,CAAC,+BAAa,CAAC,CAAC;YAE/D,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,MAAM;gBACd,GAAG,EAAE,iBAAiB;gBACtB,OAAO,EAAE,IAAA,6BAAW,GAAE;gBACtB,OAAO,EAAE,KAAK;aACf,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAA,eAAM,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,+BAAa,CAAC,EAAE,CAAC,CAAC;YACvC,IAAA,eAAM,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACpC,IAAA,eAAM,EAAC,OAAO,CAAC,YAAY,CAAC,CAAC,oBAAoB,EAAE,CAAC;YACpD,IAAA,eAAM,EAAE,OAAO,CAAC,YAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC;QAC9F,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,8CAA8C,EAAE,KAAK,IAAI,EAAE;YAC5D,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,MAAM;gBACd,GAAG,EAAE,iBAAiB;gBACtB,OAAO,EAAE,IAAA,6BAAW,GAAE;gBACtB,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;aAC7B,CAAC,CAAC;YAEH,8BAA8B;YAC9B,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAA,eAAM,EAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,uEAAuE;IAEvE,IAAA,iBAAQ,EAAC,4BAA4B,EAAE,GAAG,EAAE;QAC1C,IAAA,WAAE,EAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,MAAM,WAAW,GAAG;gBAClB,OAAO,EAAE,CAAC,+BAAa,EAAE,qCAAmB,CAAC;gBAC7C,MAAM,EAAE,EAAE;aACX,CAAC;YACD,OAAO,CAAC,kBAA0B,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;YAEnE,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,MAAM;gBACd,GAAG,EAAE,uBAAuB;gBAC5B,OAAO,EAAE,IAAA,6BAAW,GAAE;gBACtB,OAAO,EAAE;oBACP,OAAO,EAAE;wBACP,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,EAAE;wBACxC,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,+BAAa,CAAC,EAAE,EAAE;qBAC9E;iBACF;aACF,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAA,eAAM,EAAC,IAAI,CAAC,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YACrC,IAAA,eAAM,EAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,oBAAoB,EAAE,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;YACvD,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,MAAM;gBACd,GAAG,EAAE,uBAAuB;gBAC5B,OAAO,EAAE,IAAA,6BAAW,GAAE;gBACtB,OAAO,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;aACzB,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,IAAA,eAAM,EAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;YACzD,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,MAAM;gBACd,GAAG,EAAE,uBAAuB;gBAC5B,OAAO,EAAE,IAAA,6BAAW,GAAE;gBACtB,OAAO,EAAE,EAAE;aACZ,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,uEAAuE;IAEvE,IAAA,iBAAQ,EAAC,yBAAyB,EAAE,GAAG,EAAE;QACvC,IAAA,WAAE,EAAC,oBAAoB,EAAE,KAAK,IAAI,EAAE;YACjC,OAAO,CAAC,SAAiB,CAAC,iBAAiB,CAAC,+BAAa,CAAC,CAAC;YAE5D,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,KAAK;gBACb,GAAG,EAAE,mBAAmB,+BAAa,CAAC,EAAE,EAAE;gBAC1C,OAAO,EAAE,IAAA,6BAAW,GAAE;aACvB,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAA,eAAM,EAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,+BAAa,CAAC,EAAE,CAAC,CAAC;YACvC,IAAA,eAAM,EAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YACtD,MAAM,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAC;YACpE,OAAO,CAAC,SAAiB,CAAC,iBAAiB,CAC1C,IAAI,YAAY,CAAC,SAAS,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,GAAG,CAAC,CACtE,CAAC;YAEF,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,KAAK;gBACb,GAAG,EAAE,gCAAgC;gBACrC,OAAO,EAAE,IAAA,6BAAW,GAAE;aACvB,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAA,eAAM,EAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QACnD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,uEAAuE;IAEvE,IAAA,iBAAQ,EAAC,2BAA2B,EAAE,GAAG,EAAE;QACzC,IAAA,WAAE,EAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;YAChD,MAAM,aAAa,GAAG,EAAE,GAAG,+BAAa,EAAE,IAAI,EAAE,mBAAmB,EAAE,CAAC;YACrE,OAAO,CAAC,YAAoB,CAAC,iBAAiB,CAAC,aAAa,CAAC,CAAC;YAE/D,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,OAAO;gBACf,GAAG,EAAE,mBAAmB,+BAAa,CAAC,EAAE,EAAE;gBAC1C,OAAO,EAAE,IAAA,6BAAW,GAAE;gBACtB,OAAO,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE;aACvC,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,IAAA,eAAM,EAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;YACvD,IAAA,eAAM,EAAC,OAAO,CAAC,YAAY,CAAC,CAAC,oBAAoB,EAAE,CAAC;QACtD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,uEAAuE;IAEvE,IAAA,iBAAQ,EAAC,4BAA4B,EAAE,GAAG,EAAE;QAC1C,IAAA,WAAE,EAAC,8BAA8B,EAAE,KAAK,IAAI,EAAE;YAC3C,OAAO,CAAC,YAAoB,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;YAE3D,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,QAAQ;gBAChB,GAAG,EAAE,mBAAmB,+BAAa,CAAC,EAAE,EAAE;gBAC1C,OAAO,EAAE,IAAA,6BAAW,GAAE;aACvB,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,IAAA,eAAM,EAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/B,IAAA,eAAM,EAAC,OAAO,CAAC,YAAY,CAAC,CAAC,oBAAoB,EAAE,CAAC;YACpD,IAAA,eAAM,EAAE,OAAO,CAAC,YAAoB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,+BAAa,CAAC,EAAE,CAAC,CAAC;QAChF,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,uEAAuE;IAEvE,IAAA,iBAAQ,EAAC,qCAAqC,EAAE,GAAG,EAAE;QACnD,IAAA,WAAE,EAAC,4BAA4B,EAAE,KAAK,IAAI,EAAE;YAC1C,MAAM,WAAW,GAAG,CAAC,qCAAmB,CAAC,CAAC;YACzC,OAAO,CAAC,cAAsB,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;YAE/D,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,KAAK;gBACb,GAAG,EAAE,mBAAmB,+BAAa,CAAC,EAAE,cAAc;gBACtD,OAAO,EAAE,IAAA,6BAAW,GAAE;aACvB,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAA,eAAM,EAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;YAC7B,IAAA,eAAM,EAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,qCAAmB,CAAC,EAAE,CAAC,CAAC;YAChD,IAAA,eAAM,EAAC,OAAO,CAAC,cAAc,CAAC,CAAC,oBAAoB,CAAC,+BAAa,CAAC,EAAE,CAAC,CAAC;QACxE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,uEAAuE;IAEvE,IAAA,iBAAQ,EAAC,yBAAyB,EAAE,GAAG,EAAE;QACvC,IAAA,WAAE,EAAC,gDAAgD,EAAE,KAAK,IAAI,EAAE;YAC9D,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,KAAK;gBACb,GAAG,EAAE,mBAAmB,+BAAa,CAAC,EAAE,EAAE;gBAC1C,kBAAkB;aACnB,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAA,eAAM,EAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC/C,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;YAClD,OAAO,CAAC,cAAsB,CAAC,iBAAiB,CAAC,IAAI,CAAC,CAAC;YAExD,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,KAAK;gBACb,GAAG,EAAE,mBAAmB,+BAAa,CAAC,EAAE,EAAE;gBAC1C,OAAO,EAAE,EAAE,WAAW,EAAE,iBAAiB,EAAE;aAC5C,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,uEAAuE;IAEvE,IAAA,iBAAQ,EAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,IAAA,WAAE,EAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;YAC3D,IAAA,qCAAmB,EAAC,OAAO,CAAC,CAAC;YAE7B,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,MAAM;gBACd,GAAG,EAAE,iBAAiB;gBACtB,OAAO,EAAE,IAAA,6BAAW,GAAE;gBACtB,OAAO,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE;aAC/C,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;YAC7B,IAAA,eAAM,EAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,IAAA,WAAE,EAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YACtD,IAAA,qCAAmB,EAAC,OAAO,CAAC,CAAC;YAC5B,OAAO,CAAC,SAAiB,CAAC,iBAAiB,CAAC,+BAAa,CAAC,CAAC;YAE5D,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC;gBAChC,MAAM,EAAE,KAAK;gBACb,GAAG,EAAE,mBAAmB,+BAAa,CAAC,EAAE,EAAE;gBAC1C,OAAO,EAAE,IAAA,6BAAW,GAAE;aACvB,CAAC,CAAC;YAEH,IAAA,eAAM,EAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}