@terreno/api 0.20.2 → 0.21.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 (65) hide show
  1. package/.ai/guidelines/core.md +71 -0
  2. package/.ai/skills/mongoose-schema-safety/SKILL.md +143 -0
  3. package/README.md +54 -1
  4. package/dist/__tests__/versionCheckPlugin.test.js +29 -7
  5. package/dist/actions.openApi.test.js +13 -11
  6. package/dist/api.js +98 -11
  7. package/dist/api.query.test.js +31 -1
  8. package/dist/api.test.js +211 -0
  9. package/dist/auth.test.js +10 -10
  10. package/dist/betterAuth.d.ts +1 -1
  11. package/dist/consentApp.test.js +1 -0
  12. package/dist/example.js +4 -4
  13. package/dist/expressServer.d.ts +0 -22
  14. package/dist/expressServer.js +1 -125
  15. package/dist/expressServer.test.js +90 -91
  16. package/dist/githubAuth.test.js +22 -22
  17. package/dist/logger.d.ts +154 -0
  18. package/dist/logger.js +445 -26
  19. package/dist/logger.test.js +435 -0
  20. package/dist/middleware.d.ts +7 -0
  21. package/dist/middleware.js +58 -1
  22. package/dist/middleware.test.js +159 -0
  23. package/dist/openApi.test.js +10 -17
  24. package/dist/openApiBuilder.test.js +18 -10
  25. package/dist/realtime/changeStreamWatcher.d.ts +4 -4
  26. package/dist/realtime/changeStreamWatcher.js +2 -4
  27. package/dist/realtime/queryMatcher.d.ts +1 -1
  28. package/dist/realtime/queryMatcher.js +39 -14
  29. package/dist/realtime/types.d.ts +3 -3
  30. package/dist/requestContext.d.ts +61 -0
  31. package/dist/requestContext.js +74 -0
  32. package/dist/secretProviders.test.js +335 -0
  33. package/dist/terrenoApp.d.ts +27 -15
  34. package/dist/terrenoApp.js +24 -14
  35. package/dist/terrenoApp.test.js +52 -0
  36. package/dist/tests/bunSetup.js +61 -7
  37. package/dist/tests.js +27 -4
  38. package/package.json +1 -1
  39. package/src/__tests__/versionCheckPlugin.test.ts +43 -15
  40. package/src/actions.openApi.test.ts +12 -10
  41. package/src/api.query.test.ts +24 -1
  42. package/src/api.test.ts +169 -0
  43. package/src/api.ts +71 -0
  44. package/src/auth.test.ts +10 -10
  45. package/src/betterAuth.ts +1 -1
  46. package/src/consentApp.test.ts +1 -0
  47. package/src/example.ts +4 -4
  48. package/src/expressServer.test.ts +82 -85
  49. package/src/expressServer.ts +1 -213
  50. package/src/githubAuth.test.ts +22 -22
  51. package/src/logger.test.ts +466 -1
  52. package/src/logger.ts +477 -14
  53. package/src/middleware.test.ts +74 -2
  54. package/src/middleware.ts +57 -0
  55. package/src/openApi.test.ts +10 -17
  56. package/src/openApiBuilder.test.ts +18 -10
  57. package/src/realtime/changeStreamWatcher.ts +15 -10
  58. package/src/realtime/queryMatcher.ts +54 -27
  59. package/src/realtime/types.ts +4 -4
  60. package/src/requestContext.ts +86 -0
  61. package/src/secretProviders.test.ts +219 -1
  62. package/src/terrenoApp.test.ts +38 -0
  63. package/src/terrenoApp.ts +37 -15
  64. package/src/tests/bunSetup.ts +16 -3
  65. package/src/tests.ts +17 -4
@@ -104,6 +104,7 @@ var mongoose_1 = __importStar(require("mongoose"));
104
104
  var supertest_1 = __importDefault(require("supertest"));
105
105
  var api_1 = require("./api");
106
106
  var configurationPlugin_1 = require("./configurationPlugin");
107
+ var errors_1 = require("./errors");
107
108
  var permissions_1 = require("./permissions");
108
109
  var plugins_1 = require("./plugins");
109
110
  var terrenoApp_1 = require("./terrenoApp");
@@ -125,6 +126,24 @@ var typedUserModel = tests_1.UserModel;
125
126
  }).build();
126
127
  (0, bun_test_1.expect)(app).toBeDefined();
127
128
  });
