@stratum-hq/control-plane 0.1.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 (133) hide show
  1. package/dist/__tests__/api-keys.test.d.ts +2 -0
  2. package/dist/__tests__/api-keys.test.d.ts.map +1 -0
  3. package/dist/__tests__/api-keys.test.js +240 -0
  4. package/dist/__tests__/api-keys.test.js.map +1 -0
  5. package/dist/__tests__/auth.test.d.ts +2 -0
  6. package/dist/__tests__/auth.test.d.ts.map +1 -0
  7. package/dist/__tests__/auth.test.js +227 -0
  8. package/dist/__tests__/auth.test.js.map +1 -0
  9. package/dist/__tests__/config.test.d.ts +2 -0
  10. package/dist/__tests__/config.test.d.ts.map +1 -0
  11. package/dist/__tests__/config.test.js +201 -0
  12. package/dist/__tests__/config.test.js.map +1 -0
  13. package/dist/__tests__/tenants.test.d.ts +2 -0
  14. package/dist/__tests__/tenants.test.d.ts.map +1 -0
  15. package/dist/__tests__/tenants.test.js +214 -0
  16. package/dist/__tests__/tenants.test.js.map +1 -0
  17. package/dist/__tests__/test-helpers.d.ts +71 -0
  18. package/dist/__tests__/test-helpers.d.ts.map +1 -0
  19. package/dist/__tests__/test-helpers.js +172 -0
  20. package/dist/__tests__/test-helpers.js.map +1 -0
  21. package/dist/app.d.ts +3 -0
  22. package/dist/app.d.ts.map +1 -0
  23. package/dist/app.js +114 -0
  24. package/dist/app.js.map +1 -0
  25. package/dist/config.d.ts +11 -0
  26. package/dist/config.d.ts.map +1 -0
  27. package/dist/config.js +26 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/db/connection.d.ts +4 -0
  30. package/dist/db/connection.d.ts.map +1 -0
  31. package/dist/db/connection.js +30 -0
  32. package/dist/db/connection.js.map +1 -0
  33. package/dist/db/migrate.d.ts +3 -0
  34. package/dist/db/migrate.d.ts.map +1 -0
  35. package/dist/db/migrate.js +56 -0
  36. package/dist/db/migrate.js.map +1 -0
  37. package/dist/db/regional-pool-router.d.ts +9 -0
  38. package/dist/db/regional-pool-router.d.ts.map +1 -0
  39. package/dist/db/regional-pool-router.js +29 -0
  40. package/dist/db/regional-pool-router.js.map +1 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.d.ts.map +1 -0
  43. package/dist/index.js +36 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/middleware/auth.d.ts +21 -0
  46. package/dist/middleware/auth.d.ts.map +1 -0
  47. package/dist/middleware/auth.js +70 -0
  48. package/dist/middleware/auth.js.map +1 -0
  49. package/dist/middleware/authorize.d.ts +3 -0
  50. package/dist/middleware/authorize.d.ts.map +1 -0
  51. package/dist/middleware/authorize.js +54 -0
  52. package/dist/middleware/authorize.js.map +1 -0
  53. package/dist/middleware/error-handler.d.ts +3 -0
  54. package/dist/middleware/error-handler.d.ts.map +1 -0
  55. package/dist/middleware/error-handler.js +48 -0
  56. package/dist/middleware/error-handler.js.map +1 -0
  57. package/dist/middleware/per-key-rate-limit.d.ts +3 -0
  58. package/dist/middleware/per-key-rate-limit.d.ts.map +1 -0
  59. package/dist/middleware/per-key-rate-limit.js +62 -0
  60. package/dist/middleware/per-key-rate-limit.js.map +1 -0
  61. package/dist/middleware/rate-limit-redis.d.ts +16 -0
  62. package/dist/middleware/rate-limit-redis.d.ts.map +1 -0
  63. package/dist/middleware/rate-limit-redis.js +118 -0
  64. package/dist/middleware/rate-limit-redis.js.map +1 -0
  65. package/dist/middleware/telemetry.d.ts +14 -0
  66. package/dist/middleware/telemetry.d.ts.map +1 -0
  67. package/dist/middleware/telemetry.js +81 -0
  68. package/dist/middleware/telemetry.js.map +1 -0
  69. package/dist/middleware/tenant-scope.d.ts +20 -0
  70. package/dist/middleware/tenant-scope.d.ts.map +1 -0
  71. package/dist/middleware/tenant-scope.js +60 -0
  72. package/dist/middleware/tenant-scope.js.map +1 -0
  73. package/dist/openapi.d.ts +3 -0
  74. package/dist/openapi.d.ts.map +1 -0
  75. package/dist/openapi.js +44 -0
  76. package/dist/openapi.js.map +1 -0
  77. package/dist/redis.d.ts +16 -0
  78. package/dist/redis.d.ts.map +1 -0
  79. package/dist/redis.js +71 -0
  80. package/dist/redis.js.map +1 -0
  81. package/dist/routes/api-keys.d.ts +4 -0
  82. package/dist/routes/api-keys.d.ts.map +1 -0
  83. package/dist/routes/api-keys.js +78 -0
  84. package/dist/routes/api-keys.js.map +1 -0
  85. package/dist/routes/audit-logs.d.ts +6 -0
  86. package/dist/routes/audit-logs.d.ts.map +1 -0
  87. package/dist/routes/audit-logs.js +43 -0
  88. package/dist/routes/audit-logs.js.map +1 -0
  89. package/dist/routes/config-diff.d.ts +4 -0
  90. package/dist/routes/config-diff.d.ts.map +1 -0
  91. package/dist/routes/config-diff.js +33 -0
  92. package/dist/routes/config-diff.js.map +1 -0
  93. package/dist/routes/config.d.ts +4 -0
  94. package/dist/routes/config.d.ts.map +1 -0
  95. package/dist/routes/config.js +54 -0
  96. package/dist/routes/config.js.map +1 -0
  97. package/dist/routes/consent.d.ts +4 -0
  98. package/dist/routes/consent.d.ts.map +1 -0
  99. package/dist/routes/consent.js +45 -0
  100. package/dist/routes/consent.js.map +1 -0
  101. package/dist/routes/health.d.ts +9 -0
  102. package/dist/routes/health.d.ts.map +1 -0
  103. package/dist/routes/health.js +32 -0
  104. package/dist/routes/health.js.map +1 -0
  105. package/dist/routes/maintenance.d.ts +4 -0
  106. package/dist/routes/maintenance.d.ts.map +1 -0
  107. package/dist/routes/maintenance.js +35 -0
  108. package/dist/routes/maintenance.js.map +1 -0
  109. package/dist/routes/permissions.d.ts +4 -0
  110. package/dist/routes/permissions.d.ts.map +1 -0
  111. package/dist/routes/permissions.js +36 -0
  112. package/dist/routes/permissions.js.map +1 -0
  113. package/dist/routes/regions.d.ts +4 -0
  114. package/dist/routes/regions.d.ts.map +1 -0
  115. package/dist/routes/regions.js +38 -0
  116. package/dist/routes/regions.js.map +1 -0
  117. package/dist/routes/roles.d.ts +4 -0
  118. package/dist/routes/roles.d.ts.map +1 -0
  119. package/dist/routes/roles.js +95 -0
  120. package/dist/routes/roles.js.map +1 -0
  121. package/dist/routes/tenants.d.ts +4 -0
  122. package/dist/routes/tenants.d.ts.map +1 -0
  123. package/dist/routes/tenants.js +112 -0
  124. package/dist/routes/tenants.js.map +1 -0
  125. package/dist/routes/webhooks.d.ts +4 -0
  126. package/dist/routes/webhooks.d.ts.map +1 -0
  127. package/dist/routes/webhooks.js +116 -0
  128. package/dist/routes/webhooks.js.map +1 -0
  129. package/dist/services/isolation-service.d.ts +22 -0
  130. package/dist/services/isolation-service.d.ts.map +1 -0
  131. package/dist/services/isolation-service.js +94 -0
  132. package/dist/services/isolation-service.js.map +1 -0
  133. package/package.json +48 -0
