ftmocks-utils 1.1.6 → 1.1.7
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 +1 -1
- package/src/index.js +371 -231
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,135 +1,166 @@
|
|
|
1
1
|
// import fs from 'fs';
|
|
2
|
-
const fs = require(
|
|
2
|
+
const fs = require("fs");
|
|
3
3
|
// import path from 'path';
|
|
4
|
-
const path = require(
|
|
4
|
+
const path = require("path");
|
|
5
5
|
|
|
6
|
-
const nameToFolder = name => {
|
|
7
|
-
return name.replaceAll(
|
|
6
|
+
const nameToFolder = (name) => {
|
|
7
|
+
return name.replaceAll(" ", "_");
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
const getMockDir = config => {
|
|
11
|
-
if(!path.isAbsolute(config.MOCK_DIR)) {
|
|
12
|
-
return path.resolve(
|
|
10
|
+
const getMockDir = (config) => {
|
|
11
|
+
if (!path.isAbsolute(config.MOCK_DIR)) {
|
|
12
|
+
return path.resolve(process.cwd(), config.MOCK_DIR);
|
|
13
13
|
}
|
|
14
14
|
return config.MOCK_DIR;
|
|
15
|
-
}
|
|
15
|
+
};
|
|
16
16
|
|
|
17
|
-
const getFallbackDir = config => {
|
|
18
|
-
if(config.FALLBACK_DIR && !path.isAbsolute(config.FALLBACK_DIR)) {
|
|
19
|
-
return path.resolve(
|
|
17
|
+
const getFallbackDir = (config) => {
|
|
18
|
+
if (config.FALLBACK_DIR && !path.isAbsolute(config.FALLBACK_DIR)) {
|
|
19
|
+
return path.resolve(process.cwd(), config.FALLBACK_DIR);
|
|
20
20
|
}
|
|
21
21
|
return config.FALLBACK_DIR;
|
|
22
|
-
}
|
|
22
|
+
};
|
|
23
23
|
|
|
24
|
-
const capitalizeHeader = header => {
|
|
24
|
+
const capitalizeHeader = (header) => {
|
|
25
25
|
return header
|
|
26
|
-
.split(
|
|
27
|
-
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
28
|
-
.join(
|
|
29
|
-
}
|
|
26
|
+
.split("-")
|
|
27
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
28
|
+
.join("-");
|
|
29
|
+
};
|
|
30
30
|
|
|
31
|
-
const capitalizeHeaders = headers => {
|
|
31
|
+
const capitalizeHeaders = (headers) => {
|
|
32
32
|
return Object.fromEntries(
|
|
33
|
-
Object.entries(headers).map(([key, value]) => [
|
|
33
|
+
Object.entries(headers).map(([key, value]) => [
|
|
34
|
+
capitalizeHeader(key),
|
|
35
|
+
value,
|
|
36
|
+
])
|
|
34
37
|
);
|
|
35
38
|
};
|
|
36
39
|
|
|
37
|
-
const getHeaders = headers => {
|
|
40
|
+
const getHeaders = (headers) => {
|
|
38
41
|
let res = null;
|
|
39
42
|
try {
|
|
40
|
-
res = new Map([
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
res = new Map([
|
|
44
|
+
...Object.entries(headers),
|
|
45
|
+
...Object.entries(capitalizeHeaders(headers)),
|
|
46
|
+
]);
|
|
47
|
+
} catch (e) {
|
|
48
|
+
console.debug("error at getHeaders", e);
|
|
49
|
+
res = new Map([
|
|
50
|
+
["Content-Type", "application/json"],
|
|
51
|
+
["content-type", "application/json"],
|
|
52
|
+
]);
|
|
44
53
|
}
|
|
45
54
|
return Object.fromEntries(res);
|
|
46
|
-
}
|
|
55
|
+
};
|
|
47
56
|
|
|
48
57
|
const areJsonEqual = (jsonObj1, jsonObj2) => {
|
|
49
58
|
// Check if both are objects and not null
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
59
|
+
if (
|
|
60
|
+
typeof jsonObj1 === "object" &&
|
|
61
|
+
jsonObj1 !== null &&
|
|
62
|
+
typeof jsonObj2 === "object" &&
|
|
63
|
+
jsonObj2 !== null
|
|
64
|
+
) {
|
|
53
65
|
// Get the keys of both objects
|
|
54
66
|
const keys1 = Object.keys(jsonObj1);
|
|
55
67
|
const keys2 = Object.keys(jsonObj2);
|
|
56
|
-
|
|
68
|
+
|
|
57
69
|
// Check if the number of keys is different
|
|
58
70
|
if (keys1.length !== keys2.length) {
|
|
59
71
|
return false;
|
|
60
72
|
}
|
|
61
|
-
|
|
73
|
+
|
|
62
74
|
// Recursively check each key-value pair
|
|
63
75
|
for (let key of keys1) {
|
|
64
76
|
if (!keys2.includes(key) || !areJsonEqual(jsonObj1[key], jsonObj2[key])) {
|
|
65
77
|
return false;
|
|
66
78
|
}
|
|
67
79
|
}
|
|
68
|
-
|
|
80
|
+
|
|
69
81
|
return true;
|
|
70
82
|
} else {
|
|
71
83
|
// For non-object types, use strict equality comparison
|
|
72
84
|
return jsonObj1 === jsonObj2;
|
|
73
85
|
}
|
|
74
|
-
}
|
|
86
|
+
};
|
|
75
87
|
|
|
76
88
|
const getDefaultMockDataFromConfig = (testConfig) => {
|
|
77
|
-
const defaultPath = path.join(getMockDir(testConfig),
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
}
|
|
89
|
+
const defaultPath = path.join(getMockDir(testConfig), "default.json");
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const defaultData = fs.readFileSync(defaultPath, "utf8");
|
|
93
|
+
let parsedData = JSON.parse(defaultData);
|
|
94
|
+
|
|
95
|
+
// Read and attach mock data for each entry in parsedData
|
|
96
|
+
parsedData.forEach((entry) => {
|
|
97
|
+
const mockFilePath = path.join(
|
|
98
|
+
getMockDir(testConfig),
|
|
99
|
+
"defaultMocks",
|
|
100
|
+
`mock_${entry.id}.json`
|
|
101
|
+
);
|
|
102
|
+
try {
|
|
103
|
+
const mockData = fs.readFileSync(mockFilePath, "utf8");
|
|
104
|
+
entry.fileContent = JSON.parse(mockData);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(`Error reading mock data for ${entry.path}:`, error);
|
|
107
|
+
return entry; // Return the original entry if there's an error
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return parsedData;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`Error reading or parsing default.json:`, error);
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
};
|
|
100
116
|
|
|
101
117
|
// src/index.js
|
|
102
118
|
const loadMockDataFromConfig = (testConfig, _testName) => {
|
|
103
119
|
try {
|
|
104
120
|
let testName = _testName;
|
|
105
|
-
if(!testName) {
|
|
121
|
+
if (!testName) {
|
|
106
122
|
// Read the test ID from mockServer.config.json
|
|
107
|
-
const configPath = path.join(
|
|
108
|
-
|
|
123
|
+
const configPath = path.join(
|
|
124
|
+
getMockDir(testConfig),
|
|
125
|
+
"mockServer.config.json"
|
|
126
|
+
);
|
|
127
|
+
const configData = fs.readFileSync(configPath, "utf8");
|
|
109
128
|
const config = JSON.parse(configData);
|
|
110
129
|
testName = config.testName;
|
|
111
130
|
}
|
|
112
131
|
// Read the tests from testConfig
|
|
113
|
-
const mocksPath = path.join(
|
|
114
|
-
|
|
132
|
+
const mocksPath = path.join(
|
|
133
|
+
getMockDir(testConfig),
|
|
134
|
+
`test_${nameToFolder(testName)}`,
|
|
135
|
+
"_mock_list.json"
|
|
136
|
+
);
|
|
137
|
+
const mocksData = fs.readFileSync(mocksPath, "utf8");
|
|
115
138
|
const mocks = JSON.parse(mocksData);
|
|
116
139
|
|
|
117
|
-
mocks.forEach(mock => {
|
|
118
|
-
const fileContent = JSON.parse(
|
|
140
|
+
mocks.forEach((mock) => {
|
|
141
|
+
const fileContent = JSON.parse(
|
|
142
|
+
fs.readFileSync(
|
|
143
|
+
path.join(
|
|
144
|
+
getMockDir(testConfig),
|
|
145
|
+
`test_${nameToFolder(testName)}`,
|
|
146
|
+
`mock_${mock.id}.json`
|
|
147
|
+
),
|
|
148
|
+
"utf8"
|
|
149
|
+
)
|
|
150
|
+
);
|
|
119
151
|
mock.fileContent = fileContent;
|
|
120
152
|
});
|
|
121
153
|
|
|
122
|
-
|
|
123
154
|
return mocks;
|
|
124
155
|
} catch (error) {
|
|
125
|
-
console.debug(
|
|
156
|
+
console.debug("Error loading test data:", error.message);
|
|
126
157
|
return [];
|
|
127
158
|
}
|
|
128
159
|
};
|
|
129
160
|
|
|
130
|
-
const clearNulls = postData => {
|
|
131
|
-
Object.keys(postData || {}).forEach(key => {
|
|
132
|
-
if(postData[key] === null) {
|
|
161
|
+
const clearNulls = (postData) => {
|
|
162
|
+
Object.keys(postData || {}).forEach((key) => {
|
|
163
|
+
if (postData[key] === null) {
|
|
133
164
|
delete postData[key];
|
|
134
165
|
}
|
|
135
166
|
});
|
|
@@ -139,120 +170,188 @@ const isSameRequest = (req1, req2) => {
|
|
|
139
170
|
clearNulls(req1.postData);
|
|
140
171
|
clearNulls(req2.postData);
|
|
141
172
|
let matched = true;
|
|
142
|
-
if(req1.url !== req2.url) {
|
|
173
|
+
if (req1.url !== req2.url) {
|
|
143
174
|
matched = false;
|
|
144
|
-
} else if(req1.method?.toLowerCase() !== req2.method?.toLowerCase()) {
|
|
175
|
+
} else if (req1.method?.toLowerCase() !== req2.method?.toLowerCase()) {
|
|
145
176
|
matched = false;
|
|
146
|
-
} else if
|
|
147
|
-
|
|
148
|
-
|
|
177
|
+
} else if (
|
|
178
|
+
(!req1.postData && req2.postData) ||
|
|
179
|
+
(req1.postData && !req2.postData)
|
|
180
|
+
) {
|
|
181
|
+
matched = areJsonEqual(req1.postData || {}, req2.postData || {});
|
|
182
|
+
} else if (
|
|
183
|
+
req1.postData &&
|
|
184
|
+
req2.postData &&
|
|
185
|
+
!areJsonEqual(req1.postData, req2.postData)
|
|
186
|
+
) {
|
|
149
187
|
matched = false;
|
|
150
188
|
}
|
|
151
189
|
return matched;
|
|
152
|
-
}
|
|
190
|
+
};
|
|
153
191
|
|
|
154
|
-
const processURL = (url, ignoreParams=[]) => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
192
|
+
const processURL = (url, ignoreParams = []) => {
|
|
193
|
+
// Remove the hostname from the URL
|
|
194
|
+
const urlWithoutHost = url.replace(/^(https?:\/\/)?[^\/]+/, "");
|
|
195
|
+
const processedURL = new URL(`http://domain.com${urlWithoutHost}`);
|
|
196
|
+
const params = new URLSearchParams(processedURL.search);
|
|
197
|
+
if (ignoreParams?.length > 0) {
|
|
198
|
+
ignoreParams.forEach((ip) => {
|
|
199
|
+
params.delete(ip);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
params.sort();
|
|
203
|
+
return decodeURIComponent(`${processedURL.pathname}?${params}`);
|
|
204
|
+
};
|
|
168
205
|
|
|
169
206
|
function compareMockToRequest(mock, req) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
207
|
+
const mockURL = processURL(
|
|
208
|
+
mock.fileContent.url,
|
|
209
|
+
mock.fileContent.ignoreParams
|
|
210
|
+
);
|
|
211
|
+
const reqURL = processURL(req.originalUrl, mock.fileContent.ignoreParams);
|
|
212
|
+
const postData = mock.fileContent.request?.postData?.text
|
|
213
|
+
? JSON.parse(mock.fileContent.request?.postData?.text)
|
|
214
|
+
: mock.fileContent.request?.postData;
|
|
215
|
+
return isSameRequest(
|
|
216
|
+
{ url: mockURL, method: mock.fileContent.method, postData },
|
|
217
|
+
{
|
|
174
218
|
method: req.method,
|
|
175
219
|
postData: req.body,
|
|
176
220
|
url: reqURL,
|
|
177
|
-
}
|
|
221
|
+
}
|
|
222
|
+
);
|
|
178
223
|
}
|
|
179
224
|
|
|
180
225
|
function compareMockToFetchRequest(mock, fetchReq) {
|
|
181
|
-
try{
|
|
182
|
-
const mockURL = processURL(
|
|
226
|
+
try {
|
|
227
|
+
const mockURL = processURL(
|
|
228
|
+
mock.fileContent.url,
|
|
229
|
+
mock.fileContent.ignoreParams
|
|
230
|
+
);
|
|
183
231
|
const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
|
|
184
|
-
const postData = mock.fileContent.request?.postData?.text
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
url:
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
232
|
+
const postData = mock.fileContent.request?.postData?.text
|
|
233
|
+
? JSON.parse(mock.fileContent.request?.postData?.text)
|
|
234
|
+
: mock.fileContent.request?.postData;
|
|
235
|
+
return isSameRequest(
|
|
236
|
+
{ url: mockURL, method: mock.fileContent.method, postData },
|
|
237
|
+
{
|
|
238
|
+
method: fetchReq.options.method || "GET",
|
|
239
|
+
postData: fetchReq.options.body?.length
|
|
240
|
+
? JSON.parse(fetchReq.options.body)
|
|
241
|
+
: fetchReq.options.body,
|
|
242
|
+
url: reqURL,
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
} catch (e) {
|
|
246
|
+
console.debug("error at compareMockToFetchRequest", mock, fetchReq);
|
|
192
247
|
console.debug(e);
|
|
193
248
|
}
|
|
194
249
|
return false;
|
|
195
250
|
}
|
|
196
251
|
|
|
197
|
-
function getMatchingMockData({
|
|
252
|
+
function getMatchingMockData({
|
|
253
|
+
testMockData,
|
|
254
|
+
defaultMockData,
|
|
255
|
+
url,
|
|
256
|
+
options,
|
|
257
|
+
testConfig,
|
|
258
|
+
testName,
|
|
259
|
+
}) {
|
|
198
260
|
let served = false;
|
|
199
|
-
let matchedMocks =
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
261
|
+
let matchedMocks =
|
|
262
|
+
testMockData?.filter((mock) => {
|
|
263
|
+
if (mock.fileContent.waitForPrevious && !served) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
served = mock.fileContent.served;
|
|
267
|
+
return compareMockToFetchRequest(mock, { url, options });
|
|
268
|
+
}) || [];
|
|
269
|
+
let foundMock = matchedMocks.find((mock) => !mock.fileContent.served)
|
|
270
|
+
? matchedMocks.find((mock) => !mock.fileContent.served)
|
|
271
|
+
: matchedMocks[matchedMocks.length - 1];
|
|
207
272
|
// updating stats to mock file
|
|
208
|
-
if(foundMock) {
|
|
209
|
-
const mockFilePath = path.join(
|
|
273
|
+
if (foundMock) {
|
|
274
|
+
const mockFilePath = path.join(
|
|
275
|
+
getMockDir(testConfig),
|
|
276
|
+
`test_${nameToFolder(testName)}`,
|
|
277
|
+
`mock_${foundMock.id}.json`
|
|
278
|
+
);
|
|
210
279
|
foundMock.fileContent.served = true;
|
|
211
|
-
fs.writeFileSync(
|
|
280
|
+
fs.writeFileSync(
|
|
281
|
+
mockFilePath,
|
|
282
|
+
JSON.stringify(foundMock.fileContent, null, 2)
|
|
283
|
+
);
|
|
212
284
|
}
|
|
213
|
-
|
|
214
|
-
if(!foundMock) {
|
|
215
|
-
foundMock = defaultMockData.find(tm =>
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
285
|
+
|
|
286
|
+
if (!foundMock) {
|
|
287
|
+
foundMock = defaultMockData.find((tm) =>
|
|
288
|
+
compareMockToFetchRequest(tm, {
|
|
289
|
+
url,
|
|
290
|
+
options,
|
|
291
|
+
})
|
|
292
|
+
);
|
|
219
293
|
}
|
|
220
294
|
return foundMock ? foundMock.fileContent : null;
|
|
221
295
|
}
|
|
222
296
|
|
|
223
|
-
async function resetAllMockStats({testMockData, testConfig, testName}) {
|
|
224
|
-
for(let i=0; i<testMockData.length; i++) {
|
|
297
|
+
async function resetAllMockStats({ testMockData, testConfig, testName }) {
|
|
298
|
+
for (let i = 0; i < testMockData.length; i++) {
|
|
225
299
|
const tmd = testMockData[i];
|
|
226
|
-
const mockFilePath = path.join(
|
|
300
|
+
const mockFilePath = path.join(
|
|
301
|
+
getMockDir(testConfig),
|
|
302
|
+
`test_${nameToFolder(testName)}`,
|
|
303
|
+
`mock_${tmd.id}.json`
|
|
304
|
+
);
|
|
227
305
|
tmd.fileContent.served = false;
|
|
228
|
-
await fs.writeFileSync(
|
|
306
|
+
await fs.writeFileSync(
|
|
307
|
+
mockFilePath,
|
|
308
|
+
JSON.stringify(tmd.fileContent, null, 2)
|
|
309
|
+
);
|
|
229
310
|
}
|
|
230
311
|
}
|
|
231
312
|
|
|
232
|
-
async function initiatePlaywrightRoutes
|
|
233
|
-
|
|
234
|
-
|
|
313
|
+
async function initiatePlaywrightRoutes(
|
|
314
|
+
page,
|
|
315
|
+
ftmocksConifg,
|
|
316
|
+
testName,
|
|
317
|
+
mockPath = "**/*",
|
|
318
|
+
excludeMockPath = null
|
|
319
|
+
) {
|
|
320
|
+
const testMockData = testName
|
|
321
|
+
? loadMockDataFromConfig(ftmocksConifg, testName)
|
|
322
|
+
: [];
|
|
323
|
+
resetAllMockStats({ testMockData, testConfig: ftmocksConifg, testName });
|
|
235
324
|
const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
|
|
236
|
-
console.debug(
|
|
325
|
+
console.debug("calling initiatePlaywrightRoutes fetch");
|
|
237
326
|
await page.route(mockPath, async (route, request) => {
|
|
238
327
|
const url = request.url();
|
|
239
328
|
const options = {
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
}
|
|
245
|
-
}
|
|
329
|
+
url,
|
|
330
|
+
method: request.method(),
|
|
331
|
+
body: request.postData(),
|
|
332
|
+
};
|
|
246
333
|
if (excludeMockPath && new RegExp(excludeMockPath).test(url)) {
|
|
247
334
|
await route.fallback();
|
|
248
335
|
return;
|
|
249
336
|
}
|
|
250
|
-
console.debug(
|
|
251
|
-
|
|
337
|
+
console.debug(
|
|
338
|
+
"got fetch request",
|
|
339
|
+
request.method(),
|
|
340
|
+
request.url(),
|
|
341
|
+
request.postData()
|
|
342
|
+
);
|
|
343
|
+
let mockData = getMatchingMockData({
|
|
344
|
+
testMockData,
|
|
345
|
+
defaultMockData,
|
|
346
|
+
url,
|
|
347
|
+
options,
|
|
348
|
+
testConfig: ftmocksConifg,
|
|
349
|
+
testName,
|
|
350
|
+
});
|
|
252
351
|
if (mockData) {
|
|
253
|
-
console.debug(
|
|
352
|
+
console.debug("mocked", url, options);
|
|
254
353
|
const { content, headers, status } = mockData.response;
|
|
255
|
-
|
|
354
|
+
|
|
256
355
|
const json = {
|
|
257
356
|
status,
|
|
258
357
|
headers: getHeaders(headers),
|
|
@@ -261,31 +360,37 @@ async function initiatePlaywrightRoutes (page, ftmocksConifg, testName, mockPath
|
|
|
261
360
|
|
|
262
361
|
await route.fulfill(json);
|
|
263
362
|
} else {
|
|
264
|
-
console.debug(
|
|
363
|
+
console.debug("missing mock data", url, options);
|
|
265
364
|
const fallbackDir = getFallbackDir(ftmocksConifg);
|
|
266
|
-
if(!fallbackDir) {
|
|
365
|
+
if (!fallbackDir) {
|
|
267
366
|
await route.fallback();
|
|
268
367
|
return;
|
|
269
368
|
}
|
|
270
369
|
const urlObj = new URL(route.request().url());
|
|
271
|
-
const filePath = path.join(
|
|
272
|
-
|
|
370
|
+
const filePath = path.join(
|
|
371
|
+
fallbackDir,
|
|
372
|
+
urlObj.pathname === "/" || urlObj.pathname === ""
|
|
373
|
+
? ftmocksConifg.FALLBACK_DIR_INDEX_FILE || "index.html"
|
|
374
|
+
: urlObj.pathname
|
|
375
|
+
);
|
|
376
|
+
console.debug("serving file ", filePath);
|
|
273
377
|
if (fs.existsSync(filePath) && fs.lstatSync(filePath).isFile()) {
|
|
274
378
|
const fileContent = fs.readFileSync(filePath);
|
|
275
379
|
const ext = path.extname(filePath);
|
|
276
|
-
const contentType =
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
380
|
+
const contentType =
|
|
381
|
+
{
|
|
382
|
+
".html": "text/html",
|
|
383
|
+
".css": "text/css",
|
|
384
|
+
".js": "application/javascript",
|
|
385
|
+
".json": "application/json",
|
|
386
|
+
".png": "image/png",
|
|
387
|
+
".jpg": "image/jpeg",
|
|
388
|
+
}[ext] || "application/octet-stream";
|
|
389
|
+
|
|
390
|
+
console.debug("serving file", filePath);
|
|
286
391
|
await route.fulfill({
|
|
287
392
|
body: fileContent,
|
|
288
|
-
headers: {
|
|
393
|
+
headers: { "Content-Type": contentType },
|
|
289
394
|
});
|
|
290
395
|
} else {
|
|
291
396
|
await route.fallback();
|
|
@@ -294,27 +399,36 @@ async function initiatePlaywrightRoutes (page, ftmocksConifg, testName, mockPath
|
|
|
294
399
|
});
|
|
295
400
|
}
|
|
296
401
|
|
|
297
|
-
async function initiateJestFetch
|
|
298
|
-
const testMockData = testName
|
|
299
|
-
|
|
402
|
+
async function initiateJestFetch(jest, ftmocksConifg, testName) {
|
|
403
|
+
const testMockData = testName
|
|
404
|
+
? loadMockDataFromConfig(ftmocksConifg, testName)
|
|
405
|
+
: [];
|
|
406
|
+
resetAllMockStats({ testMockData, testConfig: ftmocksConifg, testName });
|
|
300
407
|
const defaultMockData = getDefaultMockDataFromConfig(ftmocksConifg);
|
|
301
|
-
console.debug(
|
|
408
|
+
console.debug("calling initiateJestFetch fetch");
|
|
302
409
|
global.fetch = jest.fn((url, options = {}) => {
|
|
303
|
-
console.debug(
|
|
304
|
-
let mockData = getMatchingMockData({
|
|
410
|
+
console.debug("got fetch request", url, options);
|
|
411
|
+
let mockData = getMatchingMockData({
|
|
412
|
+
testMockData,
|
|
413
|
+
defaultMockData,
|
|
414
|
+
url,
|
|
415
|
+
options,
|
|
416
|
+
testConfig: ftmocksConifg,
|
|
417
|
+
testName,
|
|
418
|
+
});
|
|
305
419
|
if (mockData) {
|
|
306
|
-
console.debug(
|
|
420
|
+
console.debug("mocked", url, options);
|
|
307
421
|
} else {
|
|
308
|
-
console.debug(
|
|
422
|
+
console.debug("missing mock data", url, options);
|
|
309
423
|
return Promise.resolve({
|
|
310
424
|
status: 404,
|
|
311
|
-
headers: new Map([[
|
|
312
|
-
json: () => Promise.resolve({ error:
|
|
425
|
+
headers: new Map([["Content-Type", "application/json"]]),
|
|
426
|
+
json: () => Promise.resolve({ error: "Mock data not found" }),
|
|
313
427
|
});
|
|
314
428
|
}
|
|
315
|
-
|
|
429
|
+
|
|
316
430
|
const { content, headers, status } = mockData.response;
|
|
317
|
-
|
|
431
|
+
|
|
318
432
|
return Promise.resolve({
|
|
319
433
|
status,
|
|
320
434
|
headers: new Map(Object.entries(headers)),
|
|
@@ -322,14 +436,14 @@ async function initiateJestFetch (jest, ftmocksConifg, testName) {
|
|
|
322
436
|
});
|
|
323
437
|
});
|
|
324
438
|
|
|
325
|
-
console.debug(
|
|
439
|
+
console.debug("calling XMLHttpRequest fetch");
|
|
326
440
|
global.XMLHttpRequest = jest.fn(function () {
|
|
327
441
|
const xhrMock = {
|
|
328
442
|
open: jest.fn(),
|
|
329
443
|
send: jest.fn(),
|
|
330
444
|
setRequestHeader: jest.fn(),
|
|
331
445
|
getAllResponseHeaders: jest.fn(() => {
|
|
332
|
-
return
|
|
446
|
+
return "";
|
|
333
447
|
}),
|
|
334
448
|
getResponseHeader: jest.fn((header) => {
|
|
335
449
|
return null;
|
|
@@ -337,13 +451,13 @@ async function initiateJestFetch (jest, ftmocksConifg, testName) {
|
|
|
337
451
|
readyState: 4,
|
|
338
452
|
status: 0,
|
|
339
453
|
response: null,
|
|
340
|
-
responseText:
|
|
454
|
+
responseText: "",
|
|
341
455
|
headers: new Map(Object.entries(headers)),
|
|
342
456
|
onreadystatechange: null,
|
|
343
457
|
onload: null,
|
|
344
458
|
onerror: null,
|
|
345
459
|
};
|
|
346
|
-
|
|
460
|
+
|
|
347
461
|
xhrMock.send.mockImplementation(function () {
|
|
348
462
|
const mockData = getMatchingMockData({
|
|
349
463
|
testMockData,
|
|
@@ -353,16 +467,16 @@ async function initiateJestFetch (jest, ftmocksConifg, testName) {
|
|
|
353
467
|
testConfig: ftmocksConifg,
|
|
354
468
|
testName,
|
|
355
469
|
});
|
|
356
|
-
|
|
470
|
+
|
|
357
471
|
if (mockData) {
|
|
358
|
-
console.debug(
|
|
472
|
+
console.debug("mocked", xhrMock._url, xhrMock._options);
|
|
359
473
|
const { content, headers, status } = mockData.response;
|
|
360
|
-
|
|
474
|
+
|
|
361
475
|
xhrMock.status = status;
|
|
362
476
|
xhrMock.responseText = content;
|
|
363
477
|
xhrMock.response = content;
|
|
364
478
|
xhrMock.headers = new Map(Object.entries(headers));
|
|
365
|
-
|
|
479
|
+
|
|
366
480
|
if (xhrMock.onreadystatechange) {
|
|
367
481
|
xhrMock.onreadystatechange();
|
|
368
482
|
}
|
|
@@ -370,12 +484,12 @@ async function initiateJestFetch (jest, ftmocksConifg, testName) {
|
|
|
370
484
|
xhrMock.onload();
|
|
371
485
|
}
|
|
372
486
|
} else {
|
|
373
|
-
console.debug(
|
|
374
|
-
|
|
487
|
+
console.debug("missing mock data", xhrMock._url, xhrMock._options);
|
|
488
|
+
|
|
375
489
|
xhrMock.status = 404;
|
|
376
|
-
xhrMock.responseText = JSON.stringify({ error:
|
|
490
|
+
xhrMock.responseText = JSON.stringify({ error: "Mock data not found" });
|
|
377
491
|
xhrMock.response = xhrMock.responseText;
|
|
378
|
-
|
|
492
|
+
|
|
379
493
|
if (xhrMock.onreadystatechange) {
|
|
380
494
|
xhrMock.onreadystatechange();
|
|
381
495
|
}
|
|
@@ -384,59 +498,62 @@ async function initiateJestFetch (jest, ftmocksConifg, testName) {
|
|
|
384
498
|
}
|
|
385
499
|
}
|
|
386
500
|
});
|
|
387
|
-
|
|
501
|
+
|
|
388
502
|
xhrMock.open.mockImplementation(function (method, url) {
|
|
389
503
|
xhrMock._options = { method };
|
|
390
504
|
xhrMock._url = url;
|
|
391
505
|
});
|
|
392
|
-
|
|
506
|
+
|
|
393
507
|
return xhrMock;
|
|
394
508
|
});
|
|
395
|
-
|
|
509
|
+
|
|
396
510
|
return;
|
|
397
|
-
}
|
|
511
|
+
}
|
|
398
512
|
|
|
399
513
|
function initiateConsoleLogs(jest, ftmocksConifg, testName) {
|
|
400
|
-
const logsFile = path.join(
|
|
514
|
+
const logsFile = path.join(
|
|
515
|
+
getMockDir(ftmocksConifg),
|
|
516
|
+
`test_${nameToFolder(testName)}`,
|
|
517
|
+
"_logs.json"
|
|
518
|
+
);
|
|
401
519
|
let logs = [];
|
|
402
|
-
if(!fs.existsSync(logsFile)) {
|
|
403
|
-
fs.appendFileSync(logsFile,
|
|
520
|
+
if (!fs.existsSync(logsFile)) {
|
|
521
|
+
fs.appendFileSync(logsFile, "[]", "utf8");
|
|
404
522
|
} else {
|
|
405
|
-
fs.writeFileSync(logsFile,
|
|
523
|
+
fs.writeFileSync(logsFile, "[]", "utf8");
|
|
406
524
|
}
|
|
407
525
|
|
|
408
526
|
const writeToFile = (type, params) => {
|
|
409
|
-
const logMessage = params.join(
|
|
527
|
+
const logMessage = params.join(" ") + "\n"; // Combine params into a string with spaces
|
|
410
528
|
logs.push({
|
|
411
529
|
type,
|
|
412
530
|
message: logMessage,
|
|
413
|
-
time: Date.now()
|
|
531
|
+
time: Date.now(),
|
|
414
532
|
});
|
|
415
|
-
fs.writeFileSync(logsFile, JSON.stringify(logs, null, 2),
|
|
416
|
-
}
|
|
417
|
-
|
|
533
|
+
fs.writeFileSync(logsFile, JSON.stringify(logs, null, 2), "utf8"); // Append the log message to the file
|
|
534
|
+
};
|
|
535
|
+
|
|
418
536
|
global.console = {
|
|
419
537
|
...console,
|
|
420
538
|
// uncomment to ignore a specific log level
|
|
421
539
|
log: jest.fn((...params) => {
|
|
422
|
-
writeToFile(
|
|
540
|
+
writeToFile("log", params);
|
|
423
541
|
}),
|
|
424
542
|
debug: jest.fn((...params) => {
|
|
425
|
-
writeToFile(
|
|
543
|
+
writeToFile("debug", params);
|
|
426
544
|
}),
|
|
427
545
|
info: jest.fn((...params) => {
|
|
428
|
-
writeToFile(
|
|
546
|
+
writeToFile("info", params);
|
|
429
547
|
}),
|
|
430
548
|
warn: jest.fn((...params) => {
|
|
431
|
-
writeToFile(
|
|
549
|
+
writeToFile("warn", params);
|
|
432
550
|
}),
|
|
433
551
|
error: jest.fn((...params) => {
|
|
434
|
-
writeToFile(
|
|
552
|
+
writeToFile("error", params);
|
|
435
553
|
}),
|
|
436
554
|
};
|
|
437
555
|
}
|
|
438
556
|
|
|
439
|
-
|
|
440
557
|
function countFilesInDirectory(directoryPath) {
|
|
441
558
|
return new Promise((resolve, reject) => {
|
|
442
559
|
fs.readdir(directoryPath, (err, files) => {
|
|
@@ -445,7 +562,7 @@ function countFilesInDirectory(directoryPath) {
|
|
|
445
562
|
}
|
|
446
563
|
|
|
447
564
|
// Filter out directories and only count files
|
|
448
|
-
const fileCount = files.filter(file => {
|
|
565
|
+
const fileCount = files.filter((file) => {
|
|
449
566
|
const filePath = path.join(directoryPath, file);
|
|
450
567
|
return fs.statSync(filePath).isFile();
|
|
451
568
|
}).length;
|
|
@@ -456,62 +573,85 @@ function countFilesInDirectory(directoryPath) {
|
|
|
456
573
|
}
|
|
457
574
|
|
|
458
575
|
const saveSnap = async (html, ftmocksConifg, testName) => {
|
|
459
|
-
const snapFolder = path.join(
|
|
460
|
-
|
|
461
|
-
|
|
576
|
+
const snapFolder = path.join(
|
|
577
|
+
getMockDir(ftmocksConifg),
|
|
578
|
+
`test_${nameToFolder(testName)}`,
|
|
579
|
+
"_snaps"
|
|
580
|
+
);
|
|
581
|
+
const snapTemplate = path.join(
|
|
582
|
+
getMockDir(ftmocksConifg),
|
|
583
|
+
"snap_template.html"
|
|
584
|
+
);
|
|
585
|
+
|
|
462
586
|
if (!fs.existsSync(snapFolder)) {
|
|
463
587
|
fs.mkdirSync(snapFolder);
|
|
464
588
|
}
|
|
465
|
-
const fileCount =
|
|
589
|
+
const fileCount = await countFilesInDirectory(snapFolder);
|
|
466
590
|
const snapFilePath = path.join(snapFolder, `snap_${fileCount + 1}.html`);
|
|
467
591
|
let resHtml = html;
|
|
468
592
|
if (fs.existsSync(snapFolder)) {
|
|
469
|
-
const templateHtml = fs.readFileSync(snapTemplate,
|
|
470
|
-
resHtml = templateHtml.replace(
|
|
593
|
+
const templateHtml = fs.readFileSync(snapTemplate, "utf8");
|
|
594
|
+
resHtml = templateHtml.replace(
|
|
595
|
+
"<!--FtMocks-Snap-Template-To-Be-Replaced-->",
|
|
596
|
+
html
|
|
597
|
+
);
|
|
471
598
|
}
|
|
472
|
-
fs.writeFileSync(snapFilePath, resHtml)
|
|
599
|
+
fs.writeFileSync(snapFilePath, resHtml);
|
|
473
600
|
};
|
|
474
601
|
|
|
475
602
|
const deleteAllSnaps = async (ftmocksConifg, testName) => {
|
|
476
|
-
const snapFolder = path.join(
|
|
603
|
+
const snapFolder = path.join(
|
|
604
|
+
getMockDir(ftmocksConifg),
|
|
605
|
+
`test_${nameToFolder(testName)}`,
|
|
606
|
+
"_snaps"
|
|
607
|
+
);
|
|
477
608
|
fs.rmSync(snapFolder, { recursive: true, force: true });
|
|
478
609
|
};
|
|
479
610
|
|
|
480
611
|
const deleteAllLogs = async (ftmocksConifg, testName) => {
|
|
481
|
-
const mockDir = path.join(
|
|
612
|
+
const mockDir = path.join(
|
|
613
|
+
getMockDir(ftmocksConifg),
|
|
614
|
+
`test_${nameToFolder(testName)}`
|
|
615
|
+
);
|
|
482
616
|
const logFilePath = path.join(mockDir, `_logs.json`);
|
|
483
617
|
fs.rmSync(logFilePath, { recursive: true, force: true });
|
|
484
618
|
};
|
|
485
619
|
|
|
486
620
|
function initiateJestEventSnaps(jest, ftmocksConifg, testName) {
|
|
487
|
-
const mouseEvents = ftmocksConifg.snapEvents || [
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
621
|
+
const mouseEvents = ftmocksConifg.snapEvents || [
|
|
622
|
+
"click",
|
|
623
|
+
"change",
|
|
624
|
+
"url",
|
|
625
|
+
"dblclick",
|
|
626
|
+
"contextmenu",
|
|
627
|
+
];
|
|
628
|
+
mouseEvents.forEach((event) => {
|
|
629
|
+
jest
|
|
630
|
+
.spyOn(document, "addEventListener")
|
|
631
|
+
.mockImplementation((e, callback) => {
|
|
632
|
+
if (mouseEvents.includes(e)) {
|
|
633
|
+
saveSnap(document.outerHTML, ftmocksConifg, testName);
|
|
634
|
+
}
|
|
635
|
+
});
|
|
494
636
|
});
|
|
495
637
|
}
|
|
496
638
|
|
|
497
|
-
|
|
498
|
-
|
|
499
639
|
// Export functions as a module
|
|
500
640
|
module.exports = {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
641
|
+
compareMockToRequest,
|
|
642
|
+
processURL,
|
|
643
|
+
isSameRequest,
|
|
644
|
+
loadMockDataFromConfig,
|
|
645
|
+
getDefaultMockDataFromConfig,
|
|
646
|
+
nameToFolder,
|
|
647
|
+
compareMockToFetchRequest,
|
|
648
|
+
getMatchingMockData,
|
|
649
|
+
resetAllMockStats,
|
|
650
|
+
initiateJestFetch,
|
|
651
|
+
saveSnap,
|
|
652
|
+
deleteAllSnaps,
|
|
653
|
+
deleteAllLogs,
|
|
654
|
+
initiateConsoleLogs,
|
|
655
|
+
initiatePlaywrightRoutes,
|
|
656
|
+
initiateJestEventSnaps,
|
|
517
657
|
};
|