129
+ (0, bun_test_1.it)("does not add requestId to GET /openapi.json document bodies", function () { return __awaiter(void 0, void 0, void 0, function () {
130
+ var app, res;
131
+ return __generator(this, function (_d) {
132
+ switch (_d.label) {
133
+ case 0:
134
+ app = new terrenoApp_1.TerrenoApp({
135
+ skipListen: true,
136
+ userModel: typedUserModel,
137
+ }).build();
138
+ return [4 /*yield*/, (0, supertest_1.default)(app).get("/openapi.json").expect(200)];
139
+ case 1:
140
+ res = _d.sent();
141
+ (0, bun_test_1.expect)(res.body.openapi).toBe("3.0.0");
142
+ (0, bun_test_1.expect)(res.body.requestId).toBeUndefined();
143
+ return [2 /*return*/];
144
+ }
145
+ });
146
+ }); });
128
147
  (0, bun_test_1.it)("creates server with custom corsOrigin", function () {
129
148
  var app = new terrenoApp_1.TerrenoApp({
130
149
  corsOrigin: "https://example.com",
@@ -196,6 +215,7 @@ var typedUserModel = tests_1.UserModel;
196
215
  res = _d.sent();
197
216
  (0, bun_test_1.expect)(res.body.data).toHaveLength(1);
198
217
  (0, bun_test_1.expect)(res.body.data[0].name).toBe("Apple");
218
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
199
219
  return [2 /*return*/];
200
220
  }
201
221
  });
@@ -301,6 +321,7 @@ var typedUserModel = tests_1.UserModel;
301
321
  case 2:
302
322
  res = _d.sent();
303
323
  (0, bun_test_1.expect)(res.status).toBe(200);
324
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
304
325
  return [2 /*return*/];
305
326
  }
306
327
  });
@@ -356,6 +377,37 @@ var typedUserModel = tests_1.UserModel;
356
377
  case 1:
357
378
  res = _d.sent();
358
379
  (0, bun_test_1.expect)(res.status).toBe(500);
