mockttp
Version:
Mock HTTP server for testing HTTP clients and stubbing webservices
101 lines • 4.5 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebSocketRule = void 0;
exports.explainSteps = explainSteps;
const types_1 = require("../../types");
const request_utils_1 = require("../../util/request-utils");
const rule_serialization_1 = require("../rule-serialization");
const matchers = require("../matchers");
const websocket_step_impls_1 = require("./websocket-step-impls");
class WebSocketRule {
constructor(data) {
this.requests = [];
this.requestCount = 0;
(0, rule_serialization_1.validateMockRuleData)(data);
this.id = data.id || crypto.randomUUID();
this.priority = data.priority ?? types_1.RulePriority.DEFAULT;
this.matchers = data.matchers;
this.completionChecker = data.completionChecker;
this.steps = data.steps.map((stepDefinition, i) => {
const step = Object.assign(Object.create(websocket_step_impls_1.WsStepLookup[stepDefinition.type].prototype), stepDefinition);
if (websocket_step_impls_1.WsStepLookup[step.type].isFinal && i !== data.steps.length - 1) {
throw new Error(`Cannot create a rule with a final step before the last position ("${step.explain()}" in position ${i + 1} of ${data.steps.length})`);
}
return step;
});
}
matches(request) {
return matchers.matchesAll(request, this.matchers);
}
handle(req, res, head, options) {
let stepsPromise = (async () => {
for (let step of this.steps) {
const result = await step.handle(req, res, head, options);
if (!result || result.continue === false)
break;
}
})();
// Requests are added to rule.requests as soon as they start being handled,
// as promises, which resolve only when the response & request body is complete.
if (options.record) {
this.requests.push(Promise.race([
// When the handler resolves, the request is completed:
stepsPromise,
// If the response is closed before the handler completes (due to aborts, handler
// timeouts, whatever) then that also counts as the request being completed:
new Promise((resolve) => res.on('close', resolve))
])
.catch(() => { }) // Ignore handler errors here - we're only tracking the request
.then(() => (0, request_utils_1.waitForCompletedRequest)(req)));
}
// Even if traffic recording is disabled, the number of matched
// requests is still tracked
this.requestCount += 1;
return stepsPromise;
}
isComplete() {
if (this.completionChecker) {
// If we have a specific rule, use that
return this.completionChecker.isComplete(this.requestCount);
}
else if (this.requestCount === 0) {
// Otherwise, by default we're definitely incomplete if we've seen no requests
return false;
}
else {
// And we're _maybe_ complete if we've seen at least one request. In reality, we're incomplete
// but we should be used anyway if we're at any point we're the last matching rule for a request.
return null;
}
}
explain(withoutExactCompletion = false) {
let explanation = `Match websockets ${matchers.explainMatchers(this.matchers)}, ` +
`and then ${explainSteps(this.steps)}`;
if (this.completionChecker) {
explanation += `, ${this.completionChecker.explain(withoutExactCompletion ? undefined : this.requestCount)}.`;
}
else {
explanation += '.';
}
return explanation;
}
dispose() {
this.steps.forEach(s => s.dispose());
this.matchers.forEach(m => m.dispose());
if (this.completionChecker)
this.completionChecker.dispose();
}
}
exports.WebSocketRule = WebSocketRule;
function explainSteps(steps) {
if (steps.length === 1)
return steps[0].explain();
if (steps.length === 2) {
return `${steps[0].explain()} then ${steps[1].explain()}`;
}
// With 3+, we need to oxford comma separate explanations to make them readable
return steps.slice(0, -1)
.map((s) => s.explain())
.join(', ') + ', and ' + steps.slice(-1)[0].explain();
}
//# sourceMappingURL=websocket-rule.js.map