ftmocks-utils 1.3.1 → 1.3.3

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.1",
3
+ "version": "1.3.3",
4
4
  "description": "Util functions for FtMocks",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -0,0 +1,151 @@
1
+ const { clearNulls, processURL } = require("./common-utils");
2
+ const { FtJSON } = require("./json-utils");
3
+
4
+ const isUrlAndMethodSame = (req1, req2) => {
5
+ const url1 = new URL(`http://domain.com${req1.url}`);
6
+ const url2 = new URL(`http://domain.com${req2.url}`);
7
+ return (
8
+ url1.pathname === url2.pathname &&
9
+ url1.method?.toLowerCase() === url2.method?.toLowerCase()
10
+ );
11
+ };
12
+
13
+ const isSameRequest = (req1, req2) => {
14
+ clearNulls(req1.postData);
15
+ clearNulls(req2.postData);
16
+ let matched = true;
17
+ if (req1.url !== req2.url) {
18
+ matched = false;
19
+ } else if (req1.method?.toLowerCase() !== req2.method?.toLowerCase()) {
20
+ matched = false;
21
+ } else if (
22
+ (!req1.postData && req2.postData) ||
23
+ (req1.postData && !req2.postData)
24
+ ) {
25
+ matched = FtJSON.areJsonEqual(req1.postData || {}, req2.postData || {});
26
+ } else if (
27
+ req1.postData &&
28
+ req2.postData &&
29
+ !FtJSON.areJsonEqual(req1.postData, req2.postData)
30
+ ) {
31
+ matched = false;
32
+ }
33
+ return matched;
34
+ };
35
+
36
+ const isSameResponse = (req1, req2) => {
37
+ try {
38
+ let matched = true;
39
+ if (req1.response.status !== req2.response.status) {
40
+ matched = false;
41
+ // console.log('not matched at url', req1.method, req2.method);
42
+ } else if (
43
+ (!req1.response.content && req2.response.content) ||
44
+ (req1.response.content && !req2.response.content)
45
+ ) {
46
+ matched = FtJSON.areJsonEqual(
47
+ FtJSON.parse(req1.response.content) || {},
48
+ FtJSON.parse(req2.response.content) || {}
49
+ );
50
+ // console.log('not matched at post Data 0', req1.postData, req2.postData);
51
+ } else if (
52
+ req1.response.content &&
53
+ req2.response.content &&
54
+ !FtJSON.areJsonEqual(
55
+ FtJSON.parse(req1.response.content) || {},
56
+ FtJSON.parse(req2.response.content) || {}
57
+ )
58
+ ) {
59
+ matched = false;
60
+ }
61
+ // if (matched) {
62
+ // console.log('matched responses', req1, req2);
63
+ // }
64
+ return matched;
65
+ } catch (error) {
66
+ console.error(error);
67
+ return false;
68
+ }
69
+ };
70
+
71
+ function compareMockToRequest(mock, req) {
72
+ const mockURL = processURL(
73
+ mock.fileContent.url,
74
+ mock.fileContent.ignoreParams
75
+ );
76
+ const reqURL = processURL(req.originalUrl, mock.fileContent.ignoreParams);
77
+ const isSameUrlAndMethod = isUrlAndMethodSame(
78
+ { url: mockURL, method: mock.fileContent.method },
79
+ { url: reqURL, method: req.method }
80
+ );
81
+ if (!isSameUrlAndMethod) {
82
+ return false;
83
+ }
84
+ const postData = mock.fileContent.request?.postData?.text
85
+ ? FtJSON.parse(mock.fileContent.request?.postData?.text)
86
+ : mock.fileContent.request?.postData;
87
+ return isSameRequest(
88
+ { url: mockURL, method: mock.fileContent.method, postData },
89
+ {
90
+ method: req.method,
91
+ postData: req.body,
92
+ url: reqURL,
93
+ }
94
+ );
95
+ }
96
+
97
+ function compareMockToFetchRequest(mock, fetchReq) {
98
+ try {
99
+ const mockURL = processURL(
100
+ mock.fileContent.url,
101
+ mock.fileContent.ignoreParams
102
+ );
103
+ const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
104
+ const isSameUrlAndMethod = isUrlAndMethodSame(
105
+ { url: mockURL, method: mock.fileContent.method },
106
+ { url: reqURL, method: fetchReq.options.method || "GET" }
107
+ );
108
+ if (!isSameUrlAndMethod) {
109
+ return false;
110
+ }
111
+ const postData = mock.fileContent.request?.postData?.text
112
+ ? FtJSON.parse(mock.fileContent.request?.postData?.text)
113
+ : mock.fileContent.request?.postData;
114
+ return isSameRequest(
115
+ { url: mockURL, method: mock.fileContent.method, postData },
116
+ {
117
+ method: fetchReq.options.method || "GET",
118
+ postData: fetchReq.options.body?.length
119
+ ? FtJSON.parse(fetchReq.options.body)
120
+ : fetchReq.options.body,
121
+ url: reqURL,
122
+ }
123
+ );
124
+ } catch (e) {
125
+ console.error("error at compareMockToFetchRequest", mock, fetchReq);
126
+ console.error(e);
127
+ }
128
+ return false;
129
+ }
130
+
131
+ const compareMockToMock = (mock1, mock2, matchResponse) => {
132
+ try {
133
+ if (matchResponse) {
134
+ return isSameRequest(mock1, mock2) && isSameResponse(mock1, mock2);
135
+ } else {
136
+ return isSameRequest(mock1, mock2);
137
+ }
138
+ } catch (error) {
139
+ console.error(error);
140
+ return false;
141
+ }
142
+ };
143
+
144
+ module.exports = {
145
+ isUrlAndMethodSame,
146
+ isSameRequest,
147
+ isSameResponse,
148
+ compareMockToRequest,
149
+ compareMockToFetchRequest,
150
+ compareMockToMock,
151
+ };
package/src/index.js CHANGED
@@ -12,206 +12,25 @@ const {
12
12
  countFilesInDirectory,
13
13
  getTestByName,
14
14
  } = require("./common-utils");