380
+ (0, bun_test_1.expect)(res.body.requestId).toBe(res.headers["x-request-id"]);
381
+ (0, bun_test_1.expect)(res.body.status).toBe(500);
382
+ (0, bun_test_1.expect)(res.body.title).toBe("Internal server error");
383
+ return [2 /*return*/];
384
+ }
385
+ });
386
+ }); });
387
+ (0, bun_test_1.it)("adds requestId to APIError JSON responses", function () { return __awaiter(void 0, void 0, void 0, function () {
388
+ var plugin, app, res;
389
+ return __generator(this, function (_d) {
390
+ switch (_d.label) {
391
+ case 0:
392
+ plugin = {
393
+ register: function (pluginApp) {
394
+ pluginApp.get("/api-error-route", function () {
395
+ throw new errors_1.APIError({ status: 400, title: "Bad request test" });
396
+ });
397
+ },
398
+ };
399
+ app = new terrenoApp_1.TerrenoApp({
400
+ skipListen: true,
401
+ userModel: typedUserModel,
402
+ })
403
+ .register(plugin)
404
+ .build();
405
+ return [4 /*yield*/, (0, supertest_1.default)(app).get("/api-error-route").set("X-Request-ID", "api-err-rid")];
406
+ case 1:
407
+ res = _d.sent();
408
+ (0, bun_test_1.expect)(res.status).toBe(400);
409
+ (0, bun_test_1.expect)(res.body.requestId).toBe("api-err-rid");
410
+ (0, bun_test_1.expect)(res.body.title).toBe("Bad request test");
359
411
  return [2 /*return*/];
360
412
  }
361
413
  });
@@ -1,4 +1,37 @@
1
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
+ })();
2
35
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
36
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
37
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -72,16 +105,32 @@ var winston_1 = __importDefault(require("winston"));
72
105
  var expressServer_1 = require("../expressServer");
73
106
  var logger_1 = require("../logger");
74
107
  var shouldConnectToTestDb = process.env.BUN_TEST_DISABLE_DB !== "true";
108
+ var defaultLocalMongoUri = "mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000";
109
+ /** When set by {@link TERRENO_TEST_USE_MEMORY_MONGO}, holds the server to stop in afterAll. */
110
+ var memoryMongo;
75
111
  // Connect to MongoDB once for all tests
76
112
  if (shouldConnectToTestDb) {
77
113
  (0, bun_test_1.beforeAll)(function () { return __awaiter(void 0, void 0, void 0, function () {
78
- return __generator(this, function (_a) {
79
- switch (_a.label) {
80
- case 0: return [4 /*yield*/, mongoose_1.default
81
- .connect("mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000")
82
- .catch(logger_1.logger.catch)];
114
+ var uri, MongoMemoryServer, connectUri;
115
+ var _a;
116
+ return __generator(this, function (_b) {
117
+ switch (_b.label) {
118
+ case 0:
119
+ uri = (_a = process.env.TERRENO_TEST_MONGODB_URI) === null || _a === void 0 ? void 0 : _a.trim();
120
+ if (!(!uri && process.env.TERRENO_TEST_USE_MEMORY_MONGO === "true")) return [3 /*break*/, 3];
121
+ return [4 /*yield*/, Promise.resolve().then(function () { return __importStar(require("mongodb-memory-server")); })];
83
122
  case 1:
84
- _a.sent();
123
+ MongoMemoryServer = (_b.sent()).MongoMemoryServer;
124
+ return [4 /*yield*/, MongoMemoryServer.create()];
125
+ case 2:
126
+ memoryMongo = _b.sent();
127
+ uri = memoryMongo.getUri();
128
+ _b.label = 3;
129
+ case 3:
130
+ connectUri = uri !== null && uri !== void 0 ? uri : defaultLocalMongoUri;
131
+ return [4 /*yield*/, mongoose_1.default.connect(connectUri).catch(logger_1.logger.catch)];
132
+ case 4:
133
+ _b.sent();
85
134
  return [2 /*return*/];
86
135
  }
87
136
  });
@@ -95,7 +144,12 @@ if (shouldConnectToTestDb) {
95
144
  case 0: return [4 /*yield*/, mongoose_1.default.connection.close()];
96
145
  case 1:
97
146
  _a.sent();
98
- return [2 /*return*/];
147
+ if (!memoryMongo) return [3 /*break*/, 3];
148
+ return [4 /*yield*/, memoryMongo.stop()];
149
+ case 2:
150
+ _a.sent();
151
+ _a.label = 3;
152
+ case 3: return [2 /*return*/];
99
153
  }
100
154
  });
101
155
  }); });
package/dist/tests.js CHANGED
@@ -192,7 +192,7 @@ var getBaseServer = function () {
192
192
  app.set("query parser", function (str) { return qs_1.default.parse(str, { arrayLimit: 200 }); });
193
193
  // Express 5 defaults to 'simple' query parser (Node querystring) which doesn't
194
194
  // support nested bracket notation like name[$regex]=Green. Use qs to match
195
- // what setupServer() configures.
195
+ // what TerrenoApp.build() configures.
196
196
  app.set("query parser", function (str) { return qs_1.default.parse(str, { arrayLimit: 200 }); });
197
197
  // Record mount paths on layers for Express 5 → OpenAPI compat
198
198
  (0, openApiCompat_1.patchAppUse)(app);
@@ -230,13 +230,36 @@ var authAsUser = function (app, type) { return __awaiter(void 0, void 0, void 0,
230
230
  });
231
231
  }); };
232
232
  exports.authAsUser = authAsUser;
233
+ var defaultTestMongoUri = "mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000";
234
+ /** Ensures Mongoose is connected without replacing an existing test connection (e.g. MongoMemoryServer from bunSetup). */
235
+ var ensureTestMongooseConnected = function () { return __awaiter(void 0, void 0, void 0, function () {
236
+ var uri;
237
+ var _a;
238
+ return __generator(this, function (_b) {
239
+ switch (_b.label) {
240
+ case 0:
241
+ if (mongoose_1.default.connection.readyState === 1) {
242
+ return [2 /*return*/];
243
+ }
244
+ if (!(mongoose_1.default.connection.readyState === 2)) return [3 /*break*/, 2];
245
+ return [4 /*yield*/, mongoose_1.default.connection.asPromise()];
246
+ case 1:
247
+ _b.sent();
248
+ return [2 /*return*/];
249
+ case 2:
250
+ uri = ((_a = process.env.TERRENO_TEST_MONGODB_URI) === null || _a === void 0 ? void 0 : _a.trim()) || defaultTestMongoUri;
251
+ return [4 /*yield*/, mongoose_1.default.connect(uri).catch(logger_1.logger.catch)];
252
+ case 3:
253
+ _b.sent();
254
+ return [2 /*return*/];
255
+ }
256
+ });
257
+ }); };
233
258
  var setupDb = function () { return __awaiter(void 0, void 0, void 0, function () {
234
259
  var _a, notAdmin, admin, adminOther, error_1;
235
260
  return __generator(this, function (_b) {
236
261
  switch (_b.label) {
237
- case 0: return [4 /*yield*/, mongoose_1.default
238
- .connect("mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000")
239
- .catch(logger_1.logger.catch)];
262
+ case 0: return [4 /*yield*/, ensureTestMongooseConnected()];
240
263
  case 1:
241
264
  _b.sent();
242
265
  process.env.REFRESH_TOKEN_SECRET = "refresh_secret";
package/package.json CHANGED
@@ -109,5 +109,5 @@
109
109
  "updateSnapshot": "bun test --update-snapshots"
110
110
  },
111
111
  "types": "dist/index.d.ts",
112
- "version": "0.20.2"
112
+ "version": "0.21.0"
113
113
  }
@@ -30,19 +30,27 @@ describe("VersionCheckPlugin", () => {
30
30
  it("returns ok when no VersionConfig exists", async () => {
31
31
  const res = await app.get("/version-check").query({platform: "web", version: 100});
32
32
  expect(res.status).toBe(200);
33
- expect(res.body).toEqual({pollingIntervalMs: 86400000, status: "ok"});
33
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
34
+ expect(res.body).toEqual(
35
+ expect.objectContaining({
36
+ pollingIntervalMs: 86400000,
37
+ status: "ok",
38
+ })
39
+ );
34
40
  });
35
41
 
36
42
  it("returns ok when version param is missing", async () => {
37
43
  const res = await app.get("/version-check");
38
44
  expect(res.status).toBe(200);
39
- expect(res.body).toEqual({status: "ok"});
45
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
46
+ expect(res.body).toEqual(expect.objectContaining({status: "ok"}));
40
47
  });
41
48
 
42
49
  it("returns ok when version param is invalid", async () => {
43
50
  const res = await app.get("/version-check").query({platform: "web", version: "invalid"});
44
51
  expect(res.status).toBe(200);
45
- expect(res.body).toEqual({status: "ok"});
52
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
53
+ expect(res.body).toEqual(expect.objectContaining({status: "ok"}));
46
54
  });
47
55
 
48
56
  it("returns ok when client version >= warning and required (web)", async () => {
@@ -53,12 +61,15 @@ describe("VersionCheckPlugin", () => {
53
61
 
54
62
  const res = await app.get("/version-check").query({platform: "web", version: 150});
55
63
  expect(res.status).toBe(200);
56
- expect(res.body).toEqual({
57
- pollingIntervalMs: 86400000,
58
- requiredVersion: 50,
59
- status: "ok",
60
- warningVersion: 100,
61
- });
64
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
65
+ expect(res.body).toEqual(
66
+ expect.objectContaining({
67
+ pollingIntervalMs: 86400000,
68
+ requiredVersion: 50,
69
+ status: "ok",
70
+ warningVersion: 100,
71
+ })
72
+ );
62
73
  });
63
74
 
64
75
  it("returns warning when client version < warning (web)", async () => {
@@ -70,6 +81,7 @@ describe("VersionCheckPlugin", () => {
70
81
 
71
82
  const res = await app.get("/version-check").query({platform: "web", version: 80});
72
83
  expect(res.status).toBe(200);
84
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
73
85
  expect(res.body.status).toBe("warning");
74
86
  expect(res.body.message).toBe("Please update!");
75
87
  });
@@ -84,6 +96,7 @@ describe("VersionCheckPlugin", () => {
84
96
 
85
97
  const res = await app.get("/version-check").query({platform: "web", version: 50});
86
98
  expect(res.status).toBe(200);
99
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
87
100
  expect(res.body.status).toBe("required");
88
101
  expect(res.body.message).toBe("Update required");
89
102
  expect(res.body.updateUrl).toBe("https://example.com/update");
@@ -98,9 +111,11 @@ describe("VersionCheckPlugin", () => {
98
111
  });
99
112
 
100
113
  const webRes = await app.get("/version-check").query({platform: "web", version: 100});
114
+ expect(webRes.body.requestId).toBe(webRes.headers["x-request-id"]);
101
115
  expect(webRes.body.status).toBe("ok");
102
116
 
103
117
  const mobileRes = await app.get("/version-check").query({platform: "mobile", version: 100});
118
+ expect(mobileRes.body.requestId).toBe(mobileRes.headers["x-request-id"]);
104
119
  expect(mobileRes.body.status).toBe("required");
105
120
  });
106
121
 
@@ -111,6 +126,7 @@ describe("VersionCheckPlugin", () => {
111
126
  });
112
127
 
113
128
  const res = await app.get("/version-check").query({platform: "invalid", version: 50});
129
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
114
130
  expect(res.body.status).toBe("required");
115
131
  });
116
132
 
@@ -122,12 +138,15 @@ describe("VersionCheckPlugin", () => {
122
138
 
123
139
  const res = await app.get("/version-check").query({platform: "web", version: 100});
124
140
  expect(res.status).toBe(200);
125
- expect(res.body).toEqual({
126
- pollingIntervalMs: 86400000,
127
- requiredVersion: 50,
128
- status: "ok",
129
- warningVersion: 100,
130
- });
141
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
142
+ expect(res.body).toEqual(
143
+ expect.objectContaining({
144
+ pollingIntervalMs: 86400000,
145
+ requiredVersion: 50,
146
+ status: "ok",
147
+ warningVersion: 100,
148
+ })
149
+ );
131
150
  });
132
151
 
133
152
  it("returns pollingIntervalMs from config pollingIntervalMinutes", async () => {
@@ -139,6 +158,7 @@ describe("VersionCheckPlugin", () => {
139
158
 
140
159
  const res = await app.get("/version-check").query({platform: "web", version: 100});
141
160
  expect(res.status).toBe(200);
161
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
142
162
  expect(res.body.pollingIntervalMs).toBe(3600000);
143
163
  });
144
164
 
@@ -150,6 +170,7 @@ describe("VersionCheckPlugin", () => {
150
170
 
151
171
  const res = await app.get("/version-check").query({platform: "web", version: 100});
152
172
  expect(res.status).toBe(200);
173
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
153
174
  expect(res.body.pollingIntervalMs).toBe(86400000);
154
175
  });
155
176
 
@@ -161,6 +182,7 @@ describe("VersionCheckPlugin", () => {
161
182
 
162
183
  const res = await app.get("/version-check?version=50&platform=web");
163
184
  expect(res.status).toBe(200);
185
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
164
186
  expect(res.body.status).toBe("required");
165
187
  });
166
188
 
@@ -172,6 +194,7 @@ describe("VersionCheckPlugin", () => {
172
194
 
173
195
  const res = await app.get("/version-check").query({platform: "web", version: 50});
174
196
  expect(res.status).toBe(200);
197
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
175
198
  expect(res.body.status).toBe("warning");
176
199
  expect(res.body.message).toBe(
177
200
  "A new version is available. Please update for the best experience."
@@ -186,6 +209,7 @@ describe("VersionCheckPlugin", () => {
186
209
 
187
210
  const res = await app.get("/version-check").query({platform: "web", version: 50});
188
211
  expect(res.status).toBe(200);
212
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
189
213
  expect(res.body.status).toBe("required");
190
214
  expect(res.body.message).toBe(
191
215
  "This version is no longer supported. Please update to continue."
@@ -200,6 +224,7 @@ describe("VersionCheckPlugin", () => {
200
224
 
201
225
  const res = await app.get("/version-check").query({platform: "web", version: 100});
202
226
  expect(res.status).toBe(200);
227
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
203
228
  expect(res.body.status).toBe("warning");
204
229
  });
205
230
 
@@ -210,12 +235,14 @@ describe("VersionCheckPlugin", () => {
210
235
  });
211
236
 
212
237
  const warningRes = await app.get("/version-check").query({platform: "web", version: 150});
238
+ expect(warningRes.body.requestId).toBe(warningRes.headers["x-request-id"]);
213
239
  expect(warningRes.body.status).toBe("warning");
214
240
  expect(warningRes.body.message).toBe(
215
241
  "A new version is available. Please update for the best experience."
216
242
  );
217
243
 
218
244
  const requiredRes = await app.get("/version-check").query({platform: "web", version: 50});
245
+ expect(requiredRes.body.requestId).toBe(requiredRes.headers["x-request-id"]);
219
246
  expect(requiredRes.body.status).toBe("required");
220
247
  expect(requiredRes.body.message).toBe(
221
248
  "This version is no longer supported. Please update to continue."
@@ -225,6 +252,7 @@ describe("VersionCheckPlugin", () => {
225
252
  it("handles numeric version parameter", async () => {
226
253
  const res = await app.get("/version-check?version=50&platform=web");
227
254
  expect(res.status).toBe(200);
255
+ expect(res.body.requestId).toBe(res.headers["x-request-id"]);
228
256
  });
229
257
  });
230
258
 
@@ -3,8 +3,6 @@ import {beforeEach, describe, expect, it} from "bun:test";
3
3
  import type express from "express";
4
4
  import supertest from "supertest";
5
5
  import {type ModelRouterOptions, modelRouter} from "./api";
6
- import {addAuthRoutes, setupAuth} from "./auth";
7
- import {setupServer} from "./expressServer";
8
6
  import {Permissions} from "./permissions";
9
7
  import {TerrenoApp} from "./terrenoApp";
10
8
  import {FoodModel, setupDb, UserModel} from "./tests";
@@ -57,6 +55,8 @@ const primeActionOpenApiRoutes = async (
57
55
  };
58
56
 
59
57
  const assertActionOpenApiSpec = (spec: Record<string, unknown>): void => {
58
+ expect(spec.requestId).toBeUndefined();
59
+
60
60
  const paths = spec.paths as Record<string, Record<string, unknown>>;
61
61
  const collectionPath = paths["/food/summarize"];
62
62
  const instancePath = paths["/food/{id}/ping"];
@@ -139,14 +139,18 @@ describe("action OpenAPI emission", () => {
139
139
 
140
140
  const specRes = await server.get("/openapi.json").expect(200);
141
141
  assertActionOpenApiSpec(specRes.body);
142
+
143
+ const pingRes = await server.get(`/food/${foodId}/ping`).expect(200);
144
+ expect(pingRes.body.data).toEqual({id: foodId});
145
+ expect(pingRes.body.requestId).toBe(pingRes.headers["x-request-id"]);
142
146
  });
143
147
  });
144
148
 
145
- describe("setupServer", () => {
149
+ describe("configureApp", () => {
146
150
  let app: express.Application;
147
151
 
148
152
  beforeEach(() => {
149
- const addRoutes = (
153
+ const configureApp = (
150
154
  router: express.Router,
151
155
  routerOptions?: Partial<ModelRouterOptions<unknown>>
152
156
  ): void => {
@@ -156,16 +160,14 @@ describe("action OpenAPI emission", () => {
156
160
  );
157
161
  };
158
162
 
159
- app = setupServer({
160
- addRoutes,
163
+ app = new TerrenoApp({
164
+ configureApp,
161
165
  skipListen: true,
162
166
  userModel: UserModel as any,
163
- });
164
- setupAuth(app, UserModel as any);
165
- addAuthRoutes(app, UserModel as any);
167
+ }).build();
166
168
  });
167
169
 
168
- it("emits the same action operations on first hit via legacy setupServer", async () => {
170
+ it("emits the same action operations on first hit via configureApp", async () => {
169
171
  const server = supertest(app);
170
172
  await primeActionOpenApiRoutes(server, foodId);
171
173
 
@@ -95,7 +95,17 @@ describe("query and list methods", () => {
95
95
  update: [Permissions.IsOwner],
96
96
  },
97
97
  populatePaths: [{path: "ownerId"}],
98
- queryFields: ["hidden", "name", "calories", "created", "source.name", "tags", "eatenBy"],
98
+ queryFields: [
99
+ "hidden",
100
+ "name",
101
+ "calories",
102
+ "created",
103
+ "created_gte",
104
+ "created_lte",
105
+ "source.name",
106
+ "tags",
107
+ "eatenBy",
108
+ ],
99
109
  sort: {created: "descending"},
100
110
  })
101
111
  );
@@ -190,6 +200,19 @@ describe("query and list methods", () => {
190
200
  expect(res.body.data[0].id).toBe((apple as any).id);
191
201
  });
192
202
 
203
+ it("list applies created_gte and created_lte as a Date range", async () => {
204
+ const res = await agent
205
+ .get("/food")
206
+ .query({
207
+ created_gte: "2021-12-03T00:00:05.000Z",
208
+ created_lte: "2021-12-03T00:00:25.000Z",
209
+ limit: 10,
210
+ })
211
+ .expect(200);
212
+ const names = (res.body.data as {name: string}[]).map((d) => d.name).sort();
213
+ expect(names).toEqual(["Pizza", "Spinach"]);
214
+ });
215
+
193
216
  it("list query params not in list", async () => {
194
217
  const res = await agent.get(`/food?ownerId=${admin._id}`).expect(400);
195
218
  expect(res.body.title).toBe("ownerId is not allowed as a query param.");