ftmocks-utils 1.1.8 → 1.2.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +132 -7
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ftmocks-utils",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "description": "Util functions for FtMocks",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -2,6 +2,23 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { v4: uuidv4 } = require("uuid");
4
4
 
5
+ function charDifference(str1, str2) {
6
+ let count1 = {},
7
+ count2 = {};
8
+
9
+ for (let ch of str1) count1[ch] = (count1[ch] || 0) + 1;
10
+ for (let ch of str2) count2[ch] = (count2[ch] || 0) + 1;
11
+
12
+ let diff = 0;
13
+ let chars = new Set([...Object.keys(count1), ...Object.keys(count2)]);
14
+
15
+ for (let ch of chars) {
16
+ diff += Math.abs((count1[ch] || 0) - (count2[ch] || 0));
17
+ }
18
+
19
+ return diff;
20
+ }
21
+
5
22
  const nameToFolder = (name) => {
6
23
  return name.replaceAll(" ", "_");
7
24
  };
@@ -44,7 +61,7 @@ const getHeaders = (headers) => {
44
61
  ...Object.entries(capitalizeHeaders(headers)),
45
62
  ]);
46
63
  } catch (e) {
47
- console.debug("error at getHeaders", e);
64
+ console.error("error at getHeaders", e);
48
65
  res = new Map([
49
66
  ["Content-Type", "application/json"],
50
67
  ["content-type", "application/json"],
@@ -102,7 +119,7 @@ const getDefaultMockDataFromConfig = (testConfig) => {
102
119
  const mockData = fs.readFileSync(mockFilePath, "utf8");
103
120
  entry.fileContent = JSON.parse(mockData);
104
121
  } catch (error) {
105
- console.error(`Error reading mock data for ${entry.path}:`, error);
122
+ console.error(`Error reading mock data for ${entry.id}:`, error);
106
123
  return entry; // Return the original entry if there's an error
107
124
  }
108
125
  });
@@ -152,7 +169,7 @@ const loadMockDataFromConfig = (testConfig, _testName) => {
152
169
 
153
170
  return mocks;
154
171
  } catch (error) {
155
- console.debug("Error loading test data:", error.message);
172
+ console.error("Error loading test data:", error.message);
156
173
  return [];
157
174
  }
158
175
  };
@@ -188,6 +205,31 @@ const isSameRequest = (req1, req2) => {
188
205
  return matched;
189
206
  };
190
207
 
208
+ const getSameRequestRank = (req1, req2) => {
209
+ let rank = 1;
210
+ clearNulls(req1.postData);
211
+ clearNulls(req2.postData);
212
+ // Compare path names
213
+ const url1 = new URL(`http://domain.com${req1.url}`);
214
+ const url2 = new URL(`http://domain.com${req2.url}`);
215
+ if (url1.pathname !== url2.pathname) {
216
+ rank = 0;
217
+ } else if (url1.method?.toLowerCase() !== url2.method?.toLowerCase()) {
218
+ rank = 0;
219
+ } else {
220
+ // Compare query strings
221
+ const queryDiff = charDifference(url1.search || "", url2.search || "");
222
+ rank = rank + queryDiff;
223
+ // Compare post data
224
+ const charDiff = charDifference(
225
+ JSON.stringify(req1.postData || {}),
226
+ JSON.stringify(req2.postData || {})
227
+ );
228
+ rank = rank + charDiff;
229
+ }
230
+ return rank;
231
+ };
232
+
191
233
  const processURL = (url, ignoreParams = []) => {
192
234
  // Remove the hostname from the URL
193
235
  const urlWithoutHost = url.replace(/^(https?:\/\/)?[^\/]+/, "");
@@ -242,8 +284,35 @@ function compareMockToFetchRequest(mock, fetchReq) {
242
284
  }
243
285
  );
244
286
  } catch (e) {
245
- console.debug("error at compareMockToFetchRequest", mock, fetchReq);
246
- console.debug(e);
287
+ console.error("error at compareMockToFetchRequest", mock, fetchReq);
288
+ console.error(e);
289
+ }
290
+ return false;
291
+ }
292
+
293
+ function getCompareRankMockToFetchRequest(mock, fetchReq) {
294
+ try {
295
+ const mockURL = processURL(
296
+ mock.fileContent.url,
297
+ mock.fileContent.ignoreParams
298
+ );
299
+ const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
300
+ const postData = mock.fileContent.request?.postData?.text
301
+ ? JSON.parse(mock.fileContent.request?.postData?.text)
302
+ : mock.fileContent.request?.postData;
303
+ return getSameRequestRank(
304
+ { url: mockURL, method: mock.fileContent.method, postData },
305
+ {
306
+ method: fetchReq.options.method || "GET",
307
+ postData: fetchReq.options.body?.length
308
+ ? JSON.parse(fetchReq.options.body)
309
+ : fetchReq.options.body,
310
+ url: reqURL,
311
+ }
312
+ );
313
+ } catch (e) {
314
+ console.error("error at getCompareRankMockToFetchRequest", mock, fetchReq);
315
+ console.error(e);
247
316
  }
248
317
  return false;
249
318
  }
