ftmocks-utils 1.3.3 → 1.3.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.
@@ -0,0 +1,195 @@
1
+ const { compareMockToFetchRequest } = require("./compare-utils");
2
+ const { getCompareRankMockToFetchRequest } = require("./rank-compare-utils");
3
+ const { getMockDir, nameToFolder } = require("./common-utils");
4
+ const path = require("path");
5
+ const fs = require("fs");
6
+
7
+ function getMatchingMockData({
8
+ testMockData,
9
+ defaultMockData,
10
+ url,
11
+ options,
12
+ testConfig,
13
+ testName,
14
+ mode,
15
+ }) {
16
+ let served = false;
17
+ let matchedMocks =
18
+ testMockData?.filter((mock) => {
19
+ if (mock.fileContent.waitForPrevious && !served) {
20
+ return false;
21
+ }
22
+ served = mock.fileContent.served;
23
+ return compareMockToFetchRequest(mock, { url, options });
24
+ }) || [];
25
+ let foundMock = matchedMocks.find((mock) => !mock.fileContent.served)
26
+ ? matchedMocks.find((mock) => !mock.fileContent.served)
27
+ : matchedMocks[matchedMocks.length - 1];
28
+
29
+ if (!foundMock) {
30
+ foundMock = defaultMockData.find((tm) =>
31
+ compareMockToFetchRequest(tm, {
32
+ url,
33
+ options,
34
+ })
35
+ );
36
+ }
37
+
38
+ if (!foundMock && mode !== "strict") {
39
+ const mockRanks = {};
40
+ testMockData.forEach((tm) => {
41
+ const rank = getCompareRankMockToFetchRequest(tm, {
42
+ url,
43
+ options,
44
+ });
45
+ if (rank > 0) {
46
+ mockRanks[tm.id] = rank;
47
+ }
48
+ });
49
+ defaultMockData.forEach((tm) => {
50
+ const rank = getCompareRankMockToFetchRequest(tm, {
51
+ url,
52
+ options,
53
+ });
54
+ if (rank > 0) {
55
+ mockRanks[tm.id] = rank;
56
+ }
57
+ });
58
+ // Sort by rank to find the best match
59
+ const sortedRanks = Object.entries(mockRanks).sort((a, b) => a[1] - b[1]);
60
+ if (sortedRanks.length > 0) {
61
+ const bestMockId = sortedRanks?.[0]?.[0];
62
+ if (bestMockId) {
63
+ foundMock = [...testMockData, ...defaultMockData].find(
64
+ (mock) => mock.id === bestMockId
65
+ );
66
+ }
67
+ }
68
+ }
69
+ // updating stats to mock file
70
+ if (foundMock) {
71
+ let mockFilePath = path.join(
72
+ getMockDir(testConfig),
73
+ `test_${nameToFolder(testName)}`,
74
+ `mock_${foundMock.id}.json`
75
+ );
76
+ if (!fs.existsSync(mockFilePath)) {
77
+ mockFilePath = path.join(
78
+ getMockDir(testConfig),
79
+ "defaultMocks",
80
+ `mock_${foundMock.id}.json`
81
+ );
82
+ }
83
+ foundMock.fileContent.served = true;
84
+ fs.writeFileSync(
85
+ mockFilePath,
86
+ JSON.stringify(foundMock.fileContent, null, 2)
87
+ );
88
+ }
89
+ return foundMock ? foundMock.fileContent : null;
90
+ }
91
+
92
+ function getMatchingMockDataV2({
93
+ testMockData,
94
+ defaultMockData,
95
+ url,
96
+ options,
97
+ testConfig,
98
+ testName,
99
+ mode,
100
+ }) {
101
+ const testMockIdMap = createIdMap(testMockData);
102
+ const testMockMethodPathnameMap = createMethodPathnameIdMap(testMockData);
103
+ const defaultMockIdMap = createIdMap(defaultMockData);
104
+ const defaultMockMethodPathnameMap =
105
+ createMethodPathnameIdMap(defaultMockData);
106
+ const key = getMockKey(options);
107
+ let matchedMocks = testMockMethodPathnameMap[key] || [];
108
+ let defaultMatchedMocks = [];
109
+ const nonWaitForMocks = matchedMocks.filter((mockId) => {
110
+ const mock = testMockIdMap[mockId];
111
+ if (mock.fileContent.waitFor) {
112
+ const waitForMocks = mock.fileContent.waitFor.filter((waitForMockId) => {
113
+ const waitForMock = testMockIdMap[waitForMockId];
114
+ return waitForMock && !waitForMock.fileContent.served;
115
+ });
116
+ if (waitForMocks.length > 0) {
117
+ return false;
118
+ }
119
+ }
120
+ return true;
121
+ });
122
+ const nonServedMocks = nonWaitForMocks.filter((mockId) => {
123
+ const mock = testMockIdMap[mockId];
124
+ return !mock.fileContent.served;
125
+ });
126
+ let foundMock = null;
127
+ if (nonServedMocks.length > 0) {
128
+ foundMock = testMockIdMap[nonServedMocks[0]];
129
+ } else if (nonWaitForMocks.length > 0) {
130
+ foundMock = testMockIdMap[nonWaitForMocks[nonWaitForMocks.length - 1]];
131
+ }
132
+ if (!foundMock) {
133
+ defaultMatchedMocks = defaultMockMethodPathnameMap[key] || [];
134
+ if (defaultMatchedMocks.length > 0) {
135
+ foundMock = defaultMockIdMap[defaultMatchedMocks[0]];
136
+ }
137
+ }
138
+ if (!foundMock && mode !== "strict") {
139
+ const mockRanks = {};
140
+ matchedMocks.forEach((mockId) => {
141
+ const mock = testMockIdMap[mockId];
142
+ const rank = getCompareRankMockToFetchRequest(mock, {
143
+ url,
144
+ options,
145
+ });
146
+ if (rank > 0) {
147
+ mockRanks[mock.id] = rank;
148
+ }
149
+ });
150
+ defaultMatchedMocks.forEach((mockId) => {
151
+ const mock = defaultMockIdMap[mockId];
152
+ const rank = getCompareRankMockToFetchRequest(mock, {
153
+ url,
154
+ options,
155
+ });
156
+ if (rank > 0) {
157
+ mockRanks[mock.id] = rank;
158
+ }
159
+ });
160
+ // Sort by rank to find the best match
161
+ const sortedRanks = Object.entries(mockRanks).sort((a, b) => a[1] - b[1]);
162
+ if (sortedRanks.length > 0) {
163
+ const bestMockId = sortedRanks?.[0]?.[0];
164
+ if (bestMockId) {
165
+ foundMock = testMockIdMap[bestMockId] || defaultMockIdMap[bestMockId];
166
+ }
167
+ }
168
+ }
169
+ // updating stats to mock file
170
+ if (foundMock) {
171
+ let mockFilePath = path.join(
172
+ getMockDir(testConfig),
173
+ `test_${nameToFolder(testName)}`,
174
+ `mock_${foundMock.id}.json`
175
+ );
176
+ if (!fs.existsSync(mockFilePath)) {
177
+ mockFilePath = path.join(
178
+ getMockDir(testConfig),
179
+ "defaultMocks",
180
+ `mock_${foundMock.id}.json`
181
+ );
182
+ }
183
+ foundMock.fileContent.served = true;
184
+ fs.writeFileSync(
185
+ mockFilePath,
186
+ JSON.stringify(foundMock.fileContent, null, 2)
187
+ );
188
+ }
189
+ return foundMock ? foundMock.fileContent : null;
190
+ }
191
+
192
+ module.exports = {
193
+ getMatchingMockData,
194
+ getMatchingMockDataV2,
195
+ };
@@ -0,0 +1,364 @@
1
+ const { getMatchingMockData } = require("./match-utils");
2
+ const { getMockDir, nameToFolder, getHeaders } = require("./common-utils");
3
+ const { getFallbackDir } = require("./common-utils");
4
+ const { getTestByName } = require("./common-utils");
5
+ const { compareMockToMock } = require("./compare-utils");
6
+ const { loadMockDataFromConfig } = require("./mock-utils");
7
+ const { resetAllMockStats } = require("./mock-utils");
8
+ const { getDefaultMockDataFromConfig } = require("./mock-utils");
9
+ const { Logger } = require("./log-utils");
10
+ const { saveIfItIsFile } = require("./file-utils");
11
+ const { createTest } = require("./test-utils");
12
+ const path = require("path");
13
+ const crypto = require("crypto");
14
+ const fs = require("fs");
15
+
16
+ let logger = null;
17
+ const DEFAULT_EXCLUDED_HEADERS =
18
+ "cookie,set-cookie,authorization,www-authenticate";
19
+
20
+ async function initiatePlaywrightRoutes(
21
+ page,
22
+ ftmocksConifg,
23
+ testName,
24
+ mockPath = "**/*",
25
+ excludeMockPath = null
26
+ ) {
27
+ logger = new Logger(
28
+ { disableLogs: ftmocksConifg.DISABLE_LOGS },
29
+ ftmocksConifg,
30
+ testName
31
+ );
32
+ const testMockData = testName
33
+ ? loadMockDataFromConfig(ftmocksConifg, testName)
34
+ : [];
35
+ resetAllMockStats({ testMockData, testConfig: ftmocksConifg, testName });
36
+ const test = await getTestByName(ftmocksConifg, testName);
37
+ const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
38
+ logger.debug("\x1b[32mcalling initiatePlaywrightRoutes fetch\x1b[0m");
39
+ let firstUrl = null;
40
+ await page.route(mockPath, async (route, request) => {
41
+ const url = request.url();
42
+ if (!firstUrl) {
43
+ firstUrl = url;
44
+ }
45
+ const options = {
46
+ url,
47
+ method: request.method(),
48
+ body: request.postData(),
49
+ };
50
+ if (excludeMockPath && new RegExp(excludeMockPath).test(url)) {
51
+ await route.fallback();
52
+ return;
53
+ }
54
+ let mockData = getMatchingMockData({
55
+ testMockData,
56
+ defaultMockData,
57
+ url,
58
+ options,
59
+ testConfig: ftmocksConifg,
60
+ testName,
61
+ mode: test.mode || "loose",
62
+ });
63
+ try {
64
+ if (mockData) {
65
+ const { content, headers, status, file } = mockData.response;
66
+
67
+ const json = {
68
+ status,
69
+ headers: getHeaders(headers),
70
+ body: content,
71
+ };
72
+
73
+ if (file) {
74
+ let filePath = path.join(
75
+ getMockDir(ftmocksConifg),
76
+ `test_${nameToFolder(testName)}`,
77
+ "_files",
78
+ file
79
+ );
80
+ if (!fs.existsSync(filePath)) {
81
+ filePath = path.join(
82
+ getMockDir(ftmocksConifg),
83
+ "defaultMocks",
84
+ "_files",
85
+ file
86
+ );
87
+ }
88
+ if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
89
+ const fileContent = fs.readFileSync(filePath);
90
+ json.body = fileContent;
91
+
92
+ console.debug(
93
+ "\x1b[32mresponse is a file, serving file\x1b[0m",
94
+ filePath,
95
+ url
96
+ );
97
+ await route.fulfill(json);
98
+ }
99
+ } else {
100
+ await route.fulfill(json);
101
+ }
102
+ } else {
103
+ const fallbackDir = getFallbackDir(ftmocksConifg);
104
+ if (!fallbackDir) {
105
+ await route.fallback();
106
+ return;
107
+ }
108
+ const urlObj = new URL(route.request().url());
109
+ let filePath = path.join(
110
+ fallbackDir,
111
+ urlObj.pathname === "/" || urlObj.pathname === ""
112
+ ? ftmocksConifg.FALLBACK_DIR_INDEX_FILE || "index.html"
113
+ : urlObj.pathname
114
+ );
115
+
116
+ if (
117
+ !fs.existsSync(filePath) &&
118
+ !path.extname(filePath) &&
119
+ url === firstUrl
120
+ ) {
121
+ filePath = path.join(
122
+ fallbackDir,
123
+ ftmocksConifg.FALLBACK_DIR_INDEX_FILE_FOR_STATUS_404 || "index.html"
124
+ );
125
+ logger.debug(
126
+ "\x1b[32mserving file for status 404\x1b[0m",
127
+ filePath,
128
+ url
129
+ );
130
+ }
131
+ if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
132
+ const fileContent = fs.readFileSync(filePath);
133
+ const ext = path.extname(filePath);
134
+ const contentType =
135
+ {
136
+ ".html": "text/html",
137
+ ".htm": "text/html",
138
+ ".xhtml": "application/xhtml+xml",
139
+ ".css": "text/css",
140
+ ".js": "application/javascript",
141
+ ".json": "application/json",
142
+ ".png": "image/png",
143
+ ".jpg": "image/jpeg",
144
+ ".svg": "image/svg+xml",
145
+ ".ico": "image/x-icon",
146
+ ".webp": "image/webp",
147
+ ".mp4": "video/mp4",
148
+ ".mp3": "audio/mpeg",
149
+ ".wav": "audio/wav",
150
+ ".ogg": "audio/ogg",
151
+ ".pdf": "application/pdf",
152
+ ".doc": "application/msword",
153
+ ".docx":
154
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
155
+ ".xls": "application/vnd.ms-excel",
156
+ ".xlsx":
157
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
158
+ ".ppt": "application/vnd.ms-powerpoint",
159
+ ".pptx":
160
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
161
+ ".zip": "application/zip",
162
+ ".rar": "application/x-rar-compressed",
163
+ ".7z": "application/x-7z-compressed",
164
+ ".tar": "application/x-tar",
165
+ ".gz": "application/gzip",
166
+ ".bz2": "application/x-bzip2",
167
+ ".xz": "application/x-xz",
168
+ ".exe": "application/x-msdownload",
169
+ ".dll": "application/x-msdownload",
170
+ ".so": "application/x-sharedlib",
171
+ ".dylib": "application/x-dynamiclib",
172
+ ".bin": "application/octet-stream",
173
+ ".txt": "text/plain",
174
+ ".csv": "text/csv",
175
+ ".tsv": "text/tab-separated-values",
176
+ ".xml": "application/xml",
177
+ ".xsl": "application/xml",
178
+ ".xslt": "application/xml",
179
+ ".xlt": "application/xml",
180
+ ".xltx": "application/xml",
181
+ ".xltm": "application/xml",
182
+ ".yaml": "text/yaml",
183
+ ".yml": "text/yaml",
184
+ ".toml": "text/toml",
185
+ ".php": "application/x-httpd-php",
186
+ }[ext] || "application/octet-stream";
187
+
188
+ logger.info("\x1b[32mserving file\x1b[0m", filePath);
189
+ await route.fulfill({
190
+ body: fileContent,
191
+ headers: { "Content-Type": contentType },
192
+ });
193
+ } else {
194
+ logger.debug("\x1b[31mmissing mock data, falling back\x1b[0m", url);
195
+ await route.fallback();
196
+ }
197
+ }
198
+ } catch (e) {
199
+ logger.error(e);
200
+ logger.error(
201
+ "\x1b[31merror at initiatePlaywrightRoutes\x1b[0m",
202
+ url,
203
+ options
204
+ );
205
+ }
206
+ });
207
+ }
208
+
209
+ const excludeHeaders = (headers, ftmocksConifg) => {
210
+ const excludedHeaders =
211
+ ftmocksConifg.EXCLUDED_HEADERS || DEFAULT_EXCLUDED_HEADERS;
212
+ if (!excludedHeaders) {
213
+ return headers;
214
+ }
215
+ excludedHeaders.split(",").forEach((header) => {
216
+ Object.keys(headers).forEach((key) => {
217
+ if (key.toLowerCase() === header.toLowerCase()) {
218
+ delete headers[key];
219
+ }
220
+ });
221
+ });
222
+ return headers;
223
+ };
224
+
225
+ async function recordPlaywrightRoutes(
226
+ page,
227
+ ftmocksConifg,
228
+ config = {
229
+ testName,
230
+ mockPath: "**/*",
231
+ pattern: "^/api/.*",
232
+ avoidDuplicatesInTheTest: false,
233
+ avoidDuplicatesWithDefaultMocks: false,
234
+ }
235
+ ) {
236
+ await page.route(config.mockPath, async (route) => {
237
+ try {
238
+ const urlObj = new URL(route.request().url());
239
+ if (config.pattern && config.pattern.length > 0) {
240
+ const patternRegex = new RegExp(config.pattern);
241
+ if (!patternRegex.test(urlObj.pathname)) {
242
+ await route.continue();
243
+ return;
244
+ }
245
+ }
246
+
247
+ const test = await getTestByName(ftmocksConifg, config.testName);
248
+ if (!test) {
249
+ await createTest(ftmocksConifg, config.testName);
250
+ }
251
+
252
+ const fileName = await saveIfItIsFile(
253
+ route,
254
+ config.testName,
255
+ ftmocksConifg
256
+ );
257
+
258
+ const mockData = {
259
+ url: urlObj.pathname + urlObj.search,
260
+ time: new Date().toString(),
261
+ method: route.request().method(),
262
+ request: {
263
+ headers: excludeHeaders(
264
+ await route.request().headers(),
265
+ ftmocksConifg
266
+ ),
267
+ queryString: Array.from(urlObj.searchParams.entries()).map(
268
+ ([name, value]) => ({
269
+ name,
270
+ value,
271
+ })
272
+ ),
273
+ postData: route.request().postData()
274
+ ? {
275
+ mimeType: "application/json",
276
+ text: route.request().postData(),
277
+ }
278
+ : null,
279
+ },
280
+ response: {
281
+ file: fileName,
282
+ status: (await route.fetch()).status(),
283
+ headers: (await route.fetch()).headers(),
284
+ content: fileName ? null : await (await route.fetch()).text(),
285
+ },
286
+ id: crypto.randomUUID(),
287
+ served: false,
288
+ ignoreParams: ftmocksConifg.ignoreParams || [],
289
+ };
290
+
291
+ await createTest(ftmocksConifg, config.testName);
292
+ if (config.avoidDuplicatesInTheTest) {
293
+ // Check if the mock data is a duplicate of a mock data in the test
294
+ const testMockList = loadMockDataFromConfig(
295
+ ftmocksConifg,
296
+ config.testName
297
+ );
298
+ const matchResponse = testMockList.find((mock) =>
299
+ compareMockToMock(mock.fileContent, mockData, true)
300
+ );
301
+ if (matchResponse) {
302
+ console.log("Aborting duplicate mock data in the test");
303
+ await route.continue();
304
+ return;
305
+ }
306
+ }
307
+
308
+ if (config.avoidDuplicatesWithDefaultMocks) {
309
+ // Check if the mock data is a duplicate of a mock data in the test
310
+ const defaultMockList = getDefaultMockDataFromConfig(ftmocksConifg);
311
+ const matchResponse = defaultMockList.find((mock) =>
312
+ compareMockToMock(mock.fileContent, mockData, true)
313
+ );
314
+ if (matchResponse) {
315
+ console.log("Aborting duplicate mock data with default mocks");
316
+ await route.continue();
317
+ return;
318
+ }
319
+ }
320
+
321
+ // Save the mock data to the test
322
+ const mockListPath = path.join(
323
+ getMockDir(ftmocksConifg),
324
+ `test_${nameToFolder(config.testName)}`,
325
+ "_mock_list.json"
326
+ );
327
+ let mockList = [];
328
+ if (fs.existsSync(mockListPath)) {
329
+ mockList = JSON.parse(fs.readFileSync(mockListPath, "utf8"));
330
+ }
331
+ mockList.push({
332
+ id: mockData.id,
333
+ url: mockData.url,
334
+ method: mockData.method,
335
+ time: mockData.time,
336
+ });
337
+
338
+ // Create test directory if it doesn't exist
339
+ const testDir = path.join(
340
+ getMockDir(ftmocksConifg),
341
+ `test_${nameToFolder(config.testName)}`
342
+ );
343
+ if (!fs.existsSync(testDir)) {
344
+ fs.mkdirSync(testDir, { recursive: true });
345
+ }
346
+ fs.writeFileSync(mockListPath, JSON.stringify(mockList, null, 2));
347
+ const mocDataPath = path.join(
348
+ getMockDir(ftmocksConifg),
349
+ `test_${nameToFolder(config.testName)}`,
350
+ `mock_${mockData.id}.json`
351
+ );
352
+ fs.writeFileSync(mocDataPath, JSON.stringify(mockData, null, 2));
353
+ await route.continue();
354
+ } catch (error) {
355
+ console.error(error);
356
+ await route.continue();
357
+ }
358
+ });
359
+ }
360
+
361
+ module.exports = {
362
+ initiatePlaywrightRoutes,
363
+ recordPlaywrightRoutes,
364
+ };