Skip to content
This repository was archived by the owner on Feb 27, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions lib/commands/entities/PlaybackRequestMap.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const { SERIALIZE_VERSION } = require('../constants');
const { getInterceptedResponseId } = require('../functions/getInterceptedResponseId');
const { getMatcherAsString } = require('../functions/getMatcherAsString');

const { PlaybackRequestMatcher } = require('./PlaybackRequestMatcher');

class PlaybackRequestMap {
Expand Down Expand Up @@ -90,11 +89,13 @@ class PlaybackRequestMap {
if (!matcher) {
throw new Error(`No request matcher found with id: ${matcherId}`);
}
return matcher.getResponse(getInterceptedResponseId(
const responseCollection = matcher.getResponseCollection(getInterceptedResponseId(
interceptedRequest,
/* c8 ignore next*/
options.matching?.ignores ?? []
));
), interceptedRequest.url);

return responseCollection.getNextResponse();
}

notifyRequestStarted(matcherId) {
Expand Down
71 changes: 38 additions & 33 deletions lib/commands/entities/PlaybackRequestMatcher.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { getRequestMatcherId } = require('../functions/getRequestMatcherId');
const { getMatcherAsString } = require('../functions/getMatcherAsString');
const { PlaybackResponse } = require('./PlaybackResponse');
const { PlaybackResponseCollection } = require('./PlaybackResponseCollection');

class PlaybackRequestMatcher {
/**
Expand All @@ -27,9 +27,9 @@ class PlaybackRequestMatcher {
*/
#ignores = [];
/**
* @type {Map<string, PlaybackResponse>}
* @type {Map<string, PlaybackResponseCollection>}
*/
#responses = new Map();
#responseCollections = new Map();
/**
* When true, any response matching is ignored if only a single response is
* recorded for this request matcher.
Expand Down Expand Up @@ -123,39 +123,34 @@ class PlaybackRequestMatcher {
headers,
interceptedRequest
) {
let response = new PlaybackResponse(
statusCode,
statusMessage,
body,
headers,
let responseCollection = new PlaybackResponseCollection(
interceptedRequest,
this.#ignores
);
if (this.#responses.has(response.id)) {
response = this.#responses.get(response.id);
if (this.#responseCollections.has(responseCollection.id)) {
responseCollection = this.#responseCollections.get(responseCollection.id);
} else {
this.#responses.set(response.id, response);
this.#responseCollections.set(responseCollection.id, responseCollection);
}
response.addHit();
if (response.hits > 1 && this.#anyOnce) {

responseCollection.addResponse(statusCode, statusMessage, body, headers);
if (responseCollection.hits > 1 && this.#anyOnce) {
throw new Error('Request matcher has "matching.anyOnce" set on it, but has recorded more than one response.');
}
}

getResponse(responseId) {
let response = this.#responses.get(responseId);
if (!response && this.#anyOnce && this.#responses.size === 1) {
// Get the 1st and only response in the map.
response = this.#responses.values().next().value;
}
if (response) {
response.addHit();
getResponseCollection(responseCollectionId, interceptedRequestUrl) {
let responseCollection = this.#responseCollections.get(responseCollectionId);

if (!responseCollection) {
throw new Error(`No response collection found with ID: ${responseCollectionId} and URL: ${interceptedRequestUrl}`);
}
return response;

return responseCollection;
}

getAllResponses() {
return Array.from(this.#responses.values());
getAllResponseCollections() {
return Array.from(this.#responseCollections.values());
}

isPending() {
Expand All @@ -166,8 +161,8 @@ class PlaybackRequestMatcher {
if (this.#inflight > 0) {
return true;
}
for (const response of this.#responses.values()) {
hits += response.hits;
for (const responseCollection of this.#responseCollections.values()) {
hits += responseCollection.hits;
}
return hits < this.#atLeast;
}
Expand All @@ -180,9 +175,9 @@ class PlaybackRequestMatcher {
atLeast: this.#atLeast,
anyOnce: this.#anyOnce,
ignores: this.#ignores,
responses: Array.from(this.#responses.values())
.filter(response => response.hits > 0)
.map(response => response.serialize())
responseCollections: Array.from(this.#responseCollections.values())
.filter(responseCollection => responseCollection.hits > 0)
.map(responseCollection => responseCollection.serialize())
};
}

Expand All @@ -195,10 +190,20 @@ class PlaybackRequestMatcher {
this.#ignores = data.ignores;
// Assume that a request being deserialized is stale.
this.stale = true;
data.responses.forEach(entry => {
const response = new PlaybackResponse(entry);
this.#responses.set(response.id, response);
});

if (data.responses) {
data.responses.forEach(resp => {
const { id, url, ...rest } = resp;
// This should match the shape that the response collection constructor/deserialize method expects
const responseCollection = new PlaybackResponseCollection({ id, url, responses: [rest] });
this.#responseCollections.set(responseCollection.id, responseCollection);
});
} else {
data.responseCollections.forEach(entry => {
const responseCollection = new PlaybackResponseCollection(entry);
this.#responseCollections.set(responseCollection.id, responseCollection);
});
}
}
}

Expand Down
26 changes: 2 additions & 24 deletions lib/commands/entities/PlaybackResponse.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const { getInterceptedResponseId } = require('../functions/getInterceptedResponseId');
const { arrayBufferToBase64, base64ToArrayBuffer } = require('../functions/arrayBufferFns');

const jsonStringableTypes = ['string', 'number', 'boolean'];
Expand Down Expand Up @@ -50,10 +49,6 @@ function deserializeResponseBody(body, bodyType) {
}

class PlaybackResponse {
/**
* @type {string}
*/
#id = null;
/**
* @type {number}
*/
Expand All @@ -77,9 +72,6 @@ class PlaybackResponse {
/**
* @type {number}
*/
#hits = 0;

get id() { return this.#id; }

get statusCode() { return this.#statusCode; }

Expand All @@ -91,34 +83,27 @@ class PlaybackResponse {
/* c8 ignore next */
set headers(value) { this.#headers = value; }

get hits() { return this.#hits; }

/**
* @param {number} statusCode
* @param {string} statusMessage
* @param {any} body
* @param {{[key: string]: string}} headers
* @param {CypressInterceptedRequest} interceptedRequest
* @param {ResponseMatchingIgnores} ignores
*/
constructor(...args) {
switch (args.length) {
case 1: {
this.deserialize(args[0]);
break;
}
case 6: {
const [statusCode, statusMessage, body, headers, interceptedRequest, ignores] = args;
case 4: {
const [statusCode, statusMessage, body, headers] = args;
this.#statusCode = statusCode;
this.#statusMessage = statusMessage;
this.#body = body;
this.#headers = headers;
if (body) {
this.#bodyType = getResponseBodyType(body);
}
if (interceptedRequest) {
this.#id = getInterceptedResponseId(interceptedRequest, ignores);
}
break;
}
default: {
Expand All @@ -127,13 +112,8 @@ class PlaybackResponse {
}
}

addHit() {
this.#hits += 1;
}

serialize() {
return {
id: this.#id,
statusCode: this.#statusCode,
statusMessage: this.#statusMessage,
body: serializeResponseBody(this.#body, this.#bodyType),
Expand All @@ -143,14 +123,12 @@ class PlaybackResponse {
}

deserialize({
id,
statusCode,
statusMessage,
body,
bodyType,
headers
}) {
this.#id = id;
this.#statusCode = statusCode;
this.#statusMessage = statusMessage;
this.#body = deserializeResponseBody(body, bodyType);
Expand Down
102 changes: 102 additions & 0 deletions lib/commands/entities/PlaybackResponseCollection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
const { getInterceptedResponseId } = require('../functions/getInterceptedResponseId');
const { PlaybackResponse } = require('./PlaybackResponse');

class PlaybackResponseCollection {
/**
* @type {string}
*/
#id = null;
/**
* @type {number}
*/
#hits = 0;
/**
* @type {Array<PlaybackResponse>}
*/
#responses = [];
/**
* @type {string}
*/
#url = '';

get id() { return this.#id; }

get hits() { return this.#hits; }

get url() { return this.#url; }

get responses() { return this.#responses; }

/**
* @param {CypressInterceptedRequest} interceptedRequest
* @param {ResponseMatchingIgnores} ignores
*/
constructor(...args) {
switch (args.length) {
case 1: {
this.deserialize(args[0]);
break;
}
case 2: {
const [interceptedRequest, ignores] = args;
this.#id = getInterceptedResponseId(interceptedRequest, ignores);
this.#url = interceptedRequest.url;
break;
}
default: {
throw new RangeError(`Invalid number of arguments: ${args.length}`);
}
}
}

/**
* @param {number} statusCode
* @param {string} statusMessage
* @param {any} body
* @param {{[key: string]: string}} headers
*/
addResponse(statusCode, statusMessage, body, headers) {
this.#responses.push(new PlaybackResponse(statusCode, statusMessage, body, headers));
this.#hits += 1;
}

/**
* @returns {PlaybackResponse}
*/
getNextResponse() {
if (this.#hits > this.#responses.length - 1) {
throw new Error(`No more recorded responses found for request with URL: ${this.#url}. Hit Count: ${this.#hits + 1}`);
}

const response = this.#responses[this.#hits];

this.#hits += 1;

return response;
}

serialize() {
return {
id: this.#id,
url: this.#url,
responses: this.#responses.map((response) => response.serialize()),
};
}

deserialize({
id,
url,
responses
}) {
this.#id = id;
this.#url = url;
responses.forEach((responseData) => {
const response = new PlaybackResponse(responseData);
this.#responses.push(response);
});
}
}

module.exports = {
PlaybackResponseCollection
};
Loading