@terreno/api 0.3.1 → 0.4.2

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 (42) hide show
  1. package/dist/api.js +9 -8
  2. package/dist/betterAuthSetup.js +1 -1
  3. package/dist/configuration.test.d.ts +1 -0
  4. package/dist/configuration.test.js +699 -0
  5. package/dist/configurationApp.d.ts +91 -0
  6. package/dist/configurationApp.js +407 -0
  7. package/dist/configurationPlugin.d.ts +102 -0
  8. package/dist/configurationPlugin.js +285 -0
  9. package/dist/configurationPlugin.test.d.ts +1 -0
  10. package/dist/configurationPlugin.test.js +509 -0
  11. package/dist/example.js +1 -1
  12. package/dist/expressServer.js +5 -1
  13. package/dist/githubAuth.js +2 -2
  14. package/dist/index.d.ts +5 -0
  15. package/dist/index.js +5 -0
  16. package/dist/openApiCompat.d.ts +23 -0
  17. package/dist/openApiCompat.js +198 -0
  18. package/dist/scriptRunner.d.ts +52 -0
  19. package/dist/scriptRunner.js +231 -0
  20. package/dist/secretProviders.d.ts +47 -0
  21. package/dist/secretProviders.js +214 -0
  22. package/dist/terrenoApp.d.ts +25 -0
  23. package/dist/terrenoApp.js +49 -2
  24. package/dist/tests.d.ts +27 -9
  25. package/dist/tests.js +10 -1
  26. package/package.json +13 -13
  27. package/src/api.ts +9 -8
  28. package/src/betterAuthSetup.ts +2 -2
  29. package/src/configuration.test.ts +398 -0
  30. package/src/configurationApp.ts +359 -0
  31. package/src/configurationPlugin.test.ts +299 -0
  32. package/src/configurationPlugin.ts +288 -0
  33. package/src/example.ts +1 -1
  34. package/src/expressServer.ts +6 -1
  35. package/src/githubAuth.ts +4 -4
  36. package/src/index.ts +5 -0
  37. package/src/openApiCompat.ts +147 -0
  38. package/src/permissions.ts +1 -1
  39. package/src/scriptRunner.ts +219 -0
  40. package/src/secretProviders.ts +109 -0
  41. package/src/terrenoApp.ts +44 -2
  42. package/src/tests.ts +12 -1
