@terreno/api 0.21.0 → 0.22.1

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 (51) hide show
  1. package/bunfig.toml +1 -1
  2. package/dist/auth.test.js +408 -33
  3. package/dist/models/consentForm.js +2 -1
  4. package/dist/models/consentResponse.js +2 -1
  5. package/dist/models/versionConfig.js +2 -1
  6. package/dist/openApiBuilder.d.ts +18 -0
  7. package/dist/openApiBuilder.js +21 -0
  8. package/dist/openApiBuilder.test.js +16 -0
  9. package/dist/permissions.test.js +10 -43
  10. package/dist/populate.test.js +10 -42
  11. package/dist/syncConsents.test.js +2 -2
  12. package/dist/tests/bunSetup.js +33 -283
  13. package/dist/tests/createTestData.d.ts +9 -0
  14. package/dist/tests/createTestData.js +272 -0
  15. package/dist/tests/models.d.ts +71 -0
  16. package/dist/tests/models.js +134 -0
  17. package/dist/tests/mongoTestSetup.d.ts +7 -0
  18. package/dist/tests/mongoTestSetup.js +150 -0
  19. package/dist/tests/testEnv.d.ts +0 -0
  20. package/dist/tests/testEnv.js +6 -0
  21. package/dist/tests/testHelper.d.ts +22 -0
  22. package/dist/tests/testHelper.js +115 -0
  23. package/dist/tests/types.d.ts +29 -0
  24. package/dist/tests/types.js +2 -0
  25. package/dist/tests.d.ts +10 -78
  26. package/dist/tests.js +24 -264
  27. package/dist/transformers.test.js +14 -50
  28. package/package.json +18 -4
  29. package/src/__snapshots__/openApiBuilder.test.ts.snap +1 -0
  30. package/src/auth.test.ts +277 -29
  31. package/src/models/consentForm.ts +3 -4
  32. package/src/models/consentResponse.ts +6 -4
  33. package/src/models/versionConfig.ts +3 -4
  34. package/src/openApiBuilder.test.ts +9 -0
  35. package/src/openApiBuilder.ts +24 -0
  36. package/src/permissions.test.ts +8 -23
  37. package/src/populate.test.ts +7 -22
  38. package/src/syncConsents.test.ts +1 -1
  39. package/src/tests/bunSetup.ts +22 -249
  40. package/src/tests/createTestData.ts +176 -0
  41. package/src/tests/models.ts +164 -0
  42. package/src/tests/mongoTestSetup.ts +69 -0
  43. package/src/tests/testEnv.ts +4 -0
  44. package/src/tests/testHelper.ts +57 -0
  45. package/src/tests/types.ts +35 -0
  46. package/src/tests.ts +40 -244
  47. package/src/transformers.test.ts +11 -30
  48. package/tsconfig.typedoc.json +4 -0
  49. package/dist/tests/index.d.ts +0 -1
  50. package/dist/tests/index.js +0 -17
  51. package/src/tests/index.ts +0 -1
@@ -13,7 +13,7 @@ import {
13
13
  FoodModel,
14
14
  getBaseServer,
15
15
  RequiredModel,
16
- setupDb,
16
+ setupTestData,
17
17
  UserModel,
18
18
  } from "./tests";
19
19
 
