@jaypie/testkit 1.0.25 → 1.0.27

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/README.md CHANGED
@@ -22,7 +22,7 @@ The testkit provides a complete mock for Jaypie including:
22
22
  * Most non-utility functions are mocked to allow simple testing
23
23
 
24
24
  ```javascript
25
- vi.mock("jaypie", vi.importActual("@jaypie/testkit"));
25
+ vi.mock("jaypie", async () => vi.importActual("@jaypie/testkit/mock"));
26
26
  ```
27
27
 
28
28
  #### Log Spying
@@ -30,7 +30,7 @@ vi.mock("jaypie", vi.importActual("@jaypie/testkit"));
30
30
  ```javascript
31
31
  import { log } from "jaypie";
32
32
 
33
- vi.mock("jaypie", vi.importActual("@jaypie/testkit"));
33
+ vi.mock("jaypie", async () => vi.importActual("@jaypie/testkit/mock"));
34
34
 
35
35
  afterEach(() => {
36
36
  vi.clearAllMocks();
@@ -59,6 +59,8 @@ describe("Observability", () => {
59
59
  // Act
60
60
  await myNewFunction(); // TODO: add any "happy path" parameters
61
61
  // Assert
62
+ expect(log).not.toBeCalledAboveTrace();
63
+ // or individually:
62
64
  expect(log.debug).not.toHaveBeenCalled();
63
65
  expect(log.info).not.toHaveBeenCalled();
64
66
  expect(log.warn).not.toHaveBeenCalled();
@@ -131,6 +133,7 @@ A [JSON Schema](https://json-schema.org/) validator for the [JSON:API](https://j
131
133
 
132
134
  ```javascript