@@ -0,0 +1,699 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
36
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
37
+ return new (P || (P = Promise))(function (resolve, reject) {
38
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
39
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
40
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
41
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
42
+ });
43
+ };
44
+ var __generator = (this && this.__generator) || function (thisArg, body) {
45
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
46
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
47
+ function verb(n) { return function (v) { return step([n, v]); }; }
48
+ function step(op) {
49
+ if (f) throw new TypeError("Generator is already executing.");
50
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
51
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
52
+ if (y = 0, t) op = [op[0] & 2, t.value];
53
+ switch (op[0]) {
54
+ case 0: case 1: t = op; break;
55
+ case 4: _.label++; return { value: op[1], done: false };
56
+ case 5: _.label++; y = op[1]; op = [0]; continue;
57
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
58
+ default:
59
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
60
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
61
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
62
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
63
+ if (t[2]) _.ops.pop();
64
+ _.trys.pop(); continue;
65
+ }
66
+ op = body.call(thisArg, _);
67
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
68
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
69
+ }
70
+ };
71
+ var __importDefault = (this && this.__importDefault) || function (mod) {
72
+ return (mod && mod.__esModule) ? mod : { "default": mod };
73
+ };
74
+ Object.defineProperty(exports, "__esModule", { value: true });
75
+ var bun_test_1 = require("bun:test");
76
+ var mongoose_1 = __importStar(require("mongoose"));
77
+ var supertest_1 = __importDefault(require("supertest"));
78
+ var auth_1 = require("./auth");
79
+ var configurationApp_1 = require("./configurationApp");
80
+ var configurationPlugin_1 = require("./configurationPlugin");
81
+ var errors_1 = require("./errors");
82
+ var plugins_1 = require("./plugins");
83
+ var tests_1 = require("./tests");
84
+ // -- Test configuration model --
85
+ var generalSchema = new mongoose_1.Schema({
86
+ appName: {
87
+ default: "Test App",
88
+ description: "Display name of the application",
89
+ type: String,
90
+ },
91
+ maintenanceMode: {
92
+ default: false,
93
+ description: "Enable maintenance mode",
94
+ type: Boolean,
95
+ },
96
+ }, { _id: false });
97
+ var integrationsSchema = new mongoose_1.Schema({
98
+ apiKey: {
99
+ default: "",
100
+ description: "External API key",
101
+ secret: true,
102
+ secretName: "external-api-key",
103
+ type: String,
104
+ },
105
+ webhookUrl: {
106
+ default: "https://example.com/hook",
107
+ description: "Webhook URL",
108
+ type: String,
109
+ },
110
+ }, { _id: false });
111
+ var testConfigSchema = new mongoose_1.Schema({
112
+ general: { default: function () { return ({}); }, description: "General settings", type: generalSchema },
113
+ integrations: {
114
+ default: function () { return ({}); },
115
+ description: "Integration settings",
116
+ type: integrationsSchema,
117
+ },
118
+ }, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
119
+ testConfigSchema.plugin(configurationPlugin_1.configurationPlugin);
120
+ testConfigSchema.plugin(plugins_1.createdUpdatedPlugin);
121
+ var TestConfig = (mongoose_1.default.models.TestConfig ||
122
+ mongoose_1.default.model("TestConfig", testConfigSchema));
123
+ // -- Test model with top-level scalar fields --
124
+ var scalarConfigSchema = new mongoose_1.Schema({
125
+ debugMode: { default: false, description: "Enable debug", type: Boolean },
126
+ siteName: { default: "My Site", description: "Site name", type: String },
127
+ }, { strict: "throw", toJSON: { virtuals: true }, toObject: { virtuals: true } });
128
+ scalarConfigSchema.plugin(configurationPlugin_1.configurationPlugin);
129
+ scalarConfigSchema.plugin(plugins_1.createdUpdatedPlugin);
130
+ var ScalarConfig = (mongoose_1.default.models.ScalarConfig ||
131
+ mongoose_1.default.model("ScalarConfig", scalarConfigSchema));
132
+ // -- Helpers --
133
+ var buildApp = function (configModel, options) {
134
+ var app = (0, tests_1.getBaseServer)();
135
+ (0, auth_1.setupAuth)(app, tests_1.UserModel);
136
+ (0, auth_1.addAuthRoutes)(app, tests_1.UserModel);
137
+ var configApp = new configurationApp_1.ConfigurationApp({
138
+ basePath: options === null || options === void 0 ? void 0 : options.basePath,
139
+ fieldOverrides: options === null || options === void 0 ? void 0 : options.fieldOverrides,
140
+ model: configModel,
141
+ });
142
+ configApp.register(app);
143
+ app.use(errors_1.apiUnauthorizedMiddleware);
144
+ app.use(errors_1.apiErrorMiddleware);
145
+ return app;
146
+ };
147
+ // -- Tests --
148
+ (0, bun_test_1.describe)("configurationPlugin", function () {
149
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
150
+ var _a, _b;
151
+ return __generator(this, function (_c) {
152
+ switch (_c.label) {
153
+ case 0:
154
+ // Direct MongoDB collection delete to bypass plugin hooks
155
+ return [4 /*yield*/, ((_a = mongoose_1.default.connection.db) === null || _a === void 0 ? void 0 : _a.collection("testconfigs").deleteMany({}))];
156
+ case 1:
157
+ // Direct MongoDB collection delete to bypass plugin hooks
158
+ _c.sent();
159
+ return [4 /*yield*/, ((_b = mongoose_1.default.connection.db) === null || _b === void 0 ? void 0 : _b.collection("scalarconfigs").deleteMany({}))];
160
+ case 2:
161
+ _c.sent();
162
+ return [2 /*return*/];
163
+ }
164
+ });
165
+ }); });
166
+ (0, bun_test_1.describe)("getConfig", function () {
167
+ (0, bun_test_1.it)("creates a default document when none exists", function () { return __awaiter(void 0, void 0, void 0, function () {
168
+ var config;
169
+ return __generator(this, function (_a) {
170
+ switch (_a.label) {
171
+ case 0: return [4 /*yield*/, TestConfig.getConfig()];
172
+ case 1:
173
+ config = _a.sent();
174
+ (0, bun_test_1.expect)(config).toBeDefined();
175
+ (0, bun_test_1.expect)(config.general.appName).toBe("Test App");
176
+ (0, bun_test_1.expect)(config.general.maintenanceMode).toBe(false);
177
+ return [2 /*return*/];
178
+ }
179
+ });
180
+ }); });
181
+ (0, bun_test_1.it)("returns existing document on subsequent calls", function () { return __awaiter(void 0, void 0, void 0, function () {
182
+ var first, second;
183
+ return __generator(this, function (_a) {
184
+ switch (_a.label) {
185
+ case 0: return [4 /*yield*/, TestConfig.getConfig()];
186
+ case 1:
187
+ first = _a.sent();
188
+ return [4 /*yield*/, TestConfig.getConfig()];
189
+ case 2:
190
+ second = _a.sent();
191
+ (0, bun_test_1.expect)(first._id.toString()).toBe(second._id.toString());
192
+ return [2 /*return*/];
193
+ }
194
+ });
195
+ }); });
196
+ (0, bun_test_1.it)("returns a top-level section by key", function () { return __awaiter(void 0, void 0, void 0, function () {
197
+ var general;
198
+ return __generator(this, function (_a) {
199
+ switch (_a.label) {
200
+ case 0: return [4 /*yield*/, TestConfig.getConfig("general")];
201
+ case 1:
202
+ general = _a.sent();
203
+ (0, bun_test_1.expect)(general.appName).toBe("Test App");
204
+ (0, bun_test_1.expect)(general.maintenanceMode).toBe(false);
205
+ return [2 /*return*/];
206
+ }
207
+ });
208
+ }); });
209
+ (0, bun_test_1.it)("returns a nested value by dot-notation key", function () { return __awaiter(void 0, void 0, void 0, function () {
210
+ var appName;
211
+ return __generator(this, function (_a) {
212
+ switch (_a.label) {
213
+ case 0: return [4 /*yield*/, TestConfig.getConfig("general.appName")];
214
+ case 1:
215
+ appName = _a.sent();
216
+ (0, bun_test_1.expect)(appName).toBe("Test App");
217
+ return [2 /*return*/];
218
+ }
219
+ });
220
+ }); });
221
+ (0, bun_test_1.it)("returns updated value after updateConfig", function () { return __awaiter(void 0, void 0, void 0, function () {
222
+ var appName;
223
+ return __generator(this, function (_a) {
224
+ switch (_a.label) {
225
+ case 0: return [4 /*yield*/, TestConfig.updateConfig({ general: { appName: "Updated", maintenanceMode: false } })];
226
+ case 1:
227
+ _a.sent();
228
+ return [4 /*yield*/, TestConfig.getConfig("general.appName")];
229
+ case 2:
230
+ appName = _a.sent();
231
+ (0, bun_test_1.expect)(appName).toBe("Updated");
232
+ return [2 /*return*/];
233
+ }
234
+ });
235
+ }); });
236
+ });
237
+ (0, bun_test_1.describe)("updateConfig", function () {
238
+ (0, bun_test_1.it)("creates and sets values when no document exists", function () { return __awaiter(void 0, void 0, void 0, function () {
239
+ var config;
240
+ return __generator(this, function (_a) {
241
+ switch (_a.label) {
242
+ case 0: return [4 /*yield*/, TestConfig.updateConfig({
243
+ general: { appName: "Updated", maintenanceMode: false },
244
+ })];
245
+ case 1:
246
+ config = _a.sent();
247
+ (0, bun_test_1.expect)(config.general.appName).toBe("Updated");
248
+ return [2 /*return*/];
249
+ }
250
+ });
251
+ }); });
252
+ (0, bun_test_1.it)("updates existing document", function () { return __awaiter(void 0, void 0, void 0, function () {
253
+ var updated;
254
+ return __generator(this, function (_a) {
255
+ switch (_a.label) {
256
+ case 0: return [4 /*yield*/, TestConfig.getConfig()];
257
+ case 1:
258
+ _a.sent();
259
+ return [4 /*yield*/, TestConfig.updateConfig({
260
+ general: { appName: "Changed", maintenanceMode: true },
261
+ })];
262
+ case 2:
263
+ updated = _a.sent();
264
+ (0, bun_test_1.expect)(updated.general.appName).toBe("Changed");
265
+ (0, bun_test_1.expect)(updated.general.maintenanceMode).toBe(true);
266
+ return [2 /*return*/];
267
+ }
268
+ });
269
+ }); });
270
+ });
271
+ (0, bun_test_1.describe)("singleton enforcement", function () {
272
+ (0, bun_test_1.it)("prevents creating a second document via save", function () { return __awaiter(void 0, void 0, void 0, function () {
273
+ var error_1;
274
+ return __generator(this, function (_a) {
275
+ switch (_a.label) {
276
+ case 0: return [4 /*yield*/, TestConfig.getConfig()];
277
+ case 1:
278
+ _a.sent();
279
+ _a.label = 2;
280
+ case 2:
281
+ _a.trys.push([2, 4, , 5]);
282
+ return [4 /*yield*/, TestConfig.create({})];
283
+ case 3:
284
+ _a.sent();
285
+ throw new Error("Should have thrown");
286
+ case 4:
287
+ error_1 = _a.sent();
288
+ (0, bun_test_1.expect)(error_1.title || error_1.message).toInclude("Only one configuration document");
289
+ return [3 /*break*/, 5];
290
+ case 5: return [2 /*return*/];
291
+ }
292
+ });
293
+ }); });
294
+ });
295
+ (0, bun_test_1.describe)("hard delete prevention", function () {
296
+ (0, bun_test_1.it)("blocks deleteOne", function () { return __awaiter(void 0, void 0, void 0, function () {
297
+ var error_2;
298
+ return __generator(this, function (_a) {
299
+ switch (_a.label) {
300
+ case 0: return [4 /*yield*/, TestConfig.getConfig()];
301
+ case 1:
302
+ _a.sent();
303
+ _a.label = 2;
304
+ case 2:
305
+ _a.trys.push([2, 4, , 5]);
306
+ return [4 /*yield*/, TestConfig.deleteOne({})];
307
+ case 3:
308
+ _a.sent();
309
+ throw new Error("Should have thrown");
310
+ case 4:
311
+ error_2 = _a.sent();
312
+ (0, bun_test_1.expect)(error_2.title || error_2.message).toInclude("Cannot hard-delete");
313
+ return [3 /*break*/, 5];
314
+ case 5: return [2 /*return*/];
315
+ }
316
+ });
317
+ }); });
318
+ (0, bun_test_1.it)("blocks deleteMany", function () { return __awaiter(void 0, void 0, void 0, function () {
319
+ var error_3;
320
+ return __generator(this, function (_a) {
321
+ switch (_a.label) {
322
+ case 0: return [4 /*yield*/, TestConfig.getConfig()];
323
+ case 1:
324
+ _a.sent();
325
+ _a.label = 2;
326
+ case 2:
327
+ _a.trys.push([2, 4, , 5]);
328
+ return [4 /*yield*/, TestConfig.deleteMany({})];
329
+ case 3:
330
+ _a.sent();
331
+ throw new Error("Should have thrown");
332
+ case 4:
333
+ error_3 = _a.sent();
334
+ (0, bun_test_1.expect)(error_3.title || error_3.message).toInclude("Cannot hard-delete");
335
+ return [3 /*break*/, 5];
336
+ case 5: return [2 /*return*/];
337
+ }
338
+ });
339
+ }); });
340
+ (0, bun_test_1.it)("blocks findOneAndDelete", function () { return __awaiter(void 0, void 0, void 0, function () {
341
+ var error_4;
342
+ return __generator(this, function (_a) {
343
+ switch (_a.label) {
344
+ case 0: return [4 /*yield*/, TestConfig.getConfig()];
345
+ case 1:
346
+ _a.sent();
347
+ _a.label = 2;
348
+ case 2:
349
+ _a.trys.push([2, 4, , 5]);
350
+ return [4 /*yield*/, TestConfig.findOneAndDelete({})];
351
+ case 3:
352
+ _a.sent();
353
+ throw new Error("Should have thrown");
354
+ case 4:
355
+ error_4 = _a.sent();
356
+ (0, bun_test_1.expect)(error_4.title || error_4.message).toInclude("Cannot hard-delete");
357
+ return [3 /*break*/, 5];
358
+ case 5: return [2 /*return*/];
359
+ }
360
+ });
361
+ }); });
362
+ });
363
+ (0, bun_test_1.describe)("getSecretFields", function () {
364
+ (0, bun_test_1.it)("discovers secret fields from nested schemas", function () {
365
+ var secrets = TestConfig.getSecretFields();
366
+ (0, bun_test_1.expect)(secrets).toHaveLength(1);
367
+ (0, bun_test_1.expect)(secrets[0].path).toBe("integrations.apiKey");
368
+ (0, bun_test_1.expect)(secrets[0].secretName).toBe("external-api-key");
369
+ });
370
+ (0, bun_test_1.it)("returns empty array when no secrets", function () {
371
+ var secrets = ScalarConfig.getSecretFields();
372
+ (0, bun_test_1.expect)(secrets).toHaveLength(0);
373
+ });
374
+ });
375
+ (0, bun_test_1.describe)("resolveSecrets", function () {
376
+ (0, bun_test_1.it)("resolves secrets from a provider", function () { return __awaiter(void 0, void 0, void 0, function () {
377
+ var provider, resolved;
378
+ return __generator(this, function (_a) {
379
+ switch (_a.label) {
380
+ case 0:
381
+ provider = {
382
+ getSecret: function (name) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
383
+ return [2 /*return*/, (name === "external-api-key" ? "resolved-key" : null)];
384
+ }); }); },
385
+ name: "test-provider",
386
+ };
387
+ return [4 /*yield*/, TestConfig.resolveSecrets(provider)];
388
+ case 1:
389
+ resolved = _a.sent();
390
+ (0, bun_test_1.expect)(resolved.get("integrations.apiKey")).toBe("resolved-key");
391
+ return [2 /*return*/];
392
+ }
393
+ });
394
+ }); });
395
+ (0, bun_test_1.it)("handles provider failures gracefully", function () { return __awaiter(void 0, void 0, void 0, function () {
396
+ var provider, resolved;
397
+ return __generator(this, function (_a) {
398
+ switch (_a.label) {
399
+ case 0:
400
+ provider = {
401
+ getSecret: function () { return __awaiter(void 0, void 0, void 0, function () {
402
+ return __generator(this, function (_a) {
403
+ throw new Error("Provider down");
404
+ });
405
+ }); },
406
+ name: "failing-provider",
407
+ };
408
+ return [4 /*yield*/, TestConfig.resolveSecrets(provider)];
409
+ case 1:
410
+ resolved = _a.sent();
411
+ (0, bun_test_1.expect)(resolved.size).toBe(0);
412
+ return [2 /*return*/];
413
+ }
414
+ });
415
+ }); });
416
+ });
417
+ });
418
+ (0, bun_test_1.describe)("ConfigurationApp routes", function () {
419
+ var app;
420
+ var adminAgent;
421
+ var notAdminAgent;
422
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
423
+ var _a;
424
+ return __generator(this, function (_b) {
425
+ switch (_b.label) {
426
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
427
+ case 1:
428
+ _b.sent();
429
+ return [4 /*yield*/, ((_a = mongoose_1.default.connection.db) === null || _a === void 0 ? void 0 : _a.collection("testconfigs").deleteMany({}))];
430
+ case 2:
431
+ _b.sent();
432
+ app = buildApp(TestConfig);
433
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
434
+ case 3:
435
+ adminAgent = _b.sent();
436
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "notAdmin")];
437
+ case 4:
438
+ notAdminAgent = _b.sent();
439
+ return [2 /*return*/];
440
+ }
441
+ });
442
+ }); });
443
+ (0, bun_test_1.describe)("GET /configuration/meta", function () {
444
+ (0, bun_test_1.it)("returns schema metadata for admin", function () { return __awaiter(void 0, void 0, void 0, function () {
445
+ var res, generalSection;
446
+ return __generator(this, function (_a) {
447
+ switch (_a.label) {
448
+ case 0: return [4 /*yield*/, adminAgent.get("/configuration/meta").expect(200)];
449
+ case 1:
450
+ res = _a.sent();
451
+ (0, bun_test_1.expect)(res.body.sections).toBeDefined();
452
+ (0, bun_test_1.expect)(res.body.sections.length).toBeGreaterThan(0);
453
+ generalSection = res.body.sections.find(function (s) { return s.name === "general"; });
454
+ (0, bun_test_1.expect)(generalSection).toBeDefined();
455
+ (0, bun_test_1.expect)(generalSection.fields.appName).toBeDefined();
456
+ (0, bun_test_1.expect)(generalSection.fields.appName.type).toBe("string");
457
+ return [2 /*return*/];
458
+ }
459
+ });
460
+ }); });
461
+ (0, bun_test_1.it)("marks secret fields in metadata", function () { return __awaiter(void 0, void 0, void 0, function () {
462
+ var res, intSection;
463
+ return __generator(this, function (_a) {
464
+ switch (_a.label) {
465
+ case 0: return [4 /*yield*/, adminAgent.get("/configuration/meta").expect(200)];
466
+ case 1:
467
+ res = _a.sent();
468
+ intSection = res.body.sections.find(function (s) { return s.name === "integrations"; });
469
+ (0, bun_test_1.expect)(intSection.fields.apiKey.secret).toBe(true);
470
+ (0, bun_test_1.expect)(intSection.fields.webhookUrl.secret).toBeFalsy();
471
+ return [2 /*return*/];
472
+ }
473
+ });
474
+ }); });
475
+ (0, bun_test_1.it)("returns 403 for non-admin", function () { return __awaiter(void 0, void 0, void 0, function () {
476
+ return __generator(this, function (_a) {
477
+ switch (_a.label) {
478
+ case 0: return [4 /*yield*/, notAdminAgent.get("/configuration/meta").expect(403)];
479
+ case 1:
480
+ _a.sent();
481
+ return [2 /*return*/];
482
+ }
483
+ });
484
+ }); });
485
+ (0, bun_test_1.it)("returns 401 for unauthenticated user", function () { return __awaiter(void 0, void 0, void 0, function () {
486
+ return __generator(this, function (_a) {
487
+ switch (_a.label) {
488
+ case 0: return [4 /*yield*/, (0, supertest_1.default)(app).get("/configuration/meta").expect(401)];
489
+ case 1:
490
+ _a.sent();
491
+ return [2 /*return*/];
492
+ }
493
+ });
494
+ }); });
495
+ });
496
+ (0, bun_test_1.describe)("GET /configuration", function () {
497
+ (0, bun_test_1.it)("returns config values with defaults", function () { return __awaiter(void 0, void 0, void 0, function () {
498
+ var res;
499
+ return __generator(this, function (_a) {
500
+ switch (_a.label) {
501
+ case 0: return [4 /*yield*/, adminAgent.get("/configuration").expect(200)];
502
+ case 1:
503
+ res = _a.sent();
504
+ (0, bun_test_1.expect)(res.body.data.general.appName).toBe("Test App");
505
+ (0, bun_test_1.expect)(res.body.data.general.maintenanceMode).toBe(false);
506
+ return [2 /*return*/];
507
+ }
508
+ });
509
+ }); });
510
+ (0, bun_test_1.it)("redacts secret fields", function () { return __awaiter(void 0, void 0, void 0, function () {
511
+ var res;
512
+ return __generator(this, function (_a) {
513
+ switch (_a.label) {
514
+ case 0:
515
+ // Set a secret value first
516
+ return [4 /*yield*/, TestConfig.updateConfig({ integrations: { apiKey: "super-secret-key" } })];
517
+ case 1:
518
+ // Set a secret value first
519
+ _a.sent();
520
+ return [4 /*yield*/, adminAgent.get("/configuration").expect(200)];
521
+ case 2:
522
+ res = _a.sent();
523
+ (0, bun_test_1.expect)(res.body.data.integrations.apiKey).toBe("********");
524
+ (0, bun_test_1.expect)(res.body.data.integrations.webhookUrl).toBe("https://example.com/hook");
525
+ return [2 /*return*/];
526
+ }
527
+ });
528
+ }); });
529
+ (0, bun_test_1.it)("does not redact empty secret fields", function () { return __awaiter(void 0, void 0, void 0, function () {
530
+ var res;
531
+ return __generator(this, function (_a) {
532
+ switch (_a.label) {
533
+ case 0: return [4 /*yield*/, adminAgent.get("/configuration").expect(200)];
534
+ case 1:
535
+ res = _a.sent();
536
+ // Empty string should not be redacted
537
+ (0, bun_test_1.expect)(res.body.data.integrations.apiKey).toBe("");
538
+ return [2 /*return*/];
539
+ }
540
+ });
541
+ }); });
542
+ (0, bun_test_1.it)("returns 403 for non-admin", function () { return __awaiter(void 0, void 0, void 0, function () {
543
+ return __generator(this, function (_a) {
544
+ switch (_a.label) {
545
+ case 0: return [4 /*yield*/, notAdminAgent.get("/configuration").expect(403)];
546
+ case 1:
547
+ _a.sent();
548
+ return [2 /*return*/];
549
+ }
550
+ });
551
+ }); });
552
+ });
553
+ (0, bun_test_1.describe)("PATCH /configuration", function () {
554
+ (0, bun_test_1.it)("updates configuration values", function () { return __awaiter(void 0, void 0, void 0, function () {
555
+ var res;
556
+ return __generator(this, function (_a) {
557
+ switch (_a.label) {
558
+ case 0: return [4 /*yield*/, adminAgent
559
+ .patch("/configuration")
560
+ .send({ general: { appName: "New Name" } })
561
+ .expect(200)];
562
+ case 1:
563
+ res = _a.sent();
564
+ (0, bun_test_1.expect)(res.body.data.general.appName).toBe("New Name");
565
+ return [2 /*return*/];
566
+ }
567
+ });
568
+ }); });
569
+ (0, bun_test_1.it)("redacts secrets in the response", function () { return __awaiter(void 0, void 0, void 0, function () {
570
+ var res;
571
+ return __generator(this, function (_a) {
572
+ switch (_a.label) {
573
+ case 0: return [4 /*yield*/, adminAgent
574
+ .patch("/configuration")
575
+ .send({ integrations: { apiKey: "new-secret" } })
576
+ .expect(200)];
577
+ case 1:
578
+ res = _a.sent();
579
+ (0, bun_test_1.expect)(res.body.data.integrations.apiKey).toBe("********");
580
+ return [2 /*return*/];
581
+ }
582
+ });
583
+ }); });
584
+ (0, bun_test_1.it)("returns 403 for non-admin", function () { return __awaiter(void 0, void 0, void 0, function () {
585
+ return __generator(this, function (_a) {
586
+ switch (_a.label) {
587
+ case 0: return [4 /*yield*/, notAdminAgent
588
+ .patch("/configuration")
589
+ .send({ general: { appName: "Hack" } })
590
+ .expect(403)];
591
+ case 1:
592
+ _a.sent();
593
+ return [2 /*return*/];
594
+ }
595
+ });
596
+ }); });
597
+ });
598
+ (0, bun_test_1.describe)("POST /configuration/list-secrets", function () {
599
+ (0, bun_test_1.it)("returns discovered secret fields", function () { return __awaiter(void 0, void 0, void 0, function () {
600
+ var res;
601
+ return __generator(this, function (_a) {
602
+ switch (_a.label) {
603
+ case 0: return [4 /*yield*/, adminAgent.post("/configuration/list-secrets").expect(200)];
604
+ case 1:
605
+ res = _a.sent();
606
+ (0, bun_test_1.expect)(res.body.secretFields).toHaveLength(1);
607
+ (0, bun_test_1.expect)(res.body.secretFields[0].path).toBe("integrations.apiKey");
608
+ (0, bun_test_1.expect)(res.body.secretFields[0].secretName).toBe("external-api-key");
609
+ return [2 /*return*/];
610
+ }
611
+ });
612
+ }); });
613
+ (0, bun_test_1.it)("returns 403 for non-admin", function () { return __awaiter(void 0, void 0, void 0, function () {
614
+ return __generator(this, function (_a) {
615
+ switch (_a.label) {
616
+ case 0: return [4 /*yield*/, notAdminAgent.post("/configuration/list-secrets").expect(403)];
617
+ case 1:
618
+ _a.sent();
619
+ return [2 /*return*/];
620
+ }
621
+ });
622
+ }); });
623
+ });
624
+ });
625
+ (0, bun_test_1.describe)("ConfigurationApp with scalar fields", function () {
626
+ var app;
627
+ var adminAgent;
628
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
629
+ var _a;
630
+ return __generator(this, function (_b) {
631
+ switch (_b.label) {
632
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
633
+ case 1:
634
+ _b.sent();
635
+ return [4 /*yield*/, ((_a = mongoose_1.default.connection.db) === null || _a === void 0 ? void 0 : _a.collection("scalarconfigs").deleteMany({}))];
636
+ case 2:
637
+ _b.sent();
638
+ app = buildApp(ScalarConfig);
639
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
640
+ case 3:
641
+ adminAgent = _b.sent();
642
+ return [2 /*return*/];
643
+ }
644
+ });
645
+ }); });
646
+ (0, bun_test_1.it)("puts scalar fields into __root__ section", function () { return __awaiter(void 0, void 0, void 0, function () {
647
+ var res, rootSection;
648
+ return __generator(this, function (_a) {
649
+ switch (_a.label) {
650
+ case 0: return [4 /*yield*/, adminAgent.get("/configuration/meta").expect(200)];
651
+ case 1:
652
+ res = _a.sent();
653
+ rootSection = res.body.sections.find(function (s) { return s.name === "__root__"; });
654
+ (0, bun_test_1.expect)(rootSection).toBeDefined();
655
+ (0, bun_test_1.expect)(rootSection.displayName).toBe("General");
656
+ (0, bun_test_1.expect)(rootSection.fields.siteName).toBeDefined();
657
+ (0, bun_test_1.expect)(rootSection.fields.debugMode).toBeDefined();
658
+ return [2 /*return*/];
659
+ }
660
+ });
661
+ }); });
662
+ });
663
+ (0, bun_test_1.describe)("ConfigurationApp with field overrides", function () {
664
+ var app;
665
+ var adminAgent;
666
+ (0, bun_test_1.beforeEach)(function () { return __awaiter(void 0, void 0, void 0, function () {
667
+ var _a;
668
+ return __generator(this, function (_b) {
669
+ switch (_b.label) {
670
+ case 0: return [4 /*yield*/, (0, tests_1.setupDb)()];
671
+ case 1:
672
+ _b.sent();
673
+ return [4 /*yield*/, ((_a = mongoose_1.default.connection.db) === null || _a === void 0 ? void 0 : _a.collection("testconfigs").deleteMany({}))];
674
+ case 2:
675
+ _b.sent();
676
+ app = buildApp(TestConfig, {
677
+ fieldOverrides: { "integrations.webhookUrl": { widget: "url" } },
678
+ });
679
+ return [4 /*yield*/, (0, tests_1.authAsUser)(app, "admin")];
680
+ case 3:
681
+ adminAgent = _b.sent();
682
+ return [2 /*return*/];
683
+ }
684
+ });
685
+ }); });
686
+ (0, bun_test_1.it)("applies widget overrides to metadata", function () { return __awaiter(void 0, void 0, void 0, function () {
687
+ var res, intSection;
688
+ return __generator(this, function (_a) {
689
+ switch (_a.label) {
690
+ case 0: return [4 /*yield*/, adminAgent.get("/configuration/meta").expect(200)];
691
+ case 1:
692
+ res = _a.sent();
693
+ intSection = res.body.sections.find(function (s) { return s.name === "integrations"; });
694
+ (0, bun_test_1.expect)(intSection.fields.webhookUrl.widget).toBe("url");
695
+ return [2 /*return*/];
696
+ }
697
+ });
698
+ }); });
699
+ });