http-snapshotter 0.5.3 → 0.6.0-beta.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.
package/README.md CHANGED
@@ -20,24 +20,25 @@ Example (test.js):
20
20
 
21
21
  ```js
22
22
  import test from "tape";
23
- import { fileURLToPath } from "node:url";
24
- import { resolve, dirname } from "node:path";
25
23
  import { start, startTestCase, endTestCase } from "http-snapshotter";
24
+ import { resolve } from "node:path";
26
25
 
27
- const __dirname = dirname(fileURLToPath(import.meta.url));
28
26
  // if you are using an isolated test runner, then use a different directory per test (e.g. http-snapshots/test-case-1)
29
- start({ snapshotDirectory: resolve(__dirname, "http-snapshots") });
27
+ // you can run this only once per process
28
+ start({ snapshotDirectory: resolve(import.meta.dirname, "http-snapshots") });
30
29
 
31
30
  test("Latest XKCD comic (ESM)", async (t) => {
32
31
  // if you are *not* using an isolated test runner (e.g. tape), then `startTestCase` adds snapshots to separate directory
33
32
  // Remove this line if it doesn't apply to your test runner
34
33
  startTestCase('test-case-1');
35
34
 
36
- const res = await fetch("https://xkcd.com/info.0.json");
37
- const json = await res.json();
38
-
39
- t.deepEquals(json.title, "Iceberg Efficiency", "must be equal");
40
- endTestCase();
35
+ try {
36
+ const res = await fetch("https://xkcd.com/info.0.json");
37
+ const json = await res.json();
38
+ t.deepEquals(json.title, "Iceberg Efficiency", "must be equal");
39
+ } finally {
40
+ endTestCase();
41
+ }
41
42
  });
42
43
  ```
43
44
 
@@ -68,6 +69,8 @@ The tests of this library uses this library itself, check the `tests/` directory
68
69
 