133
135
  export default {
136
+ toBeCalledAboveTrace,
134
137
  toBeCalledWithInitialParams,
135
138
  toBeClass,
136
139
  toBeJaypieError,
@@ -164,6 +167,18 @@ expect.extend(extendedMatchers);
164
167
  expect.extend(jaypieMatchers);
165
168
  ```
166
169
 
170
+ #### `expect(subject).toBeCalledAboveTrace()`
171
+
172
+ ```javascript
173
+ import { log } from "@jaypie/core";
174
+
175
+ log.trace("Hello, World!");
176
+ expect(log).not.toBeCalledAboveTrace();
177
+
178
+ log.warn("Look out, World!");
179
+ expect(log).toBeCalledAboveTrace();
180
+ ```
181
+
167
182
  #### `expect(subject).toBeJaypieError()`
168
183
 
169
184
  Validates instance objects:
@@ -315,6 +330,7 @@ const event = sqsTestRecords(
315
330
 
316
331
  | Date | Version | Summary |
317
332
  | ---------- | ------- | -------------- |
333
+ | 9/13/2024 | 1.0.27 | Matcher `toBeCalledAboveTrace` |
318
334
  | 7/16/2024 | 1.0.21 | Export Jaypie mock as default |
319
335
  | 3/20/2024 | 1.0.2 | Export `LOG` |
320
336
  | 3/16/2024 | 1.0.0 | Artists ship |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jaypie/testkit",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
4
4
  "author": "Finlayson Studio",
5
5
  "type": "module",
6
6
  "exports": {
@@ -24,12 +24,14 @@
24
24
  "new:test": "hygen jaypie vitest",
25
25
  "test": "vitest",
26
26
  "test:spec:constants": "vitest run ./src/__tests__/constants.spec.js",
27
+ "test:spec:expressHandler.mock": "vitest run ./src/__tests__/expressHandler-supertest.mock.spec.js",
27
28
  "test:spec:index": "vitest run ./src/__tests__/index.spec.js",
28
29
  "test:spec:jaypie.mock": "vitest run ./src/__tests__/jaypie.mock.spec.js",
29
30
  "test:spec:jsonApiSchema.module": "vitest run ./src/__tests__/jsonApiSchema.module.spec.js",
30
31
  "test:spec:matchers.module": "vitest run ./src/__tests__/matchers.module.spec.js",
31
32
  "test:spec:mockLog.module": "vitest run ./src/__tests__/mockLog.module.spec.js",
32
33
  "test:spec:sqsTestRecords.function": "vitest run ./src/__tests__/sqsTestRecords.function.spec.js",
34
+ "test:spec:toBeCalledAboveTrace.matcher": "vitest run ./src/matchers/__tests__/toBeCalledAboveTrace.matcher.spec.js",
33
35
  "test:spec:toBeCalledWithInitialParams.matcher": "vitest run ./src/matchers/__tests__/toBeCalledWithInitialParams.matcher.spec.js",
34
36
  "test:spec:toBeClass.matcher": "vitest run ./src/matchers/__tests__/toBeClass.matcher.spec.js",
35
37
  "test:spec:toBeJaypieError.matcher": "vitest run ./src/matchers/__tests__/toBeJaypieError.matcher.spec.js",
@@ -46,10 +48,12 @@
46
48
  "devDependencies": {
47
49
  "eslint": "^8.57.0",
48
50
  "eslint-config-jaypie": "^1.0.7",
51
+ "express": "^4.19.2",
49
52
  "hygen": "^6.2.11",
50
53
  "jest-extended": "^4.0.2",
51
54
  "prettier": "^3.2.5",
52
55
  "sort-package-json": "^2.8.0",
56
+ "supertest": "^7.0.0",
53
57
  "vitest": "^1.4.0"
54
58
  }
55
59
  }
@@ -6,6 +6,7 @@ import {
6
6
  JAYPIE,
7
7
  log,
8
8
  UnavailableError,
9
+ UnhandledError,
9
10
  } from "@jaypie/core";
10
11
  import { beforeAll, vi } from "vitest";
11
12
 
@@ -138,6 +139,12 @@ export const submitMetricSet = vi.fn(() => {
138
139
  // @jaypie/express
139
140
 
140
141
  export const expressHandler = vi.fn((handler, props = {}) => {
142
+ // If handler is an object and options is a function, swap them
143
+ if (typeof handler === "object" && typeof props === "function") {
144
+ const temp = handler;
145
+ handler = props;
146
+ props = temp;
147
+ }
141
148
  if (typeof handler !== "function") {
142
149
  throw new BadRequestError("handler must be a function");
143
150
  }
@@ -190,47 +197,65 @@ export const expressHandler = vi.fn((handler, props = {}) => {
190
197
  const jaypieFunction = jaypieHandler(handler, props);
191
198
  return async (req = {}, res = {}, ...extra) => {
192
199
  const status = HTTP.CODE.OK;
193
- if (res && typeof res.status === "function") {
194
- res.status(200);
200
+ let response;
201
+ let supertestMode = false;
202
+ if (
203
+ res &&
204
+ typeof res.socket === "object" &&
205
+ res.constructor.name === "ServerResponse"
206
+ ) {
207
+ // Use the response object in supertest mode
208
+ supertestMode = true;
195
209
  }
196
- const response = await jaypieFunction(req, res, ...extra);
197
- if (response) {
198
- if (typeof response === "object") {
199
- if (typeof response.json === "function") {
200
- if (res && typeof res.json === "function") {
201
- res.json(response.json());
202
- }
210
+ try {
211
+ response = await jaypieFunction(req, res, ...extra);
212
+ } catch (error) {
213
+ // In the mock context, if status is a function we are in a "supertest"
214
+ if (supertestMode) {
215
+ // In theory jaypieFunction has handled all errors
216
+ const errorStatus = error.status || HTTP.CODE.INTERNAL_SERVER_ERROR;
217
+ let errorResponse;
218
+ if (typeof error.json === "function") {
219
+ errorResponse = error.json();
203
220
  } else {
204
- if (res && typeof res.status === "function") {
221
+ // This should never happen
222
+ errorResponse = new UnhandledError().json();
223
+ }
224
+ res.status(errorStatus).json(errorResponse);
225
+ return;
226
+ } else {
227
+ // else, res.status is not a function, throw the error
228
+ throw error;
229
+ }
230
+ }
231
+ if (supertestMode) {
232
+ if (response) {
233
+ // res.status(200);
234
+ if (typeof response === "object") {
235
+ if (typeof response.json === "function") {
236
+ res.json(response.json());
237
+ } else {
205
238
  res.status(status).json(response);
206
239
  }
207
- }
208
- } else if (typeof response === "string") {
209
- try {
210
- if (res && typeof res.status === "function") {
240
+ } else if (typeof response === "string") {
241
+ try {
211
242
  res.status(status).json(JSON.parse(response));
243
+ } catch (error) {
244
+ if (supertestMode) {
245
+ res.status(status).send(response);
246
+ }
212
247
  }
213
- } catch (error) {
214
- if (res && typeof res.status === "function") {
215
- res.status(status).send(response);
216
- }
217
- }
218
- } else if (response === true) {
219
- if (res && typeof res.status === "function") {
248
+ } else if (response === true) {
220
249
  res.status(HTTP.CODE.CREATED).send();
221
- }
222
- } else {
223
- if (res && typeof res.status === "function") {
250
+ } else {
224
251
  res.status(status).send(response);
225
252
  }
226
- }
227
- } else {
228
- // No response
229
- if (res && typeof res.status === "function") {
253
+ } else {
230
254
  res.status(HTTP.CODE.NO_CONTENT).send();
231
255
  }
256
+ } else {
257
+ return response;
232
258
  }
233
- return response;
234
259
  };
235
260
  });
236
261
 
@@ -0,0 +1,47 @@
1
+ //
2
+ //
3
+ // Constants
4
+ //
5
+
6
+ //
7
+ //
8
+ // Helper Functions
9
+ //
10
+
11
+ //
12
+ //
13
+ // Main
14
+ //
15
+
16
+ const calledAboveTrace = (log) => {
17
+ // TODO: what if log is not an object?
18
+
19
+ try {
20
+ if (
21
+ log.debug.mock.calls.length > 0 ||
22
+ log.info.mock.calls.length > 0 ||
23
+ log.warn.mock.calls.length > 0 ||
24
+ log.error.mock.calls.length > 0 ||
25
+ log.fatal.mock.calls.length > 0
26
+ ) {
27
+ return {
28
+ message: () => `expected log not to have been called above trace`,
29
+ pass: true,
30
+ };
31
+ }
32
+ } catch (error) {
33
+ throw Error(`[calledAboveTrace] log is not a mock object`);
34
+ }
35
+
36
+ return {
37
+ message: () => `expected log not to have been called above trace`,
38
+ pass: false,
39
+ };
40
+ };
41
+
42
+ //
43
+ //
44
+ // Export
45
+ //
46
+
47
+ export default calledAboveTrace;
@@ -1,11 +1,6 @@
1
1
  import { matchers as jsonSchemaMatchers } from "jest-json-schema";
2
2
  import { jsonApiErrorSchema } from "../jsonApiSchema.module.js";
3
3
 
4
- //
5
- //
6
- // Constants
7
- //
8
-
9
4
  //
10
5
  //
11
6
  // Helper Functions
@@ -1,5 +1,6 @@
1
1
  import { matchers as jsonSchemaMatchers } from "jest-json-schema";
2
2
 
3
+ import toBeCalledAboveTrace from "./matchers/toBeCalledAboveTrace.matcher.js";
3
4
  import toBeCalledWithInitialParams from "./matchers/toBeCalledWithInitialParams.matcher.js";
4
5
  import toBeClass from "./matchers/toBeClass.matcher.js";
5
6
  import toBeJaypieError from "./matchers/toBeJaypieError.matcher.js";
@@ -28,6 +29,7 @@ import {
28
29
  //
29
30
 
30
31
  export default {
32
+ toBeCalledAboveTrace,
31
33
  toBeCalledWithInitialParams,
32
34
  toBeClass,
33
35
  toBeJaypieError,