ftmocks-utils 1.3.0 → 1.3.2

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.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "Util functions for FtMocks",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -1,21 +1,31 @@
1
1
  const path = require("path");
2
2
  const fs = require("fs");
3
+ const { FtJSON } = require("./json-utils");
3
4
 
4
5
  const charDifference = (str1, str2) => {
5
- let count1 = {},
6
- count2 = {};
6
+ if (str1 && str2) {
7
+ let count1 = {};
8
+ let count2 = {};
7
9
 
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
+ for (let ch of str1) count1[ch] = (count1[ch] || 0) + 1;
11
+ for (let ch of str2) count2[ch] = (count2[ch] || 0) + 1;
10
12
 
11
- let diff = 0;
12
- let chars = new Set([...Object.keys(count1), ...Object.keys(count2)]);
13
+ let diff = 0;
14
+ let chars = new Set([...Object.keys(count1), ...Object.keys(count2)]);
13
15
 
14
- for (let ch of chars) {
15
- diff += Math.abs((count1[ch] || 0) - (count2[ch] || 0));
16
- }
16
+ for (let ch of chars) {
17
+ diff += Math.abs((count1[ch] || 0) - (count2[ch] || 0));
18
+ }
17
19
 
18
- return diff;
20
+ return diff;
21
+ } else {
22
+ if (!str1 && str2) {
23
+ return str2.length;
24
+ } else if (str1 && !str2) {
25
+ return str1.length;
26
+ }
27
+ return 0;
28
+ }
19
29
  };
20
30
 
21
31
  const nameToFolder = (name) => {
@@ -52,37 +62,6 @@ const capitalizeHeaders = (headers) => {
52
62
  );
53
63
  };