@@ -0,0 +1,201 @@
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)("Config Routes", () => {
6
+ let app;
7
+ let stratum;
8
+ const tenantId = test_helpers_js_1.SAMPLE_TENANT.id;
9
+ (0, vitest_1.beforeEach)(async () => {
10
+ stratum = (0, test_helpers_js_1.createMockStratum)();
11
+ (0, test_helpers_js_1.setupAdminApiKey)(stratum);
12
+ app = await (0, test_helpers_js_1.buildTestApp)(stratum);
13
+ });
14
+ (0, vitest_1.afterEach)(async () => {
15
+ await app.close();
16
+ });
17
+ // ── GET /api/v1/tenants/:id/config ──────────────────────────────────
18
+ (0, vitest_1.describe)("GET /api/v1/tenants/:id/config", () => {
19
+ (0, vitest_1.it)("returns resolved config for the tenant", async () => {
20
+ const resolvedConfig = {
21
+ "feature.dark_mode": {
22
+ value: true,
23
+ source_tenant_id: tenantId,
24
+ locked: false,
25
+ sensitive: false,
26
+ },
27
+ "limits.max_users": {
28
+ value: 100,
29
+ source_tenant_id: tenantId,
30
+ locked: false,
31
+ sensitive: false,
32
+ },
33
+ };
34
+ stratum.resolveConfig.mockResolvedValue(resolvedConfig);
35
+ const response = await app.inject({
36
+ method: "GET",
37
+ url: `/api/v1/tenants/${tenantId}/config`,
38
+ headers: (0, test_helpers_js_1.authHeaders)(),
39
+ });
40
+ (0, vitest_1.expect)(response.statusCode).toBe(200);
41
+ const body = response.json();
42
+ (0, vitest_1.expect)(body["feature.dark_mode"].value).toBe(true);
43
+ (0, vitest_1.expect)(body["limits.max_users"].value).toBe(100);
44
+ (0, vitest_1.expect)(stratum.resolveConfig).toHaveBeenCalledWith(tenantId);
45
+ });
46
+ (0, vitest_1.it)("returns empty object when tenant has no config", async () => {
47
+ stratum.resolveConfig.mockResolvedValue({});
48
+ const response = await app.inject({
49
+ method: "GET",
50
+ url: `/api/v1/tenants/${tenantId}/config`,
51
+ headers: (0, test_helpers_js_1.authHeaders)(),
52
+ });
53
+ (0, vitest_1.expect)(response.statusCode).toBe(200);
54
+ (0, vitest_1.expect)(response.json()).toEqual({});
55
+ });
56
+ });
57
+ // ── PUT /api/v1/tenants/:id/config/:key ─────────────────────────────
58
+ (0, vitest_1.describe)("PUT /api/v1/tenants/:id/config/:key", () => {
59
+ (0, vitest_1.it)("sets a config value and returns the entry", async () => {
60
+ const configEntry = {
61
+ tenant_id: tenantId,
62
+ key: "feature.dark_mode",
63
+ value: true,
64
+ locked: false,
65
+ sensitive: false,
66
+ created_at: new Date().toISOString(),
67
+ updated_at: new Date().toISOString(),
68
+ };
69
+ stratum.setConfig.mockResolvedValue(configEntry);
70
+ const response = await app.inject({
71
+ method: "PUT",
72
+ url: `/api/v1/tenants/${tenantId}/config/feature.dark_mode`,
73
+ headers: (0, test_helpers_js_1.authHeaders)(),
74
+ payload: { value: true },
75
+ });
76
+ (0, vitest_1.expect)(response.statusCode).toBe(200);
77
+ const body = response.json();
78
+ (0, vitest_1.expect)(body.key).toBe("feature.dark_mode");
79
+ (0, vitest_1.expect)(body.value).toBe(true);
80
+ (0, vitest_1.expect)(stratum.setConfig).toHaveBeenCalledOnce();
81
+ });
82
+ (0, vitest_1.it)("sets a locked config value", async () => {
83
+ const configEntry = {
84
+ tenant_id: tenantId,
85
+ key: "branding.logo_url",
86
+ value: "https://example.com/logo.png",
87
+ locked: true,
88
+ sensitive: false,
89
+ created_at: new Date().toISOString(),
90
+ updated_at: new Date().toISOString(),
91
+ };
92
+ stratum.setConfig.mockResolvedValue(configEntry);
93
+ const response = await app.inject({
94
+ method: "PUT",
95
+ url: `/api/v1/tenants/${tenantId}/config/branding.logo_url`,
96
+ headers: (0, test_helpers_js_1.authHeaders)(),
97
+ payload: { value: "https://example.com/logo.png", locked: true },
98
+ });
99
+ (0, vitest_1.expect)(response.statusCode).toBe(200);
100
+ (0, vitest_1.expect)(response.json().locked).toBe(true);
101
+ });
102
+ });
103
+ // ── PUT /api/v1/tenants/:id/config/batch ────────────────────────────
104
+ (0, vitest_1.describe)("PUT /api/v1/tenants/:id/config/batch", () => {
105
+ (0, vitest_1.it)("batch sets config with partial success", async () => {
106
+ const batchResult = {
107
+ results: [
108
+ {
109
+ key: "feature.a",
110
+ status: "ok",
111
+ entry: {
112
+ tenant_id: tenantId,
113
+ key: "feature.a",
114
+ value: true,
115
+ locked: false,
116
+ sensitive: false,
117
+ },
118
+ },
119
+ {
120
+ key: "feature.b",
121
+ status: "error",
122
+ error: "CONFIG_LOCKED",
123
+ message: "Config 'feature.b' is locked by tenant parent-id and cannot be overridden",
124
+ },
125
+ ],
126
+ succeeded: 1,
127
+ failed: 1,
128
+ };
129
+ stratum.batchSetConfig.mockResolvedValue(batchResult);
130
+ const response = await app.inject({
131
+ method: "PUT",
132
+ url: `/api/v1/tenants/${tenantId}/config/batch`,
133
+ headers: (0, test_helpers_js_1.authHeaders)(),
134
+ payload: {
135
+ entries: [
136
+ { key: "feature.a", value: true },
137
+ { key: "feature.b", value: false },
138
+ ],
139
+ },
140
+ });
141
+ (0, vitest_1.expect)(response.statusCode).toBe(200);
142
+ const body = response.json();
143
+ (0, vitest_1.expect)(body.succeeded).toBe(1);
144
+ (0, vitest_1.expect)(body.failed).toBe(1);
145
+ (0, vitest_1.expect)(body.results).toHaveLength(2);
146
+ (0, vitest_1.expect)(stratum.batchSetConfig).toHaveBeenCalledOnce();
147
+ });
148
+ (0, vitest_1.it)("returns 400 for empty entries array", async () => {
149
+ const response = await app.inject({
150
+ method: "PUT",
151
+ url: `/api/v1/tenants/${tenantId}/config/batch`,
152
+ headers: (0, test_helpers_js_1.authHeaders)(),
153
+ payload: { entries: [] },
154
+ });
155
+ (0, vitest_1.expect)(response.statusCode).toBe(400);
156
+ (0, vitest_1.expect)(response.json().error.code).toBe("VALIDATION_ERROR");
157
+ });
158
+ (0, vitest_1.it)("returns 400 when entries field is missing", async () => {
159
+ const response = await app.inject({
160
+ method: "PUT",
161
+ url: `/api/v1/tenants/${tenantId}/config/batch`,
162
+ headers: (0, test_helpers_js_1.authHeaders)(),
163
+ payload: {},
164
+ });
165
+ (0, vitest_1.expect)(response.statusCode).toBe(400);
166
+ });
167
+ });
168
+ // ── DELETE /api/v1/tenants/:id/config/:key ──────────────────────────
169
+ (0, vitest_1.describe)("DELETE /api/v1/tenants/:id/config/:key", () => {
170
+ (0, vitest_1.it)("deletes a config override and returns 204", async () => {
171
+ stratum.deleteConfig.mockResolvedValue(undefined);
172
+ const response = await app.inject({
173
+ method: "DELETE",
174
+ url: `/api/v1/tenants/${tenantId}/config/feature.dark_mode`,
175
+ headers: (0, test_helpers_js_1.authHeaders)(),
176
+ });
177
+ (0, vitest_1.expect)(response.statusCode).toBe(204);
178
+ (0, vitest_1.expect)(response.body).toBe("");
179
+ (0, vitest_1.expect)(stratum.deleteConfig).toHaveBeenCalledOnce();
180
+ (0, vitest_1.expect)(stratum.deleteConfig.mock.calls[0][0]).toBe(tenantId);
181
+ (0, vitest_1.expect)(stratum.deleteConfig.mock.calls[0][1]).toBe("feature.dark_mode");
182
+ });
183
+ });
184
+ // ── Locked key error ────────────────────────────────────────────────
185
+ (0, vitest_1.describe)("Locked key error", () => {
186
+ (0, vitest_1.it)("returns 403 when trying to override a locked config key", async () => {
187
+ const { ConfigLockedError } = await import("@stratum-hq/core");
188
+ stratum.setConfig.mockRejectedValue(new ConfigLockedError("feature.locked_key", "parent-tenant-id"));
189
+ const response = await app.inject({
190
+ method: "PUT",
191
+ url: `/api/v1/tenants/${tenantId}/config/feature.locked_key`,
192
+ headers: (0, test_helpers_js_1.authHeaders)(),
193
+ payload: { value: "anything" },
194
+ });
195
+ (0, vitest_1.expect)(response.statusCode).toBe(403);
196
+ const body = response.json();
197
+ (0, vitest_1.expect)(body.error.code).toBe("CONFIG_LOCKED");
198
+ });
199
+ });
200
+ });
201
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../src/__tests__/config.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tenants.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenants.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tenants.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,214 @@
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: "t-uuid-1" },
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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenants.test.js","sourceRoot":"","sources":["../../src/__tests__/tenants.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ import { FastifyInstance } from "fastify";
2
+ import type { Stratum } from "@stratum-hq/lib";
3
+ export declare const TEST_JWT_SECRET = "dev-secret-change-in-production";
4
+ /**
5
+ * Create a mock Stratum instance with all methods stubbed via vi.fn().
6
+ * Tests override individual method implementations as needed.
7
+ */
8
+ export declare function createMockStratum(): Stratum;
9
+ /**
10
+ * Build a test Fastify app with routes registered and middleware wired up.
11
+ * Uses the provided mock Stratum instance.
12
+ *
13
+ * We skip OpenAPI/swagger, helmet, cors, and rate-limit plugins to keep tests fast
14
+ * and focused on the HTTP/route/middleware layer.
15
+ */
16
+ export declare function buildTestApp(stratum: Stratum): Promise<FastifyInstance>;
17
+ /**
18
+ * Helper: returns headers object with a valid API key authentication.
19
+ * The mock stratum.validateApiKey must be configured to accept this key.
20
+ */
21
+ export declare function authHeaders(apiKey?: string): Record<string, string>;
22
+ /**
23
+ * Helper: returns headers object with a valid JWT Bearer token.
24
+ */
25
+ export declare function jwtHeaders(payload?: Record<string, unknown>, secret?: string): Record<string, string>;
26
+ /**
27
+ * Configure the mock stratum to accept the default test API key
28
+ * with admin scopes (read + write + admin). This is the common case.
29
+ */
30
+ export declare function setupAdminApiKey(stratum: Stratum): void;
31
+ /**
32
+ * Configure the mock stratum to accept the default test API key
33
+ * with read-only scope.
34
+ */
35
+ export declare function setupReadOnlyApiKey(stratum: Stratum): void;
36
+ /**
37
+ * Configure the mock stratum to accept the default test API key
38
+ * with write scope (read + write but not admin).
39
+ */
40
+ export declare function setupWriteApiKey(stratum: Stratum): void;
41
+ /** A sample tenant object for test assertions */
42
+ export declare const SAMPLE_TENANT: {
43
+ id: string;
44
+ slug: string;
45
+ name: string;
46
+ parent_id: null;
47
+ ancestry_path: string;
48
+ depth: number;
49
+ metadata: {};
50
+ isolation_strategy: string;
51
+ region_id: null;
52
+ created_at: string;
53
+ updated_at: string;
54
+ deleted_at: null;
55
+ };
56
+ /** A second sample tenant (child) */
57
+ export declare const SAMPLE_CHILD_TENANT: {
58
+ id: string;
59
+ slug: string;
60
+ name: string;
61
+ parent_id: string;
62
+ ancestry_path: string;
63
+ depth: number;
64
+ metadata: {};
65
+ isolation_strategy: string;
66
+ region_id: null;
67
+ created_at: string;
68
+ updated_at: string;
69
+ deleted_at: null;
70
+ };
71
+ //# sourceMappingURL=test-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../src/__tests__/test-helpers.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAUnD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG/C,eAAO,MAAM,eAAe,oCAAoC,CAAC;AAEjE;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAmC3C;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,eAAe,CAAC,CAiB7E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,GAAE,MAA4B,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAExF;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,MAAM,GAAE,MAAwB,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWxB;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAQvD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAQ1D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAQvD;AAED,iDAAiD;AACjD,eAAO,MAAM,aAAa;;;;;;;;;;;;;CAazB,CAAC;AAEF,qCAAqC;AACrC,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;CAa/B,CAAC"}