15
+ const {
16
+ getDefaultMockDataFromConfig,
17
+ loadMockDataFromConfig,
18
+ resetAllMockStats,
19
+ } = require("./mock-utils");
20
+ const { createTest } = require("./test-utils");
15
21
  const { FtJSON } = require("./json-utils");
22
+ const { Logger, deleteAllLogs } = require("./log-utils");
23
+ const {
24
+ isSameRequest,
25
+ compareMockToRequest,
26
+ compareMockToFetchRequest,
27
+ compareMockToMock,
28
+ } = require("./compare-utils");
29
+ const { getCompareRankMockToFetchRequest } = require("./rank-compare-utils");
16
30
 
17
- const getDefaultMockDataFromConfig = (testConfig) => {
18
- const defaultPath = path.join(
19
- getMockDir(testConfig),
20
- "defaultMocks",
21
- "_mock_list.json"
22
- );
23
-
24
- try {
25
- const defaultData = fs.readFileSync(defaultPath, "utf8");
26
- let parsedData = JSON.parse(defaultData);
27
-
28
- // Read and attach mock data for each entry in parsedData
29
- parsedData.forEach((entry) => {
30
- const mockFilePath = path.join(
31
- getMockDir(testConfig),
32
- "defaultMocks",
33
- `mock_${entry.id}.json`
34
- );
35
- try {
36
- const mockData = fs.readFileSync(mockFilePath, "utf8");
37
- entry.fileContent = JSON.parse(mockData);
38
- } catch (error) {
39
- console.error(`Error reading mock data for ${entry.id}:`, error);
40
- return entry; // Return the original entry if there's an error
41
- }
42
- });
43
- return parsedData;
44
- } catch (error) {
45
- console.error(`Error reading or parsing default mocks:`, error);
46
- return [];
47
- }
48
- };
31
+ let logger = null;
49
32
 
50
33
  // src/index.js