54
64
 
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
65
  const clearNulls = (postData) => {
87
66
  Object.keys(postData || {}).forEach((key) => {
88
67
  if (postData[key] === null) {
@@ -162,7 +141,6 @@ module.exports = {
162
141
  getFallbackDir,
163
142
  capitalizeHeader,
164
143
  capitalizeHeaders,
165
- areJsonEqual,
166
144
  clearNulls,
167
145
  processURL,
168
146
  getHeaders,
package/src/index.js CHANGED
@@ -6,13 +6,16 @@ const {
6
6
  nameToFolder,
7
7
  getMockDir,
8
8
  getFallbackDir,
9
- areJsonEqual,
10
9
  clearNulls,
11
10
  processURL,
12
11
  getHeaders,
13
12
  countFilesInDirectory,
14
13
  getTestByName,
15
14
  } = require("./common-utils");
15
+ const { FtJSON } = require("./json-utils");
16
+ const Logger = require("./log-utils");
17
+
18
+ let logger = null;
16
19
 
17
20
  const getDefaultMockDataFromConfig = (testConfig) => {
18
21
  const defaultPath = path.join(
@@ -103,11 +106,11 @@ const isSameRequest = (req1, req2) => {
103
106
  (!req1.postData && req2.postData) ||
104
107
  (req1.postData && !req2.postData)
105
108
  ) {
106
- matched = areJsonEqual(req1.postData || {}, req2.postData || {});
109
+ matched = FtJSON.areJsonEqual(req1.postData || {}, req2.postData || {});
107
110
  } else if (
108
111
  req1.postData &&
109
112
  req2.postData &&
110
- !areJsonEqual(req1.postData, req2.postData)
113
+ !FtJSON.areJsonEqual(req1.postData, req2.postData)
111
114
  ) {
112
115
  matched = false;
113
116
  }
@@ -131,8 +134,8 @@ const getSameRequestRank = (req1, req2) => {
131
134
  rank = rank + queryDiff;
132
135
  // Compare post data
133
136
  const charDiff = charDifference(
134
- JSON.stringify(req1.postData || {}),
135
- JSON.stringify(req2.postData || {})
137
+ FtJSON.stringify(req1.postData || {}),
138
+ FtJSON.stringify(req2.postData || {})
136
139
  );
137
140
  rank = rank + charDiff;
138
141
  }
@@ -146,7 +149,7 @@ function compareMockToRequest(mock, req) {
146
149
  );
147
150
  const reqURL = processURL(req.originalUrl, mock.fileContent.ignoreParams);
148
151
  const postData = mock.fileContent.request?.postData?.text
149
- ? JSON.parse(mock.fileContent.request?.postData?.text)
152
+ ? FtJSON.parse(mock.fileContent.request?.postData?.text)
150
153
  : mock.fileContent.request?.postData;
151
154
  return isSameRequest(
152
155
  { url: mockURL, method: mock.fileContent.method, postData },
@@ -166,14 +169,14 @@ function compareMockToFetchRequest(mock, fetchReq) {
166
169
  );
167
170
  const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
168
171
  const postData = mock.fileContent.request?.postData?.text
169
- ? JSON.parse(mock.fileContent.request?.postData?.text)
172
+ ? FtJSON.parse(mock.fileContent.request?.postData?.text)
170
173
  : mock.fileContent.request?.postData;
171
174
  return isSameRequest(
172
175
  { url: mockURL, method: mock.fileContent.method, postData },
173
176
  {
174
177
  method: fetchReq.options.method || "GET",
175
178
  postData: fetchReq.options.body?.length
176
- ? JSON.parse(fetchReq.options.body)
179
+ ? FtJSON.parse(fetchReq.options.body)
177
180
  : fetchReq.options.body,
178
181
  url: reqURL,
179
182
  }
@@ -193,14 +196,14 @@ function getCompareRankMockToFetchRequest(mock, fetchReq) {
193
196
  );
194
197
  const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
195
198
  const postData = mock.fileContent.request?.postData?.text
196
- ? JSON.parse(mock.fileContent.request?.postData?.text)
199
+ ? FtJSON.parse(mock.fileContent.request?.postData?.text)
197
200
  : mock.fileContent.request?.postData;
198
201
  return getSameRequestRank(
199
202
  { url: mockURL, method: mock.fileContent.method, postData },
200
203
  {
201
204
  method: fetchReq.options.method || "GET",
202
205
  postData: fetchReq.options.body?.length
203
- ? JSON.parse(fetchReq.options.body)
206
+ ? FtJSON.parse(fetchReq.options.body)
204
207
  : fetchReq.options.body,
205
208
  url: reqURL,
206
209
  }
@@ -320,13 +323,14 @@ async function initiatePlaywrightRoutes(
320
323
  mockPath = "**/*",
321
324
  excludeMockPath = null
322
325
  ) {
326
+ logger = new Logger({disableLogs: ftmocksConifg.DISABLE_LOGS}, ftmocksConifg, testName);
323
327
  const testMockData = testName
324
328
  ? loadMockDataFromConfig(ftmocksConifg, testName)
325
329
  : [];
326
330
  resetAllMockStats({ testMockData, testConfig: ftmocksConifg, testName });
327
331
  const test = await getTestByName(ftmocksConifg, testName);
328
332
  const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
329
- console.debug("\x1b[32mcalling initiatePlaywrightRoutes fetch\x1b[0m");
333
+ logger.debug("\x1b[32mcalling initiatePlaywrightRoutes fetch\x1b[0m");
330
334
  let firstUrl = null;
331
335
  await page.route(mockPath, async (route, request) => {
332
336
  const url = request.url();
@@ -413,7 +417,7 @@ async function initiatePlaywrightRoutes(
413
417
  fallbackDir,
414
418
  ftmocksConifg.FALLBACK_DIR_INDEX_FILE_FOR_STATUS_404 || "index.html"
415
419
  );
416
- console.debug(
420
+ logger.debug(
417
421
  "\x1b[32mserving file for status 404\x1b[0m",
418
422
  filePath,
419
423
  url
@@ -476,19 +480,19 @@ async function initiatePlaywrightRoutes(
476
480
  ".php": "application/x-httpd-php",
477
481
  }[ext] || "application/octet-stream";
478
482
 
479
- console.debug("\x1b[32mserving file\x1b[0m", filePath);
483
+ logger.info("\x1b[32mserving file\x1b[0m", filePath);
480
484
  await route.fulfill({
481
485
  body: fileContent,
482
486
  headers: { "Content-Type": contentType },
483
487
  });
484
488
  } else {
485
- console.debug("\x1b[31mmissing mock data, falling back\x1b[0m", url);
489
+ logger.debug("\x1b[31mmissing mock data, falling back\x1b[0m", url);
486
490
  await route.fallback();
487
491
  }
488
492
  }
489
493
  } catch (e) {
490
- console.error(e);
491
- console.error(
494
+ logger.error(e);
495
+ logger.error(
492
496
  "\x1b[31merror at initiatePlaywrightRoutes\x1b[0m",
493
497
  url,
494
498
  options
@@ -530,7 +534,7 @@ async function initiateJestFetch(jest, ftmocksConifg, testName) {
530
534
  return Promise.resolve({
531
535
  status,
532
536
  headers: new Map(Object.entries(headers)),
533
- json: () => Promise.resolve(JSON.parse(content)),
537
+ json: () => Promise.resolve(FtJSON.parse(content)),
534
538
  });
535
539
  });
536
540
 
@@ -767,17 +771,17 @@ const isSameResponse = (req1, req2) => {
767
771
  (!req1.response.content && req2.response.content) ||
768
772
  (req1.response.content && !req2.response.content)
769
773
  ) {
770
- matched = areJsonEqual(
771
- JSON.parse(req1.response.content) || {},
772
- JSON.parse(req2.response.content) || {}
774
+ matched = FtJSON.areJsonEqual(
775
+ FtJSON.parse(req1.response.content) || {},
776
+ FtJSON.parse(req2.response.content) || {}
773
777
  );
774
778
  // console.log('not matched at post Data 0', req1.postData, req2.postData);
775
779
  } else if (
776
780
  req1.response.content &&
777
781
  req2.response.content &&
778
- !areJsonEqual(
779
- JSON.parse(req1.response.content) || {},
780
- JSON.parse(req2.response.content) || {}
782
+ !FtJSON.areJsonEqual(
783
+ FtJSON.parse(req1.response.content) || {},
784
+ FtJSON.parse(req2.response.content) || {}
781
785
  )
782
786
  ) {
783
787
  matched = false;
@@ -0,0 +1,55 @@
1
+ class FtJSON {
2
+ static parse(text, reviver, reference) {
3
+ try {
4
+ return JSON.parse(text, reviver);
5
+ } catch (error) {
6
+ console.error("FtJSON parse error:", error, reference);
7
+ return text;
8
+ }
9
+ }
10
+
11
+ static stringify(value, replacer, space, reference) {
12
+ try {
13
+ return JSON.stringify(value, replacer, space);
14
+ } catch (error) {
15
+ console.error("FtJSON stringify error:", error, reference);
16
+ return value;
17
+ }
18
+ }
19
+
20
+ static areJsonEqual(jsonObj1, jsonObj2) {
21
+ // Check if both are objects and not null
22
+ if (
23
+ typeof jsonObj1 === "object" &&
24
+ jsonObj1 !== null &&
25
+ typeof jsonObj2 === "object" &&
26
+ jsonObj2 !== null
27
+ ) {
28
+ // Get the keys of both objects
29
+ const keys1 = Object.keys(jsonObj1);
30
+ const keys2 = Object.keys(jsonObj2);
31
+
32
+ // Check if the number of keys is different
33
+ if (keys1.length !== keys2.length) {
34
+ return false;
35
+ }
36
+
37
+ // Recursively check each key-value pair
38
+ for (let key of keys1) {
39
+ if (
40
+ !keys2.includes(key) ||
41
+ !FtJSON.areJsonEqual(jsonObj1[key], jsonObj2[key])
42
+ ) {
43
+ return false;
44
+ }
45
+ }
46
+
47
+ return true;
48
+ } else {
49
+ // For non-object types, use strict equality comparison
50
+ return jsonObj1 === jsonObj2;
51
+ }
52
+ }
53
+ }
54
+
55
+ module.exports = { FtJSON };
@@ -0,0 +1,90 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+ const { getMockDir, nameToFolder } = require("./common-utils");
4
+
5
+ class Logger {
6
+ constructor(options = {}, ftmocksConifg, testName) {
7
+ this.levels = ["error", "warn", "info", "debug"];
8
+ this.level = options.level || "info";
9
+ this.disableLogs = options.disableLogs || false;
10
+ this.logsFile = path.join(
11
+ getMockDir(ftmocksConifg),
12
+ `test_${nameToFolder(testName)}`,
13
+ "_logs.json"
14
+ );
15
+ this.logs = [];
16
+ }
17
+
18
+ setLevel(level) {
19
+ if (this.levels.includes(level)) {
20
+ this.level = level;
21
+ }
22
+ }
23
+
24
+ writeToFile(type, params) {
25
+ try {
26
+ const logMessage = params.join(" ") + "\n"; // Combine params into a string with spaces
27
+ this.logs.push({
28
+ type,
29
+ message: logMessage,
30
+ time: Date.now(),
31
+ source: "ftmocks-utils",
32
+ });
33
+ fs.writeFileSync(this.logsFile, JSON.stringify(this.logs, null, 2), "utf8"); // Append the log message to the file
34
+ } catch (error) {
35
+ // Ignore error
36
+ }
37
+ }
38
+
39
+ log(level, ...args) {
40
+ if (this.disableLogs) return;
41
+ const levelIdx = this.levels.indexOf(level);
42
+ const currentLevelIdx = this.levels.indexOf(this.level);
43
+ if (levelIdx <= currentLevelIdx) {
44
+ const color = this._getColor(level);
45
+ const prefix = `[${level.toUpperCase()}]`;
46
+ if (typeof args[0] === "string") {
47
+ // Color only the prefix
48
+ console.log(`${color}${prefix}\x1b[0m`, ...args);
49
+ } else {
50
+ // Non-string first arg, just print
51
+ console.log(`${color}${prefix}\x1b[0m`, ...args);
52
+ }
53
+ }
54
+ this.writeToFile(level, args);
55
+ }
56
+
57
+ error(...args) {
58
+ this.log("error", ...args);
59
+ }
60
+
61
+ warn(...args) {
62
+ this.log("warn", ...args);
63
+ }
64
+
65
+ info(...args) {
66
+ this.log("info", ...args);
67
+ }
68
+
69
+ debug(...args) {
70
+ this.log("debug", ...args);
71
+ console.debug(...args);
72
+ }
73
+
74
+ _getColor(level) {
75
+ switch (level) {
76
+ case "error":
77
+ return "\x1b[31m"; // Red
78
+ case "warn":
79
+ return "\x1b[33m"; // Yellow
80
+ case "info":
81
+ return "\x1b[36m"; // Cyan
82
+ case "debug":
83
+ return "\x1b[90m"; // Gray
84
+ default:
85
+ return "";
86
+ }
87
+ }
88
+ }
89
+
90
+ module.exports = Logger;