ftmocks-utils 1.2.2 → 1.2.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ftmocks-utils",
3
- "version": "1.2.2",
3
+ "version": "1.2.5",
4
4
  "description": "Util functions for FtMocks",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -0,0 +1,171 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+
4
+ const charDifference = (str1, str2) => {
5
+ let count1 = {},
6
+ count2 = {};
7
+
8
+ for (let ch of str1) count1[ch] = (count1[ch] || 0) + 1;
9
+ for (let ch of str2) count2[ch] = (count2[ch] || 0) + 1;
10
+
11
+ let diff = 0;
12
+ let chars = new Set([...Object.keys(count1), ...Object.keys(count2)]);
13
+
14
+ for (let ch of chars) {
15
+ diff += Math.abs((count1[ch] || 0) - (count2[ch] || 0));
16
+ }
17
+
18
+ return diff;
19
+ };
20
+
21
+ const nameToFolder = (name) => {
22
+ return name.replaceAll(" ", "_");
23
+ };
24
+
25
+ const getMockDir = (config) => {
26
+ if (!path.isAbsolute(config.MOCK_DIR)) {
27
+ return path.resolve(process.cwd(), config.MOCK_DIR);
28
+ }
29
+ return config.MOCK_DIR;
30
+ };
31
+
32
+ const getFallbackDir = (config) => {
33
+ if (config.FALLBACK_DIR && !path.isAbsolute(config.FALLBACK_DIR)) {
34
+ return path.resolve(process.cwd(), config.FALLBACK_DIR);
35
+ }
36
+ return config.FALLBACK_DIR;
37
+ };
38
+
39
+ const capitalizeHeader = (header) => {
40
+ return header
41
+ .split("-")
42
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
43
+ .join("-");
44
+ };
45
+
46
+ const capitalizeHeaders = (headers) => {
47
+ return Object.fromEntries(
48
+ Object.entries(headers).map(([key, value]) => [
49
+ capitalizeHeader(key),
50
+ value,
51
+ ])
52
+ );
53
+ };
54
+
55
+ const areJsonEqual = (jsonObj1, jsonObj2) => {
56
+ // Check if both are objects and not null
57
+ if (
58
+ typeof jsonObj1 === "object" &&
59
+ jsonObj1 !== null &&
60
+ typeof jsonObj2 === "object" &&
61
+ jsonObj2 !== null
62
+ ) {
63
+ // Get the keys of both objects
64
+ const keys1 = Object.keys(jsonObj1);
65
+ const keys2 = Object.keys(jsonObj2);
66
+
67
+ // Check if the number of keys is different
68
+ if (keys1.length !== keys2.length) {
69
+ return false;
70
+ }
71
+
72
+ // Recursively check each key-value pair
73
+ for (let key of keys1) {
74
+ if (!keys2.includes(key) || !areJsonEqual(jsonObj1[key], jsonObj2[key])) {
75
+ return false;
76
+ }
77
+ }
78
+
79
+ return true;
80
+ } else {
81
+ // For non-object types, use strict equality comparison
82
+ return jsonObj1 === jsonObj2;
83
+ }
84
+ };
85
+
86
+ const clearNulls = (postData) => {
87
+ Object.keys(postData || {}).forEach((key) => {
88
+ if (postData[key] === null) {
89
+ delete postData[key];
90
+ }
91
+ });
92
+ };
93
+
94
+ const processURL = (url, ignoreParams = []) => {
95
+ // Remove the hostname from the URL
96
+ const urlWithoutHost = url.replace(/^(https?:\/\/)?[^\/]+/, "");
97
+ const processedURL = new URL(`http://domain.com${urlWithoutHost}`);
98
+ const params = new URLSearchParams(processedURL.search);
99
+ if (ignoreParams?.length > 0) {
100
+ ignoreParams.forEach((ip) => {
101
+ params.delete(ip);
102
+ });
103
+ }
104
+ params.sort();
105
+ return decodeURIComponent(`${processedURL.pathname}?${params}`);
106
+ };
107
+
108
+ const getHeaders = (headers) => {
109
+ let res = null;
110
+ try {
111
+ res = new Map([
112
+ ...Object.entries(headers),
113
+ ...Object.entries(capitalizeHeaders(headers)),
114
+ ]);
115
+ } catch (e) {
116
+ console.error("error at getHeaders", e);
117
+ res = new Map([
118
+ ["Content-Type", "application/json"],
119
+ ["content-type", "application/json"],
120
+ ]);
121
+ }
122
+ return Object.fromEntries(res);
123
+ };
124
+
125
+ function countFilesInDirectory(directoryPath) {
126
+ return new Promise((resolve, reject) => {
127
+ fs.readdir(directoryPath, (err, files) => {
128
+ if (err) {
129
+ return reject(err); // Handle error
130
+ }
131
+
132
+ // Filter out directories and only count files
133
+ const fileCount = files.filter((file) => {
134
+ const filePath = path.join(directoryPath, file);
135
+ return fs.statSync(filePath).isFile();
136
+ }).length;
137
+
138
+ resolve(fileCount);
139
+ });
140
+ });
141
+ }
142
+
143
+ const getTestByName = async (ftmocksConifg, testName) => {
144
+ const testsPath = path.join(getMockDir(ftmocksConifg), "tests.json");
145
+ let tests = [];
146
+ try {
147
+ // Read existing tests
148
+ const testsData = fs.readFileSync(testsPath, "utf8");
149
+ tests = JSON.parse(testsData);
150
+ const etest = tests.find((tst) => tst.name === testName);
151
+ return etest;
152
+ } catch (error) {
153
+ console.error(`\x1b[31mError reading tests.json:\x1b[0m`, error);
154
+ return null;
155
+ }
156
+ };
157
+
158
+ module.exports = {
159
+ charDifference,
160
+ nameToFolder,
161
+ getMockDir,
162
+ getFallbackDir,
163
+ capitalizeHeader,
164
+ capitalizeHeaders,
165
+ areJsonEqual,
166
+ clearNulls,
167
+ processURL,
168
+ getHeaders,
169
+ countFilesInDirectory,
170
+ getTestByName,
171
+ };
package/src/index.js CHANGED
@@ -1,105 +1,18 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { v4: uuidv4 } = require("uuid");
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
-
22
- const nameToFolder = (name) => {
23
- return name.replaceAll(" ", "_");
24
- };
25
-
26
- const getMockDir = (config) => {
27
- if (!path.isAbsolute(config.MOCK_DIR)) {
28
- return path.resolve(process.cwd(), config.MOCK_DIR);
29
- }
30
- return config.MOCK_DIR;
31
- };
32
-
33
- const getFallbackDir = (config) => {
34
- if (config.FALLBACK_DIR && !path.isAbsolute(config.FALLBACK_DIR)) {
35
- return path.resolve(process.cwd(), config.FALLBACK_DIR);
36
- }
37
- return config.FALLBACK_DIR;
38
- };
39
-
40
- const capitalizeHeader = (header) => {
41
- return header
42
- .split("-")
43
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
44
- .join("-");
45
- };
46
-
47
- const capitalizeHeaders = (headers) => {
48
- return Object.fromEntries(
49
- Object.entries(headers).map(([key, value]) => [
50
- capitalizeHeader(key),
51
- value,
52
- ])
53
- );
54
- };
55
-
56
- const getHeaders = (headers) => {
57
- let res = null;
58
- try {
59
- res = new Map([
60
- ...Object.entries(headers),
61
- ...Object.entries(capitalizeHeaders(headers)),
62
- ]);
63
- } catch (e) {
64
- console.error("error at getHeaders", e);
65
- res = new Map([
66
- ["Content-Type", "application/json"],
67
- ["content-type", "application/json"],
68
- ]);
69
- }
70
- return Object.fromEntries(res);
71
- };
72
-
73
- const areJsonEqual = (jsonObj1, jsonObj2) => {
74
- // Check if both are objects and not null
75
- if (
76
- typeof jsonObj1 === "object" &&
77
- jsonObj1 !== null &&
78
- typeof jsonObj2 === "object" &&
79
- jsonObj2 !== null
80
- ) {
81
- // Get the keys of both objects
82
- const keys1 = Object.keys(jsonObj1);
83
- const keys2 = Object.keys(jsonObj2);
84
-
85
- // Check if the number of keys is different
86
- if (keys1.length !== keys2.length) {
87
- return false;
88
- }
89
-
90
- // Recursively check each key-value pair
91
- for (let key of keys1) {
92
- if (!keys2.includes(key) || !areJsonEqual(jsonObj1[key], jsonObj2[key])) {
93
- return false;
94
- }
95
- }
96
-
97
- return true;
98
- } else {
99
- // For non-object types, use strict equality comparison
100
- return jsonObj1 === jsonObj2;
101
- }
102
- };
4
+ const {
5
+ charDifference,
6
+ nameToFolder,
7
+ getMockDir,
8
+ getFallbackDir,
9
+ areJsonEqual,
10
+ clearNulls,
11
+ processURL,
12
+ getHeaders,
13
+ countFilesInDirectory,
14
+ getTestByName,
15
+ } = require("./common-utils");
103
16
 