69
70
  2. Stripe SDK uses node http module in a way MSW interceptor cannot mock - [reference](https://github.com/mswjs/msw/issues/2259#issuecomment-2379379566). Solution mentioned in a comment there is to use Stripe SDK with `fetch` client - [reference](https://github.com/mswjs/msw/issues/2259#issuecomment-2422672039). Another workaround is to mock Stripe SDK's methods with your testing library of choice.
70
71
 
72
+ 3. You can't use MSW along with this library, because there can only be one MSW interceptor running.
73
+
71
74
  ## About snapshot files and its names
72
75
 
73
76
  A snapshot file name uniquely identifies a request. By default it is a combination of HTTP method + URL + body that makes a request unique (headers are ignored).
package/index.js CHANGED
@@ -98,6 +98,19 @@ let snapshotDirectory = null;
98
98
 
99
99
  const dynamodbHostNameRegex = /^(?:dynamodb|\d+\.ddb)\.([^.]+)\.amazonaws\.com$/;
100
100
 
101
+ /**
102
+ * Stable JSON stringify with sorted keys for deterministic hashing
103
+ * @param {any} val
104
+ * @returns {string}
105
+ */
106
+ function stableStringify(val) {
107
+ if (typeof val !== 'object' || val === null) return JSON.stringify(val);
108
+ if (Array.isArray(val)) return '[' + val.map(stableStringify).join(',') + ']';
109
+ return '{' + Object.keys(val).sort().map(k =>
110
+ JSON.stringify(k) + ':' + stableStringify(val[k])
111
+ ).join(',') + '}';
112
+ }
113
+
101
114
  const defaultKeyDerivationProps = ['method', 'url', 'body'];
102
115
  /**
103
116
  * @param {Request} request
@@ -124,8 +137,16 @@ async function defaultSnapshotFileNameGenerator(request) {
124
137
 
125
138
  // Input data
126
139
  const dataList = await Promise.all(
127
- defaultKeyDerivationProps.map((key) => {
140
+ defaultKeyDerivationProps.map(async (key) => {
128
141
  if (key === 'body') {
142
+ const contentType = request.headers.get('content-type') || '';
143
+ if (contentType.includes('application/json') || contentType.includes('application/x-amz-json')) {
144
+ try {
145
+ return stableStringify(await request.clone().json());
146
+ } catch (e) {
147
+ // Not valid JSON, fall back to text
148
+ }
149
+ }
129
150
  return request.clone().text();
130
151
  }
131
152
  //@ts-ignore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "http-snapshotter",
3
- "version": "0.5.3",
3
+ "version": "0.6.0-beta.1",
4
4
  "description": "Snapshot HTTP requests for tests (node.js)",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -12,6 +12,11 @@
12
12
  "publishConfig": {
13
13
  "access": "public"
14
14
  },
15
+ "files": [
16
+ "index.js",
17
+ "index.mjs",
18
+ "index.d.ts"
19
+ ],
15
20
  "scripts": {
16
21
  "test": "tape tests/**/*.test.* | tap-arc",
17
22
  "tsc": "tsc"
package/intercept.js DELETED
@@ -1,78 +0,0 @@
1
- /*
2
- * This function was created with a realization that once you override a method for mocking
3
- * and run a test, you can't undo the override, because shared code (test runner without
4
- * test isolation) will hold on to closures to the overridden function.
5
- *
6
- * So a solution it to intercept once before all tests, mock for a test and "undoMock()" at
7
- * end of a test will cause the intercept to "proxy" future calls to the original method.
8
- */
9
-
10
- const mocked = new Set();
11
-
12
- /**
13
- * Extracts the keys of an object where the values of the keys are functions.
14
- * @template T The object type.
15
- * @typedef {Extract<
16
- * keyof T,
17
- * { [K in keyof T]: T[K] extends Function ? K : never }[keyof T]
18
- * >} FunctionKeys
19
- */
20
-
21
- /**
22
- * @template T
23
- * @template {FunctionKeys<T>} K
24
- * @param {T} object
25
- * @param {K} methodName
26
- */
27
- function intercept(object, methodName) {
28
- let mockHandler = null;
29
- const originalMethod = object[methodName];
30
- // @ts-ignore
31
- const boundedMethod = object[methodName].bind(object);
32
-
33
- // @ts-ignore
34
- // eslint-disable-next-line no-param-reassign
35
- object[methodName] = function interceptedFunction(...args) {
36
- return mockHandler
37
- ? mockHandler(boundedMethod, ...args)
38
- // @ts-ignore
39
- : originalMethod.apply(object, args);
40
- };
41
-
42
- return {
43
- /**
44
- * @param {(originalMethod: T[K], ...args: Parameters<T[K]>) => any} mockFunction
45
- */
46
- mock(mockFunction) {
47
- mockHandler = mockFunction;
48
- mocked.add(this);
49
- },
50
- undoMock() {
51
- mockHandler = null;
52
- mocked.delete(this);
53
- },
54
- /**
55
- * Un-intercepting will not get rid of closures to interceptedFunction
56
- */
57
- destroy() {
58
- mockHandler = null;
59
- // eslint-disable-next-line no-param-reassign
60
- object[methodName] = originalMethod;
61
- },
62
- };
63
- }
64
-
65
- function undoAllMocks() {
66
- [...mocked].forEach((methods) => methods.undoMock());
67
- }
68
-
69
- const interceptAllMethods = (object) => Object.fromEntries(
70
- Object
71
- .keys(object)
72
- .filter((key) => typeof object[key] === 'function')
73
- .map(
74
- (key) => [key, intercept(object, key)],
75
- ),
76
- );
77
-
78
- module.exports = { intercept, undoAllMocks, interceptAllMethods };
package/playground.js DELETED
@@ -1,41 +0,0 @@
1
- const { diffChars } = require('diff');
2
-
3
- function findMostSimilar(target, candidates) {
4
- // Function to calculate similarity score based on diff
5
- function getSimilarityScore(str1, str2) {
6
- const changes = diffChars(str1, str2);
7
- let totalChanges = 0;
8
- let totalLength = 0;
9
-
10
- changes.forEach(change => {
11
- if (change.added || change.removed) {
12
- totalChanges += change.value.length;
13
- }
14
- totalLength += change.value.length;
15
- });
16
-
17
- // Return similarity as a percentage (100 = identical, 0 = completely different)
18
- return 100 * (1 - totalChanges / totalLength);
19
- }
20
-
21
- // Calculate scores for all candidates
22
- const scores = candidates.map(candidate => ({
23
- string: candidate,
24
- score: getSimilarityScore(target, candidate)
25
- }));
26
-
27
- // Sort by score descending and return the highest scoring match
28
- return scores.sort((a, b) => b.score - a.score)[0];
29
- }
30
-
31
- // Example usage
32
- const target = "hello world";
33
- const candidates = [
34
- "hello word",
35
- "hello there",
36
- "hi world",
37
- "completely different"
38
- ];
39
-
40
- const result = findMostSimilar(target, candidates);
41
- console.log(`Most similar to "${target}" is "${result.string}" with score ${result.score.toFixed(2)}%`);