ftmocks-utils 1.5.5 → 1.5.6

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.5.5",
3
+ "version": "1.5.6",
4
4
  "description": "Util functions for FtMocks",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -1,6 +1,70 @@
1
1
  const { clearNulls, processURL } = require("./common-utils");
2
2
  const { FtJSON } = require("./json-utils");
3
3
 
4
+ /** @param {Record<string, string>|undefined|null} headers */
5
+ function getHeaderValueCaseInsensitive(headers, headerName) {
6
+ if (!headers || !headerName) {
7
+ return undefined;
8
+ }
9
+ const lower = headerName.toLowerCase();
10
+ for (const key of Object.keys(headers)) {
11
+ if (key.toLowerCase() === lower) {
12
+ return headers[key];
13
+ }
14
+ }
15
+ return undefined;
16
+ }
17
+
18
+ function parseMatchHeadersList(testConfig) {
19
+ const raw = testConfig?.MATCH_HEADERS;
20
+ if (!raw || typeof raw !== "string") {
21
+ return [];
22
+ }
23
+ return raw
24
+ .split(",")
25
+ .map((h) => h.trim())
26
+ .filter(Boolean);
27
+ }
28
+
29
+ /** Normalize fetch `options.headers` (plain object or Headers) to a plain object. */
30
+ function normalizeIncomingHeaders(headers) {
31
+ if (!headers) {
32
+ return {};
33
+ }
34
+ if (typeof headers.forEach === "function") {
35
+ const out = {};
36
+ headers.forEach((value, key) => {
37
+ out[key] = value;
38
+ });
39
+ return out;
40
+ }
41
+ if (typeof headers === "object") {
42
+ return { ...headers };
43
+ }
44
+ return {};
45
+ }
46
+
47
+ /**
48
+ * When MATCH_HEADERS is set, each listed header must match between the mock
49
+ * recording and the incoming request (case-insensitive name, value compared as strings).
50
+ */
51
+ function incomingHeadersMatchMock(mock, incomingHeaders, testConfig) {
52
+ const names = parseMatchHeadersList(testConfig);
53
+ if (names.length === 0) {
54
+ return true;
55
+ }
56
+ const mockHeaders = mock.fileContent.request?.headers || {};
57
+ const incoming = incomingHeaders || {};
58
+ for (const name of names) {
59
+ const mockVal = getHeaderValueCaseInsensitive(mockHeaders, name);
60
+ const reqVal = getHeaderValueCaseInsensitive(incoming, name);
61
+ if (String(mockVal ?? "") !== String(reqVal ?? "")) {
62
+ return false;
63
+ }
64
+ }
65
+ return true;
66
+ }
67
+
4
68
  const isUrlAndMethodSame = (req1, req2) => {
5
69
  const url1 = new URL(`http://domain.com${req1.url}`);
6
70
  const url2 = new URL(`http://domain.com${req2.url}`);
@@ -45,7 +109,7 @@ const isSameResponse = (req1, req2) => {
45
109
  ) {
46
110
  matched = FtJSON.areJsonEqual(
47
111
  FtJSON.parse(req1.response.content) || {},
48
- FtJSON.parse(req2.response.content) || {}
112
+ FtJSON.parse(req2.response.content) || {},
49
113
  );
50
114
  // console.log('not matched at post Data 0', req1.postData, req2.postData);
51
115
  } else if (
@@ -53,7 +117,7 @@ const isSameResponse = (req1, req2) => {
53
117
  req2.response.content &&
54
118
  !FtJSON.areJsonEqual(
55
119
  FtJSON.parse(req1.response.content) || {},
56
- FtJSON.parse(req2.response.content) || {}
120
+ FtJSON.parse(req2.response.content) || {},
57
121
  )
58
122
  ) {
59
123
  matched = false;
@@ -71,12 +135,12 @@ const isSameResponse = (req1, req2) => {
71
135
  function compareMockToRequest(mock, req) {
72
136
  const mockURL = processURL(
73
137
  mock.fileContent.url,
74
- mock.fileContent.ignoreParams
138
+ mock.fileContent.ignoreParams,
75
139
  );
76
140
  const reqURL = processURL(req.originalUrl, mock.fileContent.ignoreParams);
77
141
  const isSameUrlAndMethod = isUrlAndMethodSame(
78
142
  { url: mockURL, method: mock.fileContent.method },
79
- { url: reqURL, method: req.method }
143
+ { url: reqURL, method: req.method },
80
144
  );
81
145
  if (!isSameUrlAndMethod) {
82
146
  return false;
@@ -90,20 +154,20 @@ function compareMockToRequest(mock, req) {
90
154
  method: req.method,
91
155
  postData: req.body,
92
156
  url: reqURL,
93
- }
157
+ },
94
158
  );
95
159
  }
96
160
 
97
- function compareMockToFetchRequest(mock, fetchReq) {
161
+ function compareMockToFetchRequest(mock, fetchReq, testConfig) {
98
162
  try {
99
163
  const mockURL = processURL(
100
164
  mock.fileContent.url,
101
- mock.fileContent.ignoreParams
165
+ mock.fileContent.ignoreParams,
102
166
  );
103
167
  const reqURL = processURL(fetchReq.url, mock.fileContent.ignoreParams);
104
168
  const isSameUrlAndMethod = isUrlAndMethodSame(
105
169
  { url: mockURL, method: mock.fileContent.method },
106
- { url: reqURL, method: fetchReq.options.method || "GET" }
170
+ { url: reqURL, method: fetchReq.options.method || "GET" },
107
171
  );
108
172
  if (!isSameUrlAndMethod) {
109
173
  return false;
@@ -111,7 +175,7 @@ function compareMockToFetchRequest(mock, fetchReq) {
111
175
  const postData = mock.fileContent.request?.postData?.text
112
176
  ? FtJSON.parse(mock.fileContent.request?.postData?.text)
113
177
  : mock.fileContent.request?.postData;
114
- return isSameRequest(
178
+ const sameBody = isSameRequest(
115
179
  { url: mockURL, method: mock.fileContent.method, postData },
116
180
  {
117
181
  method: fetchReq.options.method || "GET",
@@ -119,8 +183,13 @@ function compareMockToFetchRequest(mock, fetchReq) {
119
183
  ? FtJSON.parse(fetchReq.options.body)
120
184
  : fetchReq.options.body,
121
185
  url: reqURL,
122
- }
186
+ },
123
187
  );
188
+ if (!sameBody) {
189
+ return false;
190
+ }
191
+ const incomingHeaders = normalizeIncomingHeaders(fetchReq.options?.headers);
192
+ return incomingHeadersMatchMock(mock, incomingHeaders, testConfig);
124
193
  } catch (e) {
125
194
  console.error("error at compareMockToFetchRequest", mock, fetchReq);
126
195
  console.error(e);
@@ -148,4 +217,8 @@ module.exports = {
148
217
  compareMockToRequest,
149
218
  compareMockToFetchRequest,
150
219
  compareMockToMock,
220
+ getHeaderValueCaseInsensitive,
221
+ parseMatchHeadersList,
222
+ normalizeIncomingHeaders,
223
+ incomingHeadersMatchMock,
151
224
  };
@@ -38,7 +38,7 @@ function getMatchingMockData({
38
38
  }
39
39
  }
40
40
  served = mock.fileContent.served;
41
- return compareMockToFetchRequest(mock, { url, options });
41
+ return compareMockToFetchRequest(mock, { url, options }, testConfig);
42
42
  }) || [];
43
43
  let foundMock = matchedMocks.find((mock) => !mock.fileContent.served)
44
44
  ? matchedMocks.find((mock) => !mock.fileContent.served)
@@ -46,29 +46,41 @@ function getMatchingMockData({
46
46
 
47
47
  if (!foundMock) {
48
48
  foundMock = defaultMockData.find((tm) =>
49
- compareMockToFetchRequest(tm, {
50
- url,
51
- options,
52
- })
49
+ compareMockToFetchRequest(
50
+ tm,
51
+ {
52
+ url,
53
+ options,
54
+ },
55
+ testConfig
56
+ )
53
57
  );
54
58
  }
55
59
 
56
60
  if (!foundMock && mode !== "strict") {
57
61
  const mockRanks = {};
58
62
  testMockData.forEach((tm) => {
59
- const rank = getCompareRankMockToFetchRequest(tm, {
60
- url,
61
- options,
62
- });
63
+ const rank = getCompareRankMockToFetchRequest(
64
+ tm,
65
+ {
66
+ url,
67
+ options,
68
+ },
69
+ testConfig
70
+ );
63
71
  if (rank > 0) {
64
72
  mockRanks[tm.id] = rank;
65
73
  }
66
74
  });
67
75
  defaultMockData.forEach((tm) => {
68
- const rank = getCompareRankMockToFetchRequest(tm, {
69
- url,
70
- options,
71
- });
76
+ const rank = getCompareRankMockToFetchRequest(
77
+ tm,
78
+ {
79
+ url,
80
+ options,
81
+ },
82
+ testConfig
83
+ );
72
84
  if (rank > 0) {
73
85
  mockRanks[tm.id] = rank;
74
86
  }
@@ -157,20 +169,28 @@ function getMatchingMockDataV2({
157
169
  const mockRanks = {};
158
170
  matchedMocks.forEach((mockId) => {
159
171
  const mock = testMockIdMap[mockId];
160
- const rank = getCompareRankMockToFetchRequest(mock, {
161
- url,
162
- options,
163
- });
172
+ const rank = getCompareRankMockToFetchRequest(
173
+ mock,
174
+ {
175
+ url,
176
+ options,
177
+ },
178
+ testConfig
179
+ );
164
180
  if (rank > 0) {
165
181
  mockRanks[mock.id] = rank;
166
182
  }
167
183
  });
168
184
  defaultMatchedMocks.forEach((mockId) => {
169
185
  const mock = defaultMockIdMap[mockId];
170
- const rank = getCompareRankMockToFetchRequest(mock, {
171
- url,
172
- options,
173
- });
186
+ const rank = getCompareRankMockToFetchRequest(
187
+ mock,
188
+ {
189
+ url,
190
+ options,
191
+ },
192
+ testConfig
193
+ );
174
194
  if (rank > 0) {
175
195
  mockRanks[mock.id] = rank;
176
196
  }
@@ -46,6 +46,7 @@ async function initiatePlaywrightRoutes(
46
46
  url,
47
47
  method: request.method(),
48
48
  body: request.postData(),
49
+ headers: request.headers(),
49
50
  };
50
51
  if (excludeMockPath && new RegExp(excludeMockPath).test(url)) {
51
52
  await route.fallback();
@@ -1,6 +1,10 @@
1
1
  const { clearNulls, charDifference, processURL } = require("./common-utils");
2
2
  const { FtJSON } = require("./json-utils");
3
- const { isUrlAndMethodSame } = require("./compare-utils");
3
+ const {
4
+ isUrlAndMethodSame,
5
+ incomingHeadersMatchMock,
6
+ normalizeIncomingHeaders,
7
+ } = require("./compare-utils");
4
8
 
5
9
  const getSameRequestRank = (req1, req2) => {
6
10
  let rank = 1;
@@ -21,7 +25,7 @@ const getSameRequestRank = (req1, req2) => {
21
25
  return rank;
22
26
  };
23
27
 
24
- function getCompareRankMockToFetchRequest(mock, fetchReq) {
28
+ function getCompareRankMockToFetchRequest(mock, fetchReq, testConfig) {
25
29
  try {
26
30
  const mockURL = processURL(
27
31
  mock.fileContent.url,
@@ -36,6 +40,10 @@ function getCompareRankMockToFetchRequest(mock, fetchReq) {
36
40
  ) {
37
41
  return 0;
38
42
  }
43
+ const incomingHeaders = normalizeIncomingHeaders(fetchReq.options?.headers);
44
+ if (!incomingHeadersMatchMock(mock, incomingHeaders, testConfig)) {
45
+ return 0;
46
+ }
39
47
  const postData = mock.fileContent.request?.postData?.text
40
48
  ? FtJSON.parse(mock.fileContent.request?.postData?.text)
41
49
  : mock.fileContent.request?.postData;