UNPKG

mockttp

Version:

Mock HTTP server for testing HTTP clients and stubbing webservices

101 lines 4.5 kB
"use strict"; 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