@terreno/api 0.9.2 → 0.10.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.
- package/bunfig.unit.toml +3 -0
- package/dist/api.d.ts +31 -13
- package/dist/errors.js +7 -1
- package/dist/expressServer.d.ts +1 -1
- package/dist/expressServer.js +3 -9
- package/dist/expressServer.test.js +4 -7
- package/dist/openApi.d.ts +6 -5
- package/dist/openApi.test.js +5 -1
- package/dist/openApiBuilder.d.ts +2 -2
- package/dist/openApiEtag.test.d.ts +1 -0
- package/dist/openApiEtag.test.js +86 -0
- package/dist/permissions.middleware.test.d.ts +1 -0
- package/dist/permissions.middleware.test.js +341 -0
- package/dist/populate.d.ts +2 -2
- package/dist/syncConsents.js +2 -2
- package/dist/tests/bunSetup.js +27 -22
- package/package.json +4 -1
- package/src/api.ts +39 -12
- package/src/errors.ts +8 -5
- package/src/expressServer.test.ts +4 -11
- package/src/expressServer.ts +5 -9
- package/src/openApi.test.ts +5 -1
- package/src/openApi.ts +14 -7
- package/src/openApiBuilder.ts +3 -3
- package/src/openApiEtag.test.ts +112 -0
- package/src/permissions.middleware.test.ts +197 -0
- package/src/populate.ts +2 -2
- package/src/syncConsents.ts +1 -1
- package/src/tests/bunSetup.ts +14 -8
package/src/openApi.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type express from "express";
|
|
1
2
|
import flatten from "lodash/flatten";
|
|
2
3
|
import merge from "lodash/merge";
|
|
3
4
|
import type {Model} from "mongoose";
|
|
4
5
|
import m2s from "mongoose-to-swagger";
|
|
5
6
|
|
|
6
|
-
import type {ModelRouterOptions} from "./api";
|
|
7
|
+
import type {ModelRouterOptions, OpenApiMiddleware} from "./api";
|
|
7
8
|
import {logger} from "./logger";
|
|
8
9
|
import {getOpenApiSpecForModel} from "./populate";
|
|
9
10
|
|
|
@@ -43,7 +44,7 @@ export const defaultOpenApiErrorResponses = {
|
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
// We repeat this constantly, so we make it a component so we only have to define it once.
|
|
46
|
-
function createAPIErrorComponent(openApi
|
|
47
|
+
function createAPIErrorComponent(openApi?: OpenApiMiddleware) {
|
|
47
48
|
// Create a schema component called APIError
|
|
48
49
|
openApi?.component("schemas", "APIError", {
|
|
49
50
|
properties: {
|
|
@@ -112,7 +113,10 @@ function createAPIErrorComponent(openApi: any) {
|
|
|
112
113
|
});
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
export function getOpenApiMiddleware<T>(
|
|
116
|
+
export function getOpenApiMiddleware<T>(
|
|
117
|
+
model: Model<T>,
|
|
118
|
+
options: Partial<ModelRouterOptions<T>>
|
|
119
|
+
): express.RequestHandler {
|
|
116
120
|
createAPIErrorComponent(options.openApi);
|
|
117
121
|
if (!options.openApi?.path) {
|
|
118
122
|
// Just log this once rather than for each middleware.
|
|
@@ -154,7 +158,10 @@ export function getOpenApiMiddleware<T>(model: Model<T>, options: Partial<ModelR
|
|
|
154
158
|
);
|
|
155
159
|
}
|
|
156
160
|
|
|
157
|
-
export function listOpenApiMiddleware<T>(
|
|
161
|
+
export function listOpenApiMiddleware<T>(
|
|
162
|
+
model: Model<T>,
|
|
163
|
+
options: Partial<ModelRouterOptions<T>>
|
|
164
|
+
): express.RequestHandler {
|
|
158
165
|
if (!options.openApi?.path) {
|
|
159
166
|
return noop;
|
|
160
167
|
}
|
|
@@ -320,7 +327,7 @@ export function listOpenApiMiddleware<T>(model: Model<T>, options: Partial<Model
|
|
|
320
327
|
export function createOpenApiMiddleware<T>(
|
|
321
328
|
model: Model<T>,
|
|
322
329
|
options: Partial<ModelRouterOptions<T>>
|
|
323
|
-
) {
|
|
330
|
+
): express.RequestHandler {
|
|
324
331
|
if (!options.openApi?.path) {
|
|
325
332
|
return noop;
|
|
326
333
|
}
|
|
@@ -372,7 +379,7 @@ export function createOpenApiMiddleware<T>(
|
|
|
372
379
|
export function patchOpenApiMiddleware<T>(
|
|
373
380
|
model: Model<T>,
|
|
374
381
|
options: Partial<ModelRouterOptions<T>>
|
|
375
|
-
) {
|
|
382
|
+
): express.RequestHandler {
|
|
376
383
|
if (!options.openApi?.path) {
|
|
377
384
|
return noop;
|
|
378
385
|
}
|
|
@@ -424,7 +431,7 @@ export function patchOpenApiMiddleware<T>(
|
|
|
424
431
|
export function deleteOpenApiMiddleware<T>(
|
|
425
432
|
model: Model<T>,
|
|
426
433
|
options: Partial<ModelRouterOptions<T>>
|
|
427
|
-
) {
|
|
434
|
+
): express.RequestHandler {
|
|
428
435
|
if (!options.openApi?.path) {
|
|
429
436
|
return noop;
|
|
430
437
|
}
|
package/src/openApiBuilder.ts
CHANGED
|
@@ -283,7 +283,7 @@ export interface OpenApiBuildResult {
|
|
|
283
283
|
*/
|
|
284
284
|
export class OpenApiMiddlewareBuilder {
|
|
285
285
|
/** Router options containing OpenAPI configuration */
|
|
286
|
-
private options: Partial<ModelRouterOptions<
|
|
286
|
+
private options: Partial<ModelRouterOptions<unknown>>;
|
|
287
287
|
|
|
288
288
|
/** Accumulated OpenAPI configuration from builder methods */
|
|
289
289
|
private config: OpenApiConfig;
|
|
@@ -302,7 +302,7 @@ export class OpenApiMiddlewareBuilder {
|
|
|
302
302
|
*
|
|
303
303
|
* @param options - Router options containing the OpenAPI path configuration
|
|
304
304
|
*/
|
|
305
|
-
constructor(options: Partial<ModelRouterOptions<
|
|
305
|
+
constructor(options: Partial<ModelRouterOptions<unknown>>) {
|
|
306
306
|
this.options = options;
|
|
307
307
|
this.config = {
|
|
308
308
|
responses: {},
|
|
@@ -803,7 +803,7 @@ export class OpenApiMiddlewareBuilder {
|
|
|
803
803
|
* ```
|
|
804
804
|
*/
|
|
805
805
|
export function createOpenApiBuilder(
|
|
806
|
-
options: Partial<ModelRouterOptions<
|
|
806
|
+
options: Partial<ModelRouterOptions<unknown>>
|
|
807
807
|
): OpenApiMiddlewareBuilder {
|
|
808
808
|
return new OpenApiMiddlewareBuilder(options);
|
|
809
809
|
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import {describe, expect, it, mock} from "bun:test";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import type {NextFunction, Request, Response} from "express";
|
|
4
|
+
|
|
5
|
+
import {openApiEtagMiddleware} from "./openApiEtag";
|
|
6
|
+
|
|
7
|
+
interface BuildRequestOptions {
|
|
8
|
+
ifNoneMatch?: string;
|
|
9
|
+
method?: string;
|
|
10
|
+
path?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const buildRequest = (options: BuildRequestOptions = {}): Request => {
|
|
14
|
+
const {ifNoneMatch, method = "GET", path = "/openapi.json"} = options;
|
|
15
|
+
return {
|
|
16
|
+
get: (header: string) => {
|
|
17
|
+
return header === "If-None-Match" ? ifNoneMatch : undefined;
|
|
18
|
+
},
|
|
19
|
+
method,
|
|
20
|
+
path,
|
|
21
|
+
} as Request;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const buildResponse = (): {
|
|
25
|
+
originalJson: ReturnType<typeof mock>;
|
|
26
|
+
res: Response;
|
|
27
|
+
set: ReturnType<typeof mock>;
|
|
28
|
+
status: ReturnType<typeof mock>;
|
|
29
|
+
end: ReturnType<typeof mock>;
|
|
30
|
+
} => {
|
|
31
|
+
const originalJson = mock((body: unknown) => ({body}));
|
|
32
|
+
const resObject = {
|
|
33
|
+
json: originalJson,
|
|
34
|
+
} as unknown as Response & Record<string, unknown>;
|
|
35
|
+
const set = mock(() => resObject);
|
|
36
|
+
const status = mock(() => resObject);
|
|
37
|
+
const end = mock(() => resObject);
|
|
38
|
+
|
|
39
|
+
resObject.set = set;
|
|
40
|
+
resObject.status = status;
|
|
41
|
+
resObject.end = end;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
end,
|
|
45
|
+
originalJson,
|
|
46
|
+
res: resObject,
|
|
47
|
+
set,
|
|
48
|
+
status,
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
describe("openApiEtagMiddleware", () => {
|
|
53
|
+
it("skips non-openapi requests", () => {
|
|
54
|
+
const req = buildRequest({method: "POST", path: "/health"});
|
|
55
|
+
const {res, originalJson} = buildResponse();
|
|
56
|
+
const next = mock(() => {}) as NextFunction;
|
|
57
|
+
|
|
58
|
+
openApiEtagMiddleware(req, res, next);
|
|
59
|
+
|
|
60
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
61
|
+
expect(res.json).toBe(originalJson);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("skips GET requests for non-openapi.json paths", () => {
|
|
65
|
+
const req = buildRequest({method: "GET", path: "/health"});
|
|
66
|
+
const {res, originalJson} = buildResponse();
|
|
67
|
+
const next = mock(() => {}) as NextFunction;
|
|
68
|
+
|
|
69
|
+
openApiEtagMiddleware(req, res, next);
|
|
70
|
+
|
|
71
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
72
|
+
expect(res.json).toBe(originalJson);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("sets ETag and returns json body when no matching If-None-Match header is provided", () => {
|
|
76
|
+
const req = buildRequest();
|
|
77
|
+
const {res, originalJson, set, status, end} = buildResponse();
|
|
78
|
+
const next = mock(() => {}) as NextFunction;
|
|
79
|
+
const body = {openapi: "3.0.0", paths: {"/todos": {get: {}}}};
|
|
80
|
+
|
|
81
|
+
openApiEtagMiddleware(req, res, next);
|
|
82
|
+
|
|
83
|
+
const result = res.json(body) as unknown as {body: typeof body};
|
|
84
|
+
const expectedEtag = `"${crypto.createHash("sha256").update(JSON.stringify(body)).digest("hex").substring(0, 16)}"`;
|
|
85
|
+
|
|
86
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
87
|
+
expect(set).toHaveBeenCalledWith("ETag", expectedEtag);
|
|
88
|
+
expect(originalJson).toHaveBeenCalledWith(body);
|
|
89
|
+
expect(status).toHaveBeenCalledTimes(0);
|
|
90
|
+
expect(end).toHaveBeenCalledTimes(0);
|
|
91
|
+
expect(result).toEqual({body});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("returns 304 when If-None-Match matches generated ETag", () => {
|
|
95
|
+
const body = {openapi: "3.0.0", paths: {"/users": {post: {}}}};
|
|
96
|
+
const etag = `"${crypto.createHash("sha256").update(JSON.stringify(body)).digest("hex").substring(0, 16)}"`;
|
|
97
|
+
const req = buildRequest({ifNoneMatch: etag});
|
|
98
|
+
const {res, originalJson, set, status, end} = buildResponse();
|
|
99
|
+
const next = mock(() => {}) as NextFunction;
|
|
100
|
+
|
|
101
|
+
openApiEtagMiddleware(req, res, next);
|
|
102
|
+
|
|
103
|
+
const result = res.json(body);
|
|
104
|
+
|
|
105
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
106
|
+
expect(set).toHaveBeenCalledWith("ETag", etag);
|
|
107
|
+
expect(status).toHaveBeenCalledWith(304);
|
|
108
|
+
expect(end).toHaveBeenCalledTimes(1);
|
|
109
|
+
expect(originalJson).toHaveBeenCalledTimes(0);
|
|
110
|
+
expect(result).toBe(res);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {describe, expect, it, mock, spyOn} from "bun:test";
|
|
2
|
+
import * as Sentry from "@sentry/bun";
|
|
3
|
+
import type express from "express";
|
|
4
|
+
|
|
5
|
+
import {APIError} from "./errors";
|
|
6
|
+
import {Permissions, permissionMiddleware} from "./permissions";
|
|
7
|
+
|
|
8
|
+
describe("permissionMiddleware", () => {
|
|
9
|
+
const allPermissions = {
|
|
10
|
+
create: [Permissions.IsAny],
|
|
11
|
+
delete: [Permissions.IsAny],
|
|
12
|
+
list: [Permissions.IsAny],
|
|
13
|
+
read: [Permissions.IsAny],
|
|
14
|
+
update: [Permissions.IsAny],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const buildReq = (overrides: Record<string, unknown> = {}): express.Request => {
|
|
18
|
+
return {
|
|
19
|
+
method: "GET",
|
|
20
|
+
params: {},
|
|
21
|
+
user: {id: "user-1"},
|
|
22
|
+
...overrides,
|
|
23
|
+
} as unknown as express.Request;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
it("calls next immediately for OPTIONS requests", async () => {
|
|
27
|
+
const model = {
|
|
28
|
+
collection: {findOne: mock(async () => null)},
|
|
29
|
+
findById: mock(() => ({exec: mock(async () => null)})),
|
|
30
|
+
modelName: "MockModel",
|
|
31
|
+
} as any;
|
|
32
|
+
const middleware = permissionMiddleware(model, {permissions: allPermissions});
|
|
33
|
+
const next = mock(() => {});
|
|
34
|
+
|
|
35
|
+
await middleware(buildReq({method: "OPTIONS"}), {} as express.Response, next as any);
|
|
36
|
+
|
|
37
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
38
|
+
expect((next as any).mock.calls[0]).toEqual([]);
|
|
39
|
+
expect(model.findById).toHaveBeenCalledTimes(0);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns APIError for unsupported HTTP methods", async () => {
|
|
43
|
+
const model = {
|
|
44
|
+
collection: {findOne: mock(async () => null)},
|
|
45
|
+
findById: mock(() => ({exec: mock(async () => null)})),
|
|
46
|
+
modelName: "MockModel",
|
|
47
|
+
} as any;
|
|
48
|
+
const middleware = permissionMiddleware(model, {permissions: allPermissions});
|
|
49
|
+
const next = mock(() => {});
|
|
50
|
+
|
|
51
|
+
await middleware(buildReq({method: "TRACE"}), {} as express.Response, next as any);
|
|
52
|
+
|
|
53
|
+
expect(next).toHaveBeenCalledTimes(1);
|
|
54
|
+
const [error] = (next as any).mock.calls[0];
|
|
55
|
+
expect(error).toBeInstanceOf(APIError);
|
|
56
|
+
expect(error.status).toBe(405);
|
|
57
|
+
expect(error.title).toContain("Method TRACE not allowed");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("wraps query execution failures in a 500 APIError", async () => {
|
|
61
|
+
const exec = mock(async () => {
|
|
62
|
+
throw new Error("query failed");
|
|
63
|
+
});
|
|
64
|
+
const model = {
|
|
65
|
+
collection: {findOne: mock(async () => null)},
|
|
66
|
+
findById: mock(() => ({exec})),
|
|
67
|
+
modelName: "MockModel",
|
|
68
|
+
} as any;
|
|
69
|
+
const middleware = permissionMiddleware(model, {permissions: allPermissions});
|
|
70
|
+
const next = mock(() => {});
|
|
71
|
+
|
|
72
|
+
await middleware(
|
|
73
|
+
buildReq({method: "GET", params: {id: "507f1f77bcf86cd799439011"}}),
|
|
74
|
+
{} as express.Response,
|
|
75
|
+
next as any
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
expect(exec).toHaveBeenCalledTimes(1);
|
|
79
|
+
const [error] = (next as any).mock.calls[0];
|
|
80
|
+
expect(error).toBeInstanceOf(APIError);
|
|
81
|
+
expect(error.status).toBe(500);
|
|
82
|
+
expect(error.title).toContain("GET failed on 507f1f77bcf86cd799439011");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("captures sentry message when document does not exist", async () => {
|
|
86
|
+
const captureMessageSpy = spyOn(Sentry, "captureMessage");
|
|
87
|
+
const model = {
|
|
88
|
+
collection: {findOne: mock(async () => null)},
|
|
89
|
+
findById: mock(() => ({exec: mock(async () => null)})),
|
|
90
|
+
modelName: "MockModel",
|
|
91
|
+
} as any;
|
|
92
|
+
const middleware = permissionMiddleware(model, {permissions: allPermissions});
|
|
93
|
+
const next = mock(() => {});
|
|
94
|
+
|
|
95
|
+
await middleware(
|
|
96
|
+
buildReq({method: "GET", params: {id: "507f1f77bcf86cd799439011"}}),
|
|
97
|
+
{} as express.Response,
|
|
98
|
+
next as any
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(captureMessageSpy).toHaveBeenCalledWith(
|
|
102
|
+
"Document 507f1f77bcf86cd799439011 not found for model MockModel"
|
|
103
|
+
);
|
|
104
|
+
const [error] = (next as any).mock.calls[0];
|
|
105
|
+
expect(error).toBeInstanceOf(APIError);
|
|
106
|
+
expect(error.status).toBe(404);
|
|
107
|
+
expect(error.meta).toBeUndefined();
|
|
108
|
+
captureMessageSpy.mockRestore();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("returns hidden reason metadata when document is deleted", async () => {
|
|
112
|
+
const model = {
|
|
113
|
+
collection: {findOne: mock(async () => ({deleted: true}))},
|
|
114
|
+
findById: mock(() => ({exec: mock(async () => null)})),
|
|
115
|
+
modelName: "MockModel",
|
|
116
|
+
} as any;
|
|
117
|
+
const middleware = permissionMiddleware(model, {permissions: allPermissions});
|
|
118
|
+
const next = mock(() => {});
|
|
119
|
+
|
|
120
|
+
await middleware(
|
|
121
|
+
buildReq({method: "GET", params: {id: "507f1f77bcf86cd799439011"}}),
|
|
122
|
+
{} as express.Response,
|
|
123
|
+
next as any
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
const [error] = (next as any).mock.calls[0];
|
|
127
|
+
expect(error).toBeInstanceOf(APIError);
|
|
128
|
+
expect(error.status).toBe(404);
|
|
129
|
+
expect(error.meta).toEqual({deleted: "true"});
|
|
130
|
+
expect(error.disableExternalErrorTracking).toBe(true);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("returns hidden reason metadata when document is disabled", async () => {
|
|
134
|
+
const model = {
|
|
135
|
+
collection: {findOne: mock(async () => ({disabled: true}))},
|
|
136
|
+
findById: mock(() => ({exec: mock(async () => null)})),
|
|
137
|
+
modelName: "MockModel",
|
|
138
|
+
} as any;
|
|
139
|
+
const middleware = permissionMiddleware(model, {permissions: allPermissions});
|
|
140
|
+
const next = mock(() => {});
|
|
141
|
+
|
|
142
|
+
await middleware(
|
|
143
|
+
buildReq({method: "GET", params: {id: "507f1f77bcf86cd799439011"}}),
|
|
144
|
+
{} as express.Response,
|
|
145
|
+
next as any
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
const [error] = (next as any).mock.calls[0];
|
|
149
|
+
expect(error).toBeInstanceOf(APIError);
|
|
150
|
+
expect(error.status).toBe(404);
|
|
151
|
+
expect(error.meta).toEqual({disabled: "true"});
|
|
152
|
+
expect(error.disableExternalErrorTracking).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("returns hidden reason metadata when document is archived", async () => {
|
|
156
|
+
const model = {
|
|
157
|
+
collection: {findOne: mock(async () => ({archived: true}))},
|
|
158
|
+
findById: mock(() => ({exec: mock(async () => null)})),
|
|
159
|
+
modelName: "MockModel",
|
|
160
|
+
} as any;
|
|
161
|
+
const middleware = permissionMiddleware(model, {permissions: allPermissions});
|
|
162
|
+
const next = mock(() => {});
|
|
163
|
+
|
|
164
|
+
await middleware(
|
|
165
|
+
buildReq({method: "GET", params: {id: "507f1f77bcf86cd799439011"}}),
|
|
166
|
+
{} as express.Response,
|
|
167
|
+
next as any
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const [error] = (next as any).mock.calls[0];
|
|
171
|
+
expect(error).toBeInstanceOf(APIError);
|
|
172
|
+
expect(error.status).toBe(404);
|
|
173
|
+
expect(error.meta).toEqual({archived: "true"});
|
|
174
|
+
expect(error.disableExternalErrorTracking).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("returns plain not found when hidden document has no reason", async () => {
|
|
178
|
+
const model = {
|
|
179
|
+
collection: {findOne: mock(async () => ({foo: "bar"}))},
|
|
180
|
+
findById: mock(() => ({exec: mock(async () => null)})),
|
|
181
|
+
modelName: "MockModel",
|
|
182
|
+
} as any;
|
|
183
|
+
const middleware = permissionMiddleware(model, {permissions: allPermissions});
|
|
184
|
+
const next = mock(() => {});
|
|
185
|
+
|
|
186
|
+
await middleware(
|
|
187
|
+
buildReq({method: "GET", params: {id: "507f1f77bcf86cd799439011"}}),
|
|
188
|
+
{} as express.Response,
|
|
189
|
+
next as any
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const [error] = (next as any).mock.calls[0];
|
|
193
|
+
expect(error).toBeInstanceOf(APIError);
|
|
194
|
+
expect(error.status).toBe(404);
|
|
195
|
+
expect(error.meta).toBeUndefined();
|
|
196
|
+
});
|
|
197
|
+
});
|
package/src/populate.ts
CHANGED
|
@@ -138,8 +138,8 @@ export function getOpenApiSpecForModel(
|
|
|
138
138
|
{
|
|
139
139
|
populatePaths,
|
|
140
140
|
extraModelProperties,
|
|
141
|
-
}: {populatePaths?: PopulatePath[]; extraModelProperties?:
|
|
142
|
-
): {properties:
|
|
141
|
+
}: {populatePaths?: PopulatePath[]; extraModelProperties?: Record<string, unknown>} = {}
|
|
142
|
+
): {properties: Record<string, unknown>; required: string[]} {
|
|
143
143
|
const modelSwagger = m2s(model, {
|
|
144
144
|
props: ["required", "enum"],
|
|
145
145
|
});
|
package/src/syncConsents.ts
CHANGED
|
@@ -237,7 +237,7 @@ export const syncConsents = async (
|
|
|
237
237
|
|
|
238
238
|
// Deactivate forms that are no longer in definitions
|
|
239
239
|
if (deactivateRemoved) {
|
|
240
|
-
for (const [slug
|
|
240
|
+
for (const [slug] of activeBySlug) {
|
|
241
241
|
if (!definitions[slug]) {
|
|
242
242
|
logger.info(`syncConsents: deactivating "${slug}"`, {dryRun});
|
|
243
243
|
if (!dryRun) {
|
package/src/tests/bunSetup.ts
CHANGED
|
@@ -6,17 +6,23 @@ import winston from "winston";
|
|
|
6
6
|
import {setupEnvironment} from "../expressServer";
|
|
7
7
|
import {logger, winstonLogger} from "../logger";
|
|
8
8
|
|
|
9
|
+
const shouldConnectToTestDb = process.env.BUN_TEST_DISABLE_DB !== "true";
|
|
10
|
+
|
|
9
11
|
// Connect to MongoDB once for all tests
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
if (shouldConnectToTestDb) {
|
|
13
|
+
beforeAll(async () => {
|
|
14
|
+
await mongoose
|
|
15
|
+
.connect("mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000")
|
|
16
|
+
.catch(logger.catch);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
// Close MongoDB connection after all tests
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
if (shouldConnectToTestDb) {
|
|
22
|
+
afterAll(async () => {
|
|
23
|
+
await mongoose.connection.close();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
20
26
|
|
|
21
27
|
let logs: string[] = [];
|
|
22
28
|
|