104
17
  const getDefaultMockDataFromConfig = (testConfig) => {
105
18
  const defaultPath = path.join(getMockDir(testConfig), "default.json");
@@ -174,14 +87,6 @@ const loadMockDataFromConfig = (testConfig, _testName) => {
174
87
  }
175
88
  };
176
89
 
177
- const clearNulls = (postData) => {
178
- Object.keys(postData || {}).forEach((key) => {
179
- if (postData[key] === null) {
180
- delete postData[key];
181
- }
182
- });
183
- };
184
-
185
90
  const isSameRequest = (req1, req2) => {
186
91
  clearNulls(req1.postData);
187
92
  clearNulls(req2.postData);
@@ -230,20 +135,6 @@ const getSameRequestRank = (req1, req2) => {
230
135
  return rank;
231
136
  };
232
137
 
233
- const processURL = (url, ignoreParams = []) => {
234
- // Remove the hostname from the URL
235
- const urlWithoutHost = url.replace(/^(https?:\/\/)?[^\/]+/, "");
236
- const processedURL = new URL(`http://domain.com${urlWithoutHost}`);
237
- const params = new URLSearchParams(processedURL.search);
238
- if (ignoreParams?.length > 0) {
239
- ignoreParams.forEach((ip) => {
240
- params.delete(ip);
241
- });
242
- }
243
- params.sort();
244
- return decodeURIComponent(`${processedURL.pathname}?${params}`);
245
- };
246
-
247
138
  function compareMockToRequest(mock, req) {
248
139
  const mockURL = processURL(
249
140
  mock.fileContent.url,
@@ -424,9 +315,13 @@ async function initiatePlaywrightRoutes(
424
315
  resetAllMockStats({ testMockData, testConfig: ftmocksConifg, testName });
425
316
  const test = await getTestByName(ftmocksConifg, testName);
426
317
  const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
427
- console.debug("calling initiatePlaywrightRoutes fetch");
318
+ console.debug("\x1b[32mcalling initiatePlaywrightRoutes fetch\x1b[0m");
319
+ let firstUrl = null;
428
320
  await page.route(mockPath, async (route, request) => {
429
321
  const url = request.url();
322
+ if (!firstUrl) {
323
+ firstUrl = url;
324
+ }
430
325
  const options = {
431
326
  url,
432
327
  method: request.method(),
@@ -436,12 +331,6 @@ async function initiatePlaywrightRoutes(
436
331
  await route.fallback();
437
332
  return;
438
333
  }
439
- console.debug(
440
- "got fetch request",
441
- request.method(),
442
- request.url(),
443
- request.postData()
444
- );
445
334
  let mockData = getMatchingMockData({
446
335
  testMockData,
447
336
  defaultMockData,
@@ -451,53 +340,140 @@ async function initiatePlaywrightRoutes(
451
340
  testName,
452
341
  mode: test.mode || "loose",
453
342
  });
454
- if (mockData) {
455
- console.debug("mocked", url, options);
456
- const { content, headers, status } = mockData.response;
457
-
458
- const json = {
459
- status,
460
- headers: getHeaders(headers),
461
- body: content,
462
- };
343
+ try {
344
+ if (mockData) {
345
+ const { content, headers, status, file } = mockData.response;
463
346
 
464
- await route.fulfill(json);
465
- } else {
466
- console.debug("missing mock data", url, options);
467
- const fallbackDir = getFallbackDir(ftmocksConifg);
468
- if (!fallbackDir) {
469
- await route.fallback();
470
- return;
471
- }
472
- const urlObj = new URL(route.request().url());
473
- const filePath = path.join(
474
- fallbackDir,
475
- urlObj.pathname === "/" || urlObj.pathname === ""
476
- ? ftmocksConifg.FALLBACK_DIR_INDEX_FILE || "index.html"
477
- : urlObj.pathname
478
- );
479
- console.debug("serving file ", filePath);
480
- if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
481
- const fileContent = fs.readFileSync(filePath);
482
- const ext = path.extname(filePath);
483
- const contentType =
484
- {
485
- ".html": "text/html",
486
- ".css": "text/css",
487
- ".js": "application/javascript",
488
- ".json": "application/json",
489
- ".png": "image/png",
490
- ".jpg": "image/jpeg",
491
- }[ext] || "application/octet-stream";
347
+ const json = {
348
+ status,
349
+ headers: getHeaders(headers),
350
+ body: content,
351
+ };
492
352
 
493
- console.debug("serving file", filePath);
494
- await route.fulfill({
495
- body: fileContent,
496
- headers: { "Content-Type": contentType },
497
- });
353
+ if (file) {
354
+ const filePath = path.join(
355
+ getMockDir(ftmocksConifg),
356
+ `test_${nameToFolder(testName)}`,
357
+ "_files",
358
+ file
359
+ );
360
+ if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
361
+ const fileContent = fs.readFileSync(filePath);
362
+ json.body = fileContent;
363
+
364
+ console.debug(
365
+ "\x1b[32mresponse is a file, serving file\x1b[0m",
366
+ filePath,
367
+ url
368
+ );
369
+ await route.fulfill(json);
370
+ }
371
+ } else {
372
+ await route.fulfill(json);
373
+ }
498
374
  } else {
499
- await route.fallback();
375
+ const fallbackDir = getFallbackDir(ftmocksConifg);
376
+ if (!fallbackDir) {
377
+ await route.fallback();
378
+ return;
379
+ }
380
+ const urlObj = new URL(route.request().url());
381
+ let filePath = path.join(
382
+ fallbackDir,
383
+ urlObj.pathname === "/" || urlObj.pathname === ""
384
+ ? ftmocksConifg.FALLBACK_DIR_INDEX_FILE || "index.html"
385
+ : urlObj.pathname
386
+ );
387
+
388
+ if (
389
+ !fs.existsSync(filePath) &&
390
+ !path.extname(filePath) &&
391
+ url === firstUrl
392
+ ) {
393
+ filePath = path.join(
394
+ fallbackDir,
395
+ ftmocksConifg.FALLBACK_DIR_INDEX_FILE_FOR_STATUS_404 || "index.html"
396
+ );
397
+ console.debug(
398
+ "\x1b[32mserving file for status 404\x1b[0m",
399
+ filePath,
400
+ url
401
+ );
402
+ }
403
+ if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
404
+ const fileContent = fs.readFileSync(filePath);
405
+ const ext = path.extname(filePath);
406
+ const contentType =
407
+ {
408
+ ".html": "text/html",
409
+ ".htm": "text/html",
410
+ ".xhtml": "application/xhtml+xml",
411
+ ".css": "text/css",
412
+ ".js": "application/javascript",
413
+ ".json": "application/json",
414
+ ".png": "image/png",
415
+ ".jpg": "image/jpeg",
416
+ ".svg": "image/svg+xml",
417
+ ".ico": "image/x-icon",
418
+ ".webp": "image/webp",
419
+ ".mp4": "video/mp4",
420
+ ".mp3": "audio/mpeg",
421
+ ".wav": "audio/wav",
422
+ ".ogg": "audio/ogg",
423
+ ".pdf": "application/pdf",
424
+ ".doc": "application/msword",
425
+ ".docx":
426
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
427
+ ".xls": "application/vnd.ms-excel",
428
+ ".xlsx":
429
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
430
+ ".ppt": "application/vnd.ms-powerpoint",
431
+ ".pptx":
432
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
433
+ ".zip": "application/zip",
434
+ ".rar": "application/x-rar-compressed",
435
+ ".7z": "application/x-7z-compressed",
436
+ ".tar": "application/x-tar",
437
+ ".gz": "application/gzip",
438
+ ".bz2": "application/x-bzip2",
439
+ ".xz": "application/x-xz",
440
+ ".exe": "application/x-msdownload",
441
+ ".dll": "application/x-msdownload",
442
+ ".so": "application/x-sharedlib",
443
+ ".dylib": "application/x-dynamiclib",
444
+ ".bin": "application/octet-stream",
445
+ ".txt": "text/plain",
446
+ ".csv": "text/csv",
447
+ ".tsv": "text/tab-separated-values",
448
+ ".xml": "application/xml",
449
+ ".xsl": "application/xml",
450
+ ".xslt": "application/xml",
451
+ ".xlt": "application/xml",
452
+ ".xltx": "application/xml",
453
+ ".xltm": "application/xml",
454
+ ".yaml": "text/yaml",
455
+ ".yml": "text/yaml",
456
+ ".toml": "text/toml",
457
+ ".php": "application/x-httpd-php",
458
+ }[ext] || "application/octet-stream";
459
+
460
+ console.debug("\x1b[32mserving file\x1b[0m", filePath);
461
+ await route.fulfill({
462
+ body: fileContent,
463
+ headers: { "Content-Type": contentType },
464
+ });
465
+ } else {
466
+ console.debug("\x1b[31mmissing mock data, falling back\x1b[0m", url);
467
+ await route.fallback();
468
+ }
500
469
  }
470
+ } catch (e) {
471
+ console.error(e);
472
+ console.error(
473
+ "\x1b[31merror at initiatePlaywrightRoutes\x1b[0m",
474
+ url,
475
+ options
476
+ );
501
477
  }
502
478
  });
503
479
  }
@@ -657,24 +633,6 @@ function initiateConsoleLogs(jest, ftmocksConifg, testName) {
657
633
  };
658
634
  }
659
635
 
660
- function countFilesInDirectory(directoryPath) {
661
- return new Promise((resolve, reject) => {
662
- fs.readdir(directoryPath, (err, files) => {
663
- if (err) {
664
- return reject(err); // Handle error
665
- }
666
-
667
- // Filter out directories and only count files
668
- const fileCount = files.filter((file) => {
669
- const filePath = path.join(directoryPath, file);
670
- return fs.statSync(filePath).isFile();
671
- }).length;
672
-
673
- resolve(fileCount);
674
- });
675
- });
676
- }
677
-
678
636
  const saveSnap = async (html, ftmocksConifg, testName) => {
679
637
  const snapFolder = path.join(
680
638
  getMockDir(ftmocksConifg),
@@ -739,21 +697,6 @@ function initiateJestEventSnaps(jest, ftmocksConifg, testName) {
739
697
  });
740
698
  }
741
699
 
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
-
757
700
  const createTest = async (ftmocksConifg, testName) => {
758
701
  const testsPath = path.join(getMockDir(ftmocksConifg), "tests.json");
759
702
  let tests = [];
@@ -776,13 +719,13 @@ const createTest = async (ftmocksConifg, testName) => {
776
719
  const mockListFilePath = path.join(folderPath, "_mock_list.json");
777
720
  fs.mkdir(folderPath, { recursive: true }, (err) => {
778
721
  if (err) {
779
- console.error("Error creating directory:", err);
722
+ console.error("\x1b[31mError creating directory:\x1b[0m", err);
780
723
  } else {
781
- console.log("Directory created successfully!");
724
+ console.log("\x1b[32mDirectory created successfully!\x1b[0m");
782
725
  }
783
726
  });
784
727
  await fs.appendFile(mockListFilePath, "[]", () => {
785
- console.log("mock list file created successfully");
728
+ console.log("\x1b[32mmock list file created successfully\x1b[0m");
786
729
  });
787
730
 
788
731
  return newTest;
@@ -790,7 +733,7 @@ const createTest = async (ftmocksConifg, testName) => {
790
733
  throw "Test already exists";
791
734
  }
792
735
  } catch (error) {
793
- console.error(`Error reading tests.json:`, error);
736
+ console.error(`\x1b[31mError reading tests.json:\x1b[0m`, error);
794
737
  return null;
795
738
  }
796
739
  };