@@ -255,6 +324,7 @@ function getMatchingMockData({
255
324
  options,
256
325
  testConfig,
257
326
  testName,
327
+ mode,
258
328
  }) {
259
329
  let served = false;
260
330
  let matchedMocks =
@@ -290,6 +360,38 @@ function getMatchingMockData({
290
360
  })
291
361
  );
292
362
  }
363
+
364
+ if (!foundMock && mode !== "strict") {
365
+ const mockRanks = {};
366
+ testMockData.forEach((tm) => {
367
+ const rank = getCompareRankMockToFetchRequest(tm, {
368
+ url,
369
+ options,
370
+ });
371
+ if (rank > 0) {
372
+ mockRanks[tm.id] = rank;
373
+ }
374
+ });
375
+ defaultMockData.forEach((tm) => {
376
+ const rank = getCompareRankMockToFetchRequest(tm, {
377
+ url,
378
+ options,
379
+ });
380
+ if (rank > 0) {
381
+ mockRanks[tm.id] = rank;
382
+ }
383
+ });
384
+ // Sort by rank to find the best match
385
+ const sortedRanks = Object.entries(mockRanks).sort((a, b) => a[1] - b[1]);
386
+ if (sortedRanks.length > 0) {
387
+ const bestMockId = sortedRanks?.[0]?.[0];
388
+ if (bestMockId) {
389
+ foundMock = [...testMockData, ...defaultMockData].find(
390
+ (mock) => mock.id === bestMockId
391
+ );
392
+ }
393
+ }
394
+ }
293
395
  return foundMock ? foundMock.fileContent : null;
294
396
  }
295
397
 
@@ -320,6 +422,7 @@ async function initiatePlaywrightRoutes(
320
422
  ? loadMockDataFromConfig(ftmocksConifg, testName)
321
423
  : [];
322
424
  resetAllMockStats({ testMockData, testConfig: ftmocksConifg, testName });
425
+ const test = await getTestByName(ftmocksConifg, config.testName);
323
426
  const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
324
427
  console.debug("calling initiatePlaywrightRoutes fetch");
325
428
  await page.route(mockPath, async (route, request) => {
@@ -346,6 +449,7 @@ async function initiatePlaywrightRoutes(
346
449
  options,
347
450
  testConfig: ftmocksConifg,
348
451
  testName,
452
+ mode: test.mode || "loose",
349
453
  });
350
454
  if (mockData) {
351
455
  console.debug("mocked", url, options);
@@ -635,6 +739,21 @@ function initiateJestEventSnaps(jest, ftmocksConifg, testName) {
635
739
  });
636
740
  }
637
741
 
742
+ const getTestByName = async (ftmocksConifg, testName) => {
743
+ const testsPath = path.join(getMockDir(ftmocksConifg), "tests.json");
744
+ let tests = [];
745
+ try {
746
+ // Read existing tests
747
+ const testsData = fs.readFileSync(testsPath, "utf8");
748
+ tests = JSON.parse(testsData);
749
+ const etest = tests.find((tst) => tst.name === testName);
750
+ return etest;
751
+ } catch (error) {
752
+ console.error(`Error reading tests.json:`, error);
753
+ return null;
754
+ }
755
+ };
756
+
638
757
  const createTest = async (ftmocksConifg, testName) => {
639
758
  const testsPath = path.join(getMockDir(ftmocksConifg), "tests.json");
640
759
  let tests = [];
@@ -763,8 +882,8 @@ async function recordPlaywrightRoutes(
763
882
  testName,
764
883
  mockPath: "**/*",
765
884
  pattern: "^/api/.*",
766
- avoidDuplicatesInTheTest: true,
767
- avoidDuplicatesWithDefaultMocks: true,
885
+ avoidDuplicatesInTheTest: false,
886
+ avoidDuplicatesWithDefaultMocks: false,
768
887
  }
769
888
  ) {
770
889
  await page.route(config.mockPath, async (route) => {
@@ -778,6 +897,11 @@ async function recordPlaywrightRoutes(
778
897
  }
779
898
  }
780
899
 
900
+ const test = await getTestByName(ftmocksConifg, config.testName);
901
+ if (!test) {
902
+ await createTest(ftmocksConifg, config.testName);
903
+ }
904
+
781
905
  if (await saveIfItIsFile(route, config.testName, ftmocksConifg)) {
782
906
  return;
783
907
  }
@@ -883,6 +1007,7 @@ async function recordPlaywrightRoutes(
883
1007
 
884
1008
  // Export functions as a module
885
1009
  module.exports = {
1010
+ getTestByName,
886
1011
  compareMockToRequest,
887
1012
  processURL,
888
1013
  isSameRequest,