51
- const loadMockDataFromConfig = (testConfig, _testName) => {
52
- try {
53
- let testName = _testName;
54
- if (!testName) {
55
- // Read the test ID from mockServer.config.json
56
- const configPath = path.join(
57
- getMockDir(testConfig),
58
- "mockServer.config.json"
59
- );
60
- const configData = fs.readFileSync(configPath, "utf8");
61
- const config = JSON.parse(configData);
62
- testName = config.testName;
63
- }
64
- // Read the tests from testConfig
65
- const mocksPath = path.join(
66
- getMockDir(testConfig),
67
- `test_${nameToFolder(testName)}`,
68
- "_mock_list.json"
69
- );
70
- const mocksData = fs.readFileSync(mocksPath, "utf8");
71
- const mocks = JSON.parse(mocksData);
72
-
73
- mocks.forEach((mock) => {
74
- const fileContent = JSON.parse(
75
- fs.readFileSync(
76
- path.join(
77
- getMockDir(testConfig),
78
- `test_${nameToFolder(testName)}`,
79
- `mock_${mock.id}.json`
80
- ),
81
- "utf8"
82
- )
83
- );
84
- mock.fileContent = fileContent;
85
- });
86
-
87
- return mocks;
88
- } catch (error) {
89
- console.error("Error loading test data:", error.message);
90
- return [];
91
- }
92
- };
93
-
94
- const isSameRequest = (req1, req2) => {
95
- clearNulls(req1.postData);
96
- clearNulls(req2.postData);
97
- let matched = true;
98
- if (req1.url !== req2.url) {
99
- matched = false;
100
- } else if (req1.method?.toLowerCase() !== req2.method?.toLowerCase()) {
101
- matched = false;
102
- } else if (
103
- (!req1.postData && req2.postData) ||
104
- (req1.postData && !req2.postData)
105
- ) {
106
- matched = FtJSON.areJsonEqual(req1.postData || {}, req2.postData || {});
107
- } else if (
108
- req1.postData &&
109
- req2.postData &&
110
- !FtJSON.areJsonEqual(req1.postData, req2.postData)
111
- ) {
112
- matched = false;
113
- }
114
- return matched;
115
- };
116
-
117
- const getSameRequestRank = (req1, req2) => {
118
- let rank = 1;
119
- clearNulls(req1.postData);
120
- clearNulls(req2.postData);
121
- // Compare path names
122
- const url1 = new URL(`http://domain.com${req1.url}`);
123
- const url2 = new URL(`http://domain.com${req2.url}`);
124
- if (url1.pathname !== url2.pathname) {
125
- rank = 0;
126
- } else if (url1.method?.toLowerCase() !== url2.method?.toLowerCase()) {
127
- rank = 0;
128
- } else {
129
- // Compare query strings
130
- const queryDiff = charDifference(url1.search || "", url2.search || "");
131
- rank = rank + queryDiff;
132
- // Compare post data
133
- const charDiff = charDifference(
134
- FtJSON.stringify(req1.postData || {}),
135
- FtJSON.stringify(req2.postData || {})
136
- );
137
- rank = rank + charDiff;
138
- }
139
- return rank;
140
- };
141
-
142
- function compareMockToRequest(mock, req) {
143
- const mockURL = processURL(
144
- mock.fileContent.url,
145
- mock.fileContent.ignoreParams
146
- );
147
- const reqURL = processURL(req.originalUrl, mock.fileContent.ignoreParams);
148
- const postData = mock.fileContent.request?.postData?.text
149
- ? FtJSON.parse(mock.fileContent.request?.postData?.text)
150
- : mock.fileContent.request?.postData;
151
- return isSameRequest(
152
- { url: mockURL, method: mock.fileContent.method, postData },
153
- {
154
- method: req.method,
155
- postData: req.body,
156
- url: reqURL,
157
- }
158
- );
159
- }
160
-
161
- function compareMockToFetchRequest(mock, fetchReq) {
162
- try {
163
- const mockURL = processURL(
164
- mock.fileContent.url,
165
- mock.fileContent.ignoreParams
166
- );
167
- const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
168
- const postData = mock.fileContent.request?.postData?.text
169
- ? FtJSON.parse(mock.fileContent.request?.postData?.text)
170
- : mock.fileContent.request?.postData;
171
- return isSameRequest(
172
- { url: mockURL, method: mock.fileContent.method, postData },
173
- {
174
- method: fetchReq.options.method || "GET",
175
- postData: fetchReq.options.body?.length
176
- ? FtJSON.parse(fetchReq.options.body)
177
- : fetchReq.options.body,
178
- url: reqURL,
179
- }
180
- );
181
- } catch (e) {
182
- console.error("error at compareMockToFetchRequest", mock, fetchReq);
183
- console.error(e);
184
- }
185
- return false;
186
- }
187
-
188
- function getCompareRankMockToFetchRequest(mock, fetchReq) {
189
- try {
190
- const mockURL = processURL(
191
- mock.fileContent.url,
192
- mock.fileContent.ignoreParams
193
- );
194
- const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
195
- const postData = mock.fileContent.request?.postData?.text
196
- ? FtJSON.parse(mock.fileContent.request?.postData?.text)
197
- : mock.fileContent.request?.postData;
198
- return getSameRequestRank(
199
- { url: mockURL, method: mock.fileContent.method, postData },
200
- {
201
- method: fetchReq.options.method || "GET",
202
- postData: fetchReq.options.body?.length
203
- ? FtJSON.parse(fetchReq.options.body)
204
- : fetchReq.options.body,
205
- url: reqURL,
206
- }
207
- );
208
- } catch (e) {
209
- console.error("error at getCompareRankMockToFetchRequest", mock, fetchReq);
210
- console.error(e);
211
- }
212
- return false;
213
- }
214
-
215
34
  function getMatchingMockData({
216
35
  testMockData,
217
36
  defaultMockData,
@@ -297,22 +116,6 @@ function getMatchingMockData({
297
116
  return foundMock ? foundMock.fileContent : null;
298
117
  }
299
118
 
300
- async function resetAllMockStats({ testMockData, testConfig, testName }) {
301
- for (let i = 0; i < testMockData.length; i++) {
302
- const tmd = testMockData[i];
303
- const mockFilePath = path.join(
304
- getMockDir(testConfig),
305
- `test_${nameToFolder(testName)}`,
306
- `mock_${tmd.id}.json`
307
- );
308
- tmd.fileContent.served = false;
309
- await fs.writeFileSync(
310
- mockFilePath,
311
- JSON.stringify(tmd.fileContent, null, 2)
312
- );
313
- }
314
- }
315
-
316
119
  async function initiatePlaywrightRoutes(
317
120
  page,
318
121
  ftmocksConifg,
@@ -320,13 +123,18 @@ async function initiatePlaywrightRoutes(
320
123
  mockPath = "**/*",
321
124
  excludeMockPath = null
322
125
  ) {
126
+ logger = new Logger(
127
+ { disableLogs: ftmocksConifg.DISABLE_LOGS },
128
+ ftmocksConifg,
129
+ testName
130
+ );
323
131
  const testMockData = testName
324
132
  ? loadMockDataFromConfig(ftmocksConifg, testName)
325
133
  : [];
326
134
  resetAllMockStats({ testMockData, testConfig: ftmocksConifg, testName });
327
135
  const test = await getTestByName(ftmocksConifg, testName);
328
136
  const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
329
- console.debug("\x1b[32mcalling initiatePlaywrightRoutes fetch\x1b[0m");
137
+ logger.debug("\x1b[32mcalling initiatePlaywrightRoutes fetch\x1b[0m");
330
138
  let firstUrl = null;
331
139
  await page.route(mockPath, async (route, request) => {
332
140
  const url = request.url();
@@ -413,7 +221,7 @@ async function initiatePlaywrightRoutes(
413
221
  fallbackDir,
414
222
  ftmocksConifg.FALLBACK_DIR_INDEX_FILE_FOR_STATUS_404 || "index.html"
415
223
  );
416
- console.debug(
224
+ logger.debug(
417
225
  "\x1b[32mserving file for status 404\x1b[0m",
418
226
  filePath,
419
227
  url
@@ -476,19 +284,19 @@ async function initiatePlaywrightRoutes(
476
284
  ".php": "application/x-httpd-php",
477
285
  }[ext] || "application/octet-stream";
478
286
 
479
- console.debug("\x1b[32mserving file\x1b[0m", filePath);
287
+ logger.info("\x1b[32mserving file\x1b[0m", filePath);
480
288
  await route.fulfill({
481
289
  body: fileContent,
482
290
  headers: { "Content-Type": contentType },
483
291
  });
484
292
  } else {
485
- console.debug("\x1b[31mmissing mock data, falling back\x1b[0m", url);
293
+ logger.debug("\x1b[31mmissing mock data, falling back\x1b[0m", url);
486
294
  await route.fallback();
487
295
  }
488
296
  }
489
297
  } catch (e) {
490
- console.error(e);
491
- console.error(
298
+ logger.error(e);
299
+ logger.error(
492
300
  "\x1b[31merror at initiatePlaywrightRoutes\x1b[0m",
493
301
  url,
494
302
  options
@@ -688,15 +496,6 @@ const deleteAllSnaps = async (ftmocksConifg, testName) => {
688
496
  fs.rmSync(snapFolder, { recursive: true, force: true });
689
497
  };
690
498
 
691
- const deleteAllLogs = async (ftmocksConifg, testName) => {
692
- const mockDir = path.join(
693
- getMockDir(ftmocksConifg),
694
- `test_${nameToFolder(testName)}`
695
- );
696
- const logFilePath = path.join(mockDir, `_logs.json`);
697
- fs.rmSync(logFilePath, { recursive: true, force: true });
698
- };
699
-
700
499
  function initiateJestEventSnaps(jest, ftmocksConifg, testName) {
701
500
  const mouseEvents = ftmocksConifg.snapEvents || [
702
501
  "click",
@@ -716,95 +515,6 @@ function initiateJestEventSnaps(jest, ftmocksConifg, testName) {
716
515
  });
717
516
  }
718
517
 
719
- const createTest = async (ftmocksConifg, testName) => {
720
- const testsPath = path.join(getMockDir(ftmocksConifg), "tests.json");
721
- let tests = [];
722
- try {
723
- // Read existing tests
724
- const testsData = fs.readFileSync(testsPath, "utf8");
725
- tests = JSON.parse(testsData);
726
- const etest = tests.find((tst) => tst.name === testName);
727
- if (!etest) {
728
- const newTest = {
729
- id: uuidv4(),
730
- name: testName,
731
- };
732
- tests.push(newTest);
733
- fs.writeFileSync(testsPath, JSON.stringify(tests, null, 2));
734
- const folderPath = path.join(
735
- getMockDir(ftmocksConifg),
736
- `test_${nameToFolder(testName)}`
737
- );
738
- const mockListFilePath = path.join(folderPath, "_mock_list.json");
739
- fs.mkdir(folderPath, { recursive: true }, (err) => {
740
- if (err) {
741
- console.error("\x1b[31mError creating directory:\x1b[0m", err);
742
- } else {
743
- console.log("\x1b[32mDirectory created successfully!\x1b[0m");
744
- }
745
- });
746
- await fs.appendFile(mockListFilePath, "[]", () => {
747
- console.log("\x1b[32mmock list file created successfully\x1b[0m");
748
- });
749
-
750
- return newTest;
751
- } else {
752
- throw "Test already exists";
753
- }
754
- } catch (error) {
755
- console.error(`\x1b[31mError reading tests.json:\x1b[0m`, error);
756
- return null;
757
- }
758
- };
759
-
760
- const isSameResponse = (req1, req2) => {
761
- try {
762
- let matched = true;
763
- if (req1.response.status !== req2.response.status) {
764
- matched = false;
765
- // console.log('not matched at url', req1.method, req2.method);
766
- } else if (
767
- (!req1.response.content && req2.response.content) ||
768
- (req1.response.content && !req2.response.content)
769
- ) {
770
- matched = FtJSON.areJsonEqual(
771
- FtJSON.parse(req1.response.content) || {},
772
- FtJSON.parse(req2.response.content) || {}
773
- );
774
- // console.log('not matched at post Data 0', req1.postData, req2.postData);
775
- } else if (
776
- req1.response.content &&
777
- req2.response.content &&
778
- !FtJSON.areJsonEqual(
779
- FtJSON.parse(req1.response.content) || {},
780
- FtJSON.parse(req2.response.content) || {}
781
- )
782
- ) {
783
- matched = false;
784
- }
785
- // if (matched) {
786
- // console.log('matched responses', req1, req2);
787
- // }
788
- return matched;
789
- } catch (error) {
790
- console.error(error);
791
- return false;
792
- }
793
- };
794
-
795
- const compareMockToMock = (mock1, mock2, matchResponse) => {
796
- try {
797
- if (matchResponse) {
798
- return isSameRequest(mock1, mock2) && isSameResponse(mock1, mock2);
799
- } else {
800
- return isSameRequest(mock1, mock2);
801
- }
802
- } catch (error) {
803
- console.error(error);
804
- return false;
805
- }
806
- };
807
-
808
518
  const saveIfItIsFile = async (route, testName, ftmocksConifg) => {
809
519
  const urlObj = new URL(route.request().url());
810
520
 
@@ -0,0 +1,103 @@
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(
34
+ this.logsFile,
35
+ JSON.stringify(this.logs, null, 2),
36
+ "utf8"
37
+ ); // Append the log message to the file
38
+ } catch (error) {
39
+ // Ignore error
40
+ }
41
+ }
42
+
43
+ log(level, ...args) {
44
+ if (this.disableLogs) return;
45
+ const levelIdx = this.levels.indexOf(level);
46
+ const currentLevelIdx = this.levels.indexOf(this.level);
47
+ if (levelIdx <= currentLevelIdx) {
48
+ const color = this._getColor(level);
49
+ const prefix = `[${level.toUpperCase()}]`;
50
+ if (typeof args[0] === "string") {
51
+ // Color only the prefix
52
+ console.log(`${color}${prefix}\x1b[0m`, ...args);
53
+ } else {
54
+ // Non-string first arg, just print
55
+ console.log(`${color}${prefix}\x1b[0m`, ...args);
56
+ }
57
+ }
58
+ this.writeToFile(level, args);
59
+ }
60
+
61
+ error(...args) {
62
+ this.log("error", ...args);
63
+ }
64
+
65
+ warn(...args) {
66
+ this.log("warn", ...args);
67
+ }
68
+
69
+ info(...args) {
70
+ this.log("info", ...args);
71
+ }
72
+
73
+ debug(...args) {
74
+ this.log("debug", ...args);
75
+ console.debug(...args);
76
+ }
77
+
78
+ _getColor(level) {
79
+ switch (level) {
80
+ case "error":
81
+ return "\x1b[31m"; // Red
82
+ case "warn":
83
+ return "\x1b[33m"; // Yellow
84
+ case "info":
85
+ return "\x1b[36m"; // Cyan
86
+ case "debug":
87
+ return "\x1b[90m"; // Gray
88
+ default:
89
+ return "";
90
+ }
91
+ }
92
+ }
93
+
94
+ const deleteAllLogs = async (ftmocksConifg, testName) => {
95
+ const mockDir = path.join(
96
+ getMockDir(ftmocksConifg),
97
+ `test_${nameToFolder(testName)}`
98
+ );
99
+ const logFilePath = path.join(mockDir, `_logs.json`);
100
+ fs.rmSync(logFilePath, { recursive: true, force: true });
101
+ };
102
+
103
+ module.exports = { Logger, deleteAllLogs };