@@ -24,22 +24,7 @@ describe("permissions", () => {
24
24
  beforeEach(async () => {
25
25
  process.env.REFRESH_TOKEN_SECRET = "testsecret1234";
26
26
 
27
- const [admin, notAdmin] = await setupDb();
28
-
29
- await Promise.all([
30
- FoodModel.create({
31
- calories: 1,
32
- created: new Date(),
33
- name: "Spinach",
34
- ownerId: notAdmin._id,
35
- }),
36
- FoodModel.create({
37
- calories: 100,
38
- created: Date.now() - 10,
39
- name: "Apple",
40
- ownerId: admin._id,
41
- }),
42
- ]);
27
+ await setupTestData();
43
28
  app = getBaseServer();
44
29
  setupAuth(app, UserModel as any);
45
30
  addAuthRoutes(app, UserModel as any);
@@ -74,12 +59,12 @@ describe("permissions", () => {
74
59
  describe("anonymous food", () => {
75
60
  it("list", async () => {
76
61
  const res = await server.get("/food").expect(200);
77
- expect(res.body.data).toHaveLength(2);
62
+ expect(res.body.data).toHaveLength(4);
78
63
  });
79
64
 
80
65
  it("get", async () => {
81
66
  const res = await server.get("/food").expect(200);
82
- expect(res.body.data).toHaveLength(2);
67
+ expect(res.body.data).toHaveLength(4);
83
68
  const res2 = await server.get(`/food/${res.body.data[0]._id}`).expect(200);
84
69
  expect(res.body.data[0]._id).toBe(res2.body.data._id);
85
70
  });
@@ -116,12 +101,12 @@ describe("permissions", () => {
116
101
 
117
102
  it("list", async () => {
118
103
  const res = await agent.get("/food").expect(200);
119
- expect(res.body.data).toHaveLength(2);
104
+ expect(res.body.data).toHaveLength(4);
120
105
  });
121
106
 
122
107
  it("get", async () => {
123
108
  const res = await agent.get("/food").expect(200);
124
- expect(res.body.data).toHaveLength(2);
109
+ expect(res.body.data).toHaveLength(4);
125
110
  const res2 = await server.get(`/food/${res.body.data[0]._id}`).expect(200);
126
111
  expect(res.body.data[0]._id).toBe(res2.body.data._id);
127
112
  });
@@ -175,12 +160,12 @@ describe("permissions", () => {
175
160
 
176
161
  it("list", async () => {
177
162
  const res = await agent.get("/food");
178
- expect(res.body.data).toHaveLength(2);
163
+ expect(res.body.data).toHaveLength(4);
179
164
  });
180
165
 
181
166
  it("get", async () => {
182
167
  const res = await agent.get("/food");
183
- expect(res.body.data).toHaveLength(2);
168
+ expect(res.body.data).toHaveLength(4);
184
169
  const res2 = await agent.get(`/food/${res.body.data[0]._id}`);
185
170
  expect(res.body.data[0]._id).toBe(res2.body.data._id);
186
171
  });
@@ -3,7 +3,7 @@ import {beforeEach, describe, expect, it} from "bun:test";
3
3
  import mongoose, {type Document, type HydratedDocument, Schema} from "mongoose";
4
4
 
5
5
  import {fixMixedFields, getOpenApiSpecForModel, unpopulate} from "./populate";
6
- import {FoodModel, setupDb, type User, UserModel} from "./tests";
6
+ import {FoodModel, setupTestData, type User, UserModel} from "./tests";
7
7
 
8
8
  describe("populate functions", () => {
9
9
  let admin: HydratedDocument<User>;
@@ -13,32 +13,17 @@ describe("populate functions", () => {
13
13
  let spinach: any;
14
14
 
15
15
  beforeEach(async () => {
16
- [admin, notAdmin] = await setupDb();
17
-
18
- [spinach] = await Promise.all([
19
- FoodModel.create({
20
- calories: 1,
21
- created: new Date("2021-12-03T00:00:20.000Z"),
22
- eatenBy: [admin._id],
23
- hidden: false,
24
- likesIds: [
25
- {likes: true, userId: admin._id},
26
- {likes: false, userId: notAdmin._id},
27
- ],
28
- name: "Spinach",
29
- ownerId: admin._id,
30
- source: {
31
- name: "Brand",
32
- },
33
- }),
34
- ]);
16
+ const testData = await setupTestData();
17
+ admin = testData.users.admin;
18
+ notAdmin = testData.users.notAdmin;
19
+ spinach = testData.foods.spinach;
35
20
  });
36
21
 
37
22
  it("unpopulate", async () => {
38
23
  let populated = await spinach.populate("ownerId");
39
24
  populated = await populated.populate("eatenBy");
40
25
  populated = await populated.populate("likesIds.userId");
41
- expect(populated.ownerId.name).toBe("Admin");
26
+ expect(populated.ownerId.name).toBe("Not Admin");
42
27
  expect(populated.eatenBy[0].id).toBe(admin.id);
43
28
  expect(populated.eatenBy[0].name).toBe("Admin");
44
29
  expect(populated.likesIds[0].userId.id).toBe(admin.id);
@@ -49,7 +34,7 @@ describe("populate functions", () => {
49
34
  // noExplicitAny: unpopulate returns Document<T> which doesn't expose model properties; would require refactoring the return type
50
35
  let unpopulated: any = unpopulate(populated, "ownerId");
51
36
  expect(spinach.ownerId.name).toBeUndefined();
52
- expect(unpopulated.ownerId.toString()).toBe(admin.id);
37
+ expect(unpopulated.ownerId.toString()).toBe(notAdmin.id);
53
38
  // Ensure nothing else was touched.
54
39
  expect(populated.likesIds[0].userId.id).toBe(admin.id);
55
40
  expect(populated.likesIds[0].userId.name).toBe("Admin");
@@ -2,7 +2,7 @@ import {afterEach, beforeEach, describe, expect, it} from "bun:test";
2
2
  import {ConsentForm} from "./models/consentForm";
3
3
  import type {ConsentFormDefinition} from "./syncConsents";
4
4
  import {syncConsents} from "./syncConsents";
5
- import {setupDb} from "./tests";
5
+ import {setupDb} from "./tests/testHelper";
6
6
 
7
7
  const baseDef: ConsentFormDefinition = {
8
8
  content: {en: "# Terms\nPlease agree."},
@@ -1,254 +1,27 @@
1
- // biome-ignore-all lint/suspicious/noExplicitAny: test mock typing
2
- import {afterAll, afterEach, beforeAll, beforeEach, mock} from "bun:test";
3
- import {Writable} from "node:stream";
4
- import mongoose from "mongoose";
5
- import winston from "winston";
1
+ import {registerBackendPreload, registerSimpleMongoPreload} from "@terreno/test";
6
2
 
7
- import {setupEnvironment} from "../expressServer";
8
- import {logger, winstonLogger} from "../logger";
3
+ const useFixtureCache = process.env.TERRENO_TEST_USE_FIXTURE_CACHE === "true";
9
4
 
10
- const shouldConnectToTestDb = process.env.BUN_TEST_DISABLE_DB !== "true";
11
-
12
- const defaultLocalMongoUri = "mongodb://127.0.0.1/terreno?&connectTimeoutMS=360000";
13
-
14
- /** When set by {@link TERRENO_TEST_USE_MEMORY_MONGO}, holds the server to stop in afterAll. */
15
- let memoryMongo: {getUri: () => string; stop: () => Promise<boolean>} | undefined;
16
-
17
- // Connect to MongoDB once for all tests
18
- if (shouldConnectToTestDb) {
19
- beforeAll(async () => {
20
- let uri = process.env.TERRENO_TEST_MONGODB_URI?.trim();
21
- if (!uri && process.env.TERRENO_TEST_USE_MEMORY_MONGO === "true") {
22
- const {MongoMemoryServer} = await import("mongodb-memory-server");
23
- memoryMongo = await MongoMemoryServer.create();
24
- uri = memoryMongo.getUri();
25
- }
26
- const connectUri = uri ?? defaultLocalMongoUri;
27
- await mongoose.connect(connectUri).catch(logger.catch);
5
+ if (useFixtureCache) {
6
+ registerBackendPreload({
7
+ connectMongoInBeforeAll: true,
8
+ loadTestDataFromCache: async () => {
9
+ const {loadTestDataFromCache} = await import("./mongoTestSetup");
10
+ await loadTestDataFromCache();
11
+ },
12
+ mongo: {
13
+ baseDatabaseName: "terrenoTest_base",
14
+ useReplSet: true,
15
+ },
16
+ testEnv: {
17
+ tokenIssuer: "terreno-api.test",
18
+ },
19
+ useTransactions: true,
28
20
  });
29
- }
30
-
31
- // Close MongoDB connection after all tests
32
- if (shouldConnectToTestDb) {
33
- afterAll(async () => {
34
- await mongoose.connection.close();
35
- if (memoryMongo) {
36
- await memoryMongo.stop();
37
- }
21
+ } else {
22
+ registerSimpleMongoPreload({
23
+ testEnv: {
24
+ tokenIssuer: "terreno-api.test",
25
+ },
38
26
  });
39
27
  }
40
-
41
- let logs: string[] = [];
42
-
43
- const SHOW_ALL_LOGS = process.env.SHOW_ALL_TEST_LOGS === "true";
44
-
45
- // Create a custom stream that captures logs
46
- const logStream = new Writable({
47
- write(chunk: any, _encoding: any, callback: any) {
48
- logs.push(chunk.toString());
49
- if (SHOW_ALL_LOGS) {
50
- process.stdout.write(chunk);
51
- }
52
- callback();
53
- },
54
- });
55
-
56
- // Silence both winston loggers by replacing all transports with our capturing stream
57
- const silentTransport = new winston.transports.Stream({
58
- format: winston.format.simple(),
59
- stream: logStream,
60
- });
61
-
62
- // Clear and silence the default winston logger
63
- winston.clear();
64
- winston.add(silentTransport);
65
-
66
- // Clear and silence the custom winstonLogger
67
- winstonLogger.clear();
68
- winstonLogger.add(silentTransport);
69
-
70
- // Capture and silence console methods
71
- const originalConsole = {
72
- debug: console.debug,
73
- error: console.error,
74
- info: console.info,
75
- // biome-ignore lint/suspicious/noConsole: We keep the original reference.
76
- log: console.log,
77
- warn: console.warn,
78
- };
79
-
80
- const captureConsoleMethod = (method: keyof typeof originalConsole): void => {
81
- (console as any)[method] = (...args: any[]) => {
82
- const logMessage = `[console.${method}] ${args.map((arg) => (typeof arg === "object" ? JSON.stringify(arg) : String(arg))).join(" ")}`;
83
- logs.push(logMessage);
84
- if (SHOW_ALL_LOGS) {
85
- originalConsole[method](...args);
86
- }
87
- };
88
- };
89
-
90
- captureConsoleMethod("log");
91
- captureConsoleMethod("info");
92
- captureConsoleMethod("warn");
93
- captureConsoleMethod("error");
94
- captureConsoleMethod("debug");
95
-
96
- // Setup before each test
97
- beforeEach(() => {
98
- process.env.TOKEN_SECRET = "secret";
99
- process.env.TOKEN_ISSUER = "terreno-api.test";
100
- process.env.SESSION_SECRET = "sessionSecret";
101
- process.env.REFRESH_TOKEN_SECRET = "refreshTokenSecret";
102
- setupEnvironment();
103
- // Re-silence loggers after setupEnvironment which may reconfigure them
104
- winston.clear();
105
- winston.add(silentTransport);
106
- winstonLogger.clear();
107
- winstonLogger.add(silentTransport);
108
- logs = [];
109
- });
110
-
111
- // Clear logs after each test
112
- afterEach(() => {
113
- logs = [];
114
- });
115
-
116
- // Mock @sentry/bun module
117
- mock.module("@sentry/bun", () => {
118
- const mockFn = (): ReturnType<typeof mock> => mock(() => {});
119
-
120
- // Mock Scope
121
- const mockScope = {
122
- addBreadcrumb: mockFn(),
123
- clear: mockFn(),
124
- getSpan: mockFn(),
125
- setContext: mockFn(),
126
- setFingerprint: mockFn(),
127
- setLevel: mockFn(),
128
- setSpan: mockFn(),
129
- setTag: mockFn(),
130
- setTags: mockFn(),
131
- setTransactionName: mockFn(),
132
- setUser: mockFn(),
133
- };
134
-
135
- // Mock Hub
136
- const mockClient = {
137
- captureException: mockFn(),
138
- captureMessage: mockFn(),
139
- close: mock(() => Promise.resolve(true)),
140
- flush: mock(() => Promise.resolve(true)),
141
- getOptions: mock(() => ({})),
142
- };
143
-
144
- const mockHub = {
145
- addBreadcrumb: mockFn(),
146
- captureException: mockFn(),
147
- captureMessage: mockFn(),
148
- configureScope: mockFn(),
149
- getClient: mock(() => mockClient),
150
- getScope: mock(() => mockScope),
151
- popScope: mockFn(),
152
- pushScope: mockFn(),
153
- setContext: mockFn(),
154
- setTag: mockFn(),
155
- setTags: mockFn(),
156
- setUser: mockFn(),
157
- withScope: mockFn(),
158
- };
159
-
160
- const mockSpan: any = {
161
- finish: mockFn(),
162
- setData: mockFn(),
163
- setStatus: mockFn(),
164
- setTag: mockFn(),
165
- startChild: mockFn(),
166
- toTraceparent: mock(() => "mock-trace-parent"),
167
- };
168
- mockSpan.startChild = mock(() => mockSpan);
169
-
170
- const mockTransaction = {
171
- finish: mockFn(),
172
- setData: mockFn(),
173
- setName: mockFn(),
174
- setStatus: mockFn(),
175
- setTag: mockFn(),
176
- startChild: mock(() => mockSpan),
177
- toTraceparent: mock(() => "mock-trace-parent"),
178
- };
179
-
180
- return {
181
- addBreadcrumb: mockFn(),
182
- captureException: mockFn(),
183
- captureMessage: mockFn(),
184
- clearScope: mockFn(),
185
- close: mock(() => Promise.resolve(true)),
186
- configureScope: mockFn(),
187
- default: {
188
- addBreadcrumb: mockFn(),
189
- captureException: mockFn(),
190
- captureMessage: mockFn(),
191
- clearScope: mockFn(),
192
- close: mock(() => Promise.resolve(true)),
193
- configureScope: mockFn(),
194
- flush: mock(() => Promise.resolve(true)),
195
- getClient: mock(() => mockClient),
196
- getCurrentHub: mock(() => mockHub),
197
- getCurrentScope: mock(() => mockScope),
198
- Handlers: {
199
- errorHandler: mock(() => (err: any, _req: any, _res: any, next: any) => next(err)),
200
- requestHandler: mock(() => (_req: any, _res: any, next: any) => next()),
201
- tracingHandler: mock(() => (_req: any, _res: any, next: any) => next()),
202
- },
203
- init: mockFn(),
204
- isInitialized: mock(() => true),
205
- popScope: mockFn(),
206
- pushScope: mockFn(),
207
- Severity: {
208
- Debug: "debug",
209
- Error: "error",
210
- Fatal: "fatal",
211
- Info: "info",
212
- Warning: "warning",
213
- } as const,
214
- setContext: mockFn(),
215
- setFingerprint: mockFn(),
216
- setLevel: mockFn(),
217
- setTag: mockFn(),
218
- setTags: mockFn(),
219
- setUser: mockFn(),
220
- setupExpressErrorHandler: mockFn(),
221
- startTransaction: mock(() => mockTransaction),
222
- withScope: mock((callback: any) => callback(mockScope)),
223
- },
224
- flush: mock(() => Promise.resolve(true)),
225
- getClient: mock(() => mockClient),
226
- getCurrentHub: mock(() => mockHub),
227
- getCurrentScope: mock(() => mockScope),
228
- Handlers: {
229
- errorHandler: mock(() => (err: any, _req: any, _res: any, next: any) => next(err)),
230
- requestHandler: mock(() => (_req: any, _res: any, next: any) => next()),
231
- tracingHandler: mock(() => (_req: any, _res: any, next: any) => next()),
232
- },
233
- init: mockFn(),
234
- isInitialized: mock(() => true),
235
- popScope: mockFn(),
236
- pushScope: mockFn(),
237
- Severity: {
238
- Debug: "debug",
239
- Error: "error",
240
- Fatal: "fatal",
241
- Info: "info",
242
- Warning: "warning",
243
- } as const,
244
- setContext: mockFn(),
245
- setFingerprint: mockFn(),
246
- setLevel: mockFn(),
247
- setTag: mockFn(),
248
- setTags: mockFn(),
249
- setUser: mockFn(),
250
- setupExpressErrorHandler: mockFn(),
251
- startTransaction: mock(() => mockTransaction),
252
- withScope: mock((callback: any) => callback(mockScope)),
253
- };
254
- });
@@ -0,0 +1,176 @@
1
+ import type {HydratedDocument} from "mongoose";
2
+ import type {PassportLocalMongooseDocument} from "passport-local-mongoose";
3
+
4
+ import {logger} from "../logger";
5
+ import {FoodModel, RequiredModel, type User, UserModel} from "./models";
6
+ import type {CachedTestData, TestData, TestFoods, TestRequired, TestUsers} from "./types";
7
+
8
+ const setPassword = async (user: HydratedDocument<User>, password: string): Promise<void> => {
9
+ await (user as unknown as PassportLocalMongooseDocument).setPassword(password);
10
+ await user.save();
11
+ };
12
+
13
+ export const clearTestCollections = async (): Promise<void> => {
14
+ await Promise.all([
15
+ UserModel.deleteMany({}),
16
+ FoodModel.deleteMany({}),
17
+ RequiredModel.deleteMany({}),
18
+ ]).catch(logger.catch);
19
+ };
20
+
21
+ export const createTestUsers = async (): Promise<TestUsers> => {
22
+ const [notAdmin, admin, adminOther] = await Promise.all([
23
+ UserModel.create({email: "notAdmin@example.com", name: "Not Admin"}),
24
+ UserModel.create({admin: true, email: "admin@example.com", name: "Admin"}),
25
+ UserModel.create({admin: true, email: "admin+other@example.com", name: "Admin Other"}),
26
+ ]);
27
+
28
+ await Promise.all([
29
+ setPassword(notAdmin, "password"),
30
+ setPassword(admin, "securePassword"),
31
+ setPassword(adminOther, "otherPassword"),
32
+ ]);
33
+
34
+ return {admin, adminOther, notAdmin};
35
+ };
36
+
37
+ export const createStandardFoods = async (users: TestUsers): Promise<TestFoods> => {
38
+ const {admin, adminOther, notAdmin} = users;
39
+
40
+ const [spinach, apple, carrots, pizza] = await Promise.all([
41
+ FoodModel.create({
42
+ calories: 1,
43
+ categories: [{name: "Vegetables", show: true}],
44
+ created: new Date("2021-12-03T00:00:20.000Z"),
45
+ eatenBy: [admin._id],
46
+ expiration: "2026-12-31",
47
+ hidden: false,
48
+ lastEatenWith: {
49
+ dressing: new Date("2021-12-03T19:00:30.000Z"),
50
+ },
51
+ likesIds: [
52
+ {likes: true, userId: admin._id},
53
+ {likes: false, userId: notAdmin._id},
54
+ ],
55
+ name: "Spinach",
56
+ ownerId: notAdmin._id,
57
+ source: {
58
+ dateAdded: "2023-12-13T12:30:00.000Z",
59
+ href: "https://www.example.com/spinach",
60
+ name: "Brand",
61
+ },
62
+ tags: ["healthy"],
63
+ }),
64
+ FoodModel.create({
65
+ calories: 100,
66
+ created: new Date("2021-12-03T00:00:30.000Z"),
67
+ expiration: "2026-12-31",
68
+ hidden: true,
69
+ likesIds: [{likes: true, userId: admin._id}],
70
+ name: "Apple",
71
+ ownerId: admin._id,
72
+ source: {name: "Orchard"},
73
+ tags: ["healthy"],
74
+ }),
75
+ FoodModel.create({
76
+ calories: 100,
77
+ created: new Date("2021-12-03T00:00:00.000Z"),
78
+ eatenBy: [admin._id, notAdmin._id],
79
+ expiration: "2026-12-31",
80
+ hidden: false,
81
+ likesIds: [{likes: false, userId: notAdmin._id}],
82
+ name: "Carrots",
83
+ ownerId: admin._id,
84
+ source: {name: "Farm"},
85
+ tags: ["vegetable"],
86
+ }),
87
+ FoodModel.create({
88
+ calories: 800,
89
+ created: new Date("2022-01-01T00:00:00.000Z"),
90
+ expiration: "2026-12-31",
91
+ hidden: false,
92
+ likesIds: [{likes: true, userId: adminOther._id}],
93
+ name: "Pizza",
94
+ ownerId: adminOther._id,
95
+ source: {name: "Pizzeria"},
96
+ tags: ["comfort"],
97
+ }),
98
+ ]);
99
+
100
+ return {apple, carrots, pizza, spinach};
101
+ };
102
+
103
+ export const createRequiredFixtures = async (): Promise<TestRequired> => {
104
+ const [sample, withAbout] = await Promise.all([
105
+ RequiredModel.create({name: "Sample Required"}),
106
+ RequiredModel.create({about: "Optional about text", name: "Required With About"}),
107
+ ]);
108
+
109
+ return {sample, withAbout};
110
+ };
111
+
112
+ /** Builds the standard Terreno API test database (users, foods, required docs). */
113
+ export const createTestData = async (): Promise<TestData> => {
114
+ await clearTestCollections();
115
+
116
+ const users = await createTestUsers();
117
+ const [foods, required] = await Promise.all([
118
+ createStandardFoods(users),
119
+ createRequiredFixtures(),
120
+ ]);
121
+
122
+ return {foods, required, users};
123
+ };
124
+
125
+ export const toCachedTestData = (testData: TestData): CachedTestData => ({
126
+ foods: {
127
+ apple: testData.foods.apple.id,
128
+ carrots: testData.foods.carrots.id,
129
+ pizza: testData.foods.pizza.id,
130
+ spinach: testData.foods.spinach.id,
131
+ },
132
+ required: {
133
+ sample: testData.required.sample.id,
134
+ withAbout: testData.required.withAbout.id,
135
+ },
136
+ users: {
137
+ admin: testData.users.admin.id,
138
+ adminOther: testData.users.adminOther.id,
139
+ notAdmin: testData.users.notAdmin.id,
140
+ },
141
+ });
142
+
143
+ export const loadTestDataFromDocuments = async (cached: CachedTestData): Promise<TestData> => {
144
+ const [admin, notAdmin, adminOther, spinach, apple, carrots, pizza, sample, withAbout] =
145
+ await Promise.all([
146
+ UserModel.findById(cached.users.admin),
147
+ UserModel.findById(cached.users.notAdmin),
148
+ UserModel.findById(cached.users.adminOther),
149
+ FoodModel.findById(cached.foods.spinach),
150
+ FoodModel.findById(cached.foods.apple),
151
+ FoodModel.findById(cached.foods.carrots),
152
+ FoodModel.findById(cached.foods.pizza),
153
+ RequiredModel.findById(cached.required.sample),
154
+ RequiredModel.findById(cached.required.withAbout),
155
+ ]);
156
+
157
+ if (
158
+ !admin ||
159
+ !notAdmin ||
160
+ !adminOther ||
161
+ !spinach ||
162
+ !apple ||
163
+ !carrots ||
164
+ !pizza ||
165
+ !sample ||
166
+ !withAbout
167
+ ) {
168
+ throw new Error("[createTestData] Cached test data references missing documents");
169
+ }
170
+
171
+ return {
172
+ foods: {apple, carrots, pizza, spinach},
173
+ required: {sample, withAbout},
174
+ users: {admin, adminOther, notAdmin},
175
+ };
176
+ };