UNPKG

mockttp

Version:

Mock HTTP server for testing HTTP clients and stubbing webservices

173 lines 7.91 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildAdminServerModel = buildAdminServerModel; const _ = require("lodash"); const graphql_subscriptions_1 = require("graphql-subscriptions"); const util_1 = require("@httptoolkit/util"); const rule_deserialization_1 = require("../rules/rule-deserialization"); const request_utils_1 = require("../util/request-utils"); const graphqlSubscriptionPairs = Object.entries({ 'requestInitiated': 'request-initiated', 'requestBodyData': 'request-body-data', 'requestReceived': 'request', 'responseInitiated': 'response-initiated', 'responseInformation': 'response-information', 'responseBodyData': 'response-body-data', 'responseCompleted': 'response', 'webSocketRequest': 'websocket-request', 'webSocketAccepted': 'websocket-accepted', 'webSocketMessageReceived': 'websocket-message-received', 'webSocketMessageSent': 'websocket-message-sent', 'webSocketClose': 'websocket-close', 'requestAborted': 'abort', 'tlsPassthroughOpened': 'tls-passthrough-opened', 'tlsPassthroughClosed': 'tls-passthrough-closed', 'failedTlsRequest': 'tls-client-error', 'failedClientRequest': 'client-error', 'rawPassthroughOpened': 'raw-passthrough-opened', 'rawPassthroughClosed': 'raw-passthrough-closed', 'rawPassthroughData': 'raw-passthrough-data', 'ruleEvent': 'rule-event' }); async function buildMockedEndpointData(endpoint) { return { id: endpoint.id, explanation: endpoint.toString(true), seenRequests: await endpoint.getSeenRequests(), isPending: await endpoint.isPending() }; } const decodeAndSerializeBody = async (body, headers) => { try { const decoded = await (0, request_utils_1.decodeBodyBuffer)(body.buffer, headers); if (decoded === body.buffer) return false; // No decoding required - no-op. else return { decoded }; // Successful decoding result } catch (e) { return { decodingError: e?.message ?? 'Failed to decode message body' }; } }; const serverSideRuleBodySerializer = async (body, headers) => { const encoded = body.buffer.toString('base64'); const result = await decodeAndSerializeBody(body, headers); if (result === false) { // No decoding required - no-op. return { encoded }; } else if (result.decodingError !== undefined) { // Failed decoding - we just return the error message. return { encoded, decodingError: result.decodingError }; } else if (result.decoded) { // Success - we return both formats to the client return { encoded, decoded: result.decoded.toString('base64') }; } else { throw new util_1.UnreachableCheck(result); } }; // messageBodyDecoding === 'None' => Just send encoded body as base64 const noopRuleBodySerializer = (body) => body.buffer.toString('base64'); function buildAdminServerModel(mockServer, stream, ruleParams, options = {}) { const pubsub = new graphql_subscriptions_1.PubSub(); const messageBodyDecoding = options.messageBodyDecoding || 'server-side'; const ruleDeserializationOptions = { bodySerializer: messageBodyDecoding === 'server-side' ? serverSideRuleBodySerializer : noopRuleBodySerializer, ruleParams }; // Build a set of event publishing callbacks (but don't subscribe them yet - we only // want to subscribe on demand, to allow the server to opt-out unused event processing). const eventListeners = graphqlSubscriptionPairs.reduce((acc, [gqlName, eventName]) => { acc[eventName] = (evt) => { pubsub.publish(eventName, { [gqlName]: evt }); }; return acc; }, {}); const subscriptionResolvers = Object.fromEntries(graphqlSubscriptionPairs.map(([gqlName, eventName]) => ([ gqlName, { subscribe: () => { // Subscribe to the underlying server event, if we haven't already. Needs to actively check // currently listeners because reset() clears all listeners, so they may disappear any time. if (mockServer.listenerCount(eventName, eventListeners[eventName]) === 0) { mockServer.on(eventName, eventListeners[eventName]); } return pubsub.asyncIterator(eventName); } } ]))); return { Query: { mockedEndpoints: async () => { return Promise.all((await mockServer.getMockedEndpoints()).map(buildMockedEndpointData)); }, pendingEndpoints: async () => { return Promise.all((await mockServer.getPendingEndpoints()).map(buildMockedEndpointData)); }, mockedEndpoint: async (__, { id }) => { let endpoint = _.find(await mockServer.getMockedEndpoints(), (endpoint) => { return endpoint.id === id; }); if (!endpoint) return null; return buildMockedEndpointData(endpoint); } }, Mutation: { addRule: async (__, { input }) => { return mockServer.addRequestRule((0, rule_deserialization_1.deserializeRuleData)(input, stream, ruleDeserializationOptions)); }, addRules: async (__, { input }) => { return mockServer.addRequestRules(...input.map((rule) => (0, rule_deserialization_1.deserializeRuleData)(rule, stream, ruleDeserializationOptions))); }, setRules: async (__, { input }) => { return mockServer.setRequestRules(...input.map((rule) => (0, rule_deserialization_1.deserializeRuleData)(rule, stream, ruleDeserializationOptions))); }, addWebSocketRule: async (__, { input }) => { return mockServer.addWebSocketRule((0, rule_deserialization_1.deserializeWebSocketRuleData)(input, stream, ruleDeserializationOptions)); }, addWebSocketRules: async (__, { input }) => { return mockServer.addWebSocketRules(...input.map((rule) => (0, rule_deserialization_1.deserializeWebSocketRuleData)(rule, stream, ruleDeserializationOptions))); }, setWebSocketRules: async (__, { input }) => { return mockServer.setWebSocketRules(...input.map((rule) => (0, rule_deserialization_1.deserializeWebSocketRuleData)(rule, stream, ruleDeserializationOptions))); } }, Subscription: subscriptionResolvers, Request: { body: (request) => { return request.body.buffer; }, decodedBody: async (request) => { if (messageBodyDecoding === 'none') { throw new Error('Decoded body requested, but messageBodyDecoding is set to "none"'); } return (await decodeAndSerializeBody(request.body, request.headers)) || {}; // No decoding required } }, Response: { body: (response) => { return response.body.buffer; }, decodedBody: async (response) => { if (messageBodyDecoding === 'none') { throw new Error('Decoded body requested, but messageBodyDecoding is set to "none"'); } return (await decodeAndSerializeBody(response.body, response.headers)) || {}; // No decoding required } }, ClientError: { response: (error) => { if (error.response === 'aborted') return undefined; else return error.response; } } }; } //# sourceMappingURL=mockttp-admin-model.js.map