UNPKG

mockttp-mvs

Version:

Mock HTTP server for testing HTTP clients and stubbing webservices

254 lines (224 loc) 9.61 kB
import * as _ from "lodash"; import { Duplex } from "stream"; import { PubSub } from "graphql-subscriptions"; import type { IResolvers } from "@graphql-tools/utils"; import type { MockttpServer } from "../server/mockttp-server"; import type { ServerMockedEndpoint } from "../server/mocked-endpoint"; import type { MockedEndpoint, MockedEndpointData, CompletedRequest, CompletedResponse, ClientError } from "../types"; import type { Serialized } from "../serialization/serialization"; import type { RequestRuleData } from "../rules/requests/request-rule"; import type { WebSocketRuleData } from "../rules/websockets/websocket-rule"; import { deserializeRuleData, deserializeWebSocketRuleData } from "../rules/rule-deserialization"; const REQUEST_INITIATED_TOPIC = 'request-initiated'; const REQUEST_RECEIVED_TOPIC = 'request-received'; const RESPONSE_COMPLETED_TOPIC = 'response-completed'; const WEBSOCKET_REQUEST_TOPIC = 'websocket-request'; const WEBSOCKET_ACCEPTED_TOPIC = 'websocket-accepted'; const WEBSOCKET_MESSAGE_RECEIVED_TOPIC = 'websocket-message-received'; const WEBSOCKET_MESSAGE_SENT_TOPIC = 'websocket-message-sent'; const WEBSOCKET_CLOSE_TOPIC = 'websocket-close'; const REQUEST_ABORTED_TOPIC = 'request-aborted'; const TLS_PASSTHROUGH_OPENED_TOPIC = 'tls-passthrough-opened'; const TLS_PASSTHROUGH_CLOSED_TOPIC = 'tls-passthrough-closed'; const TLS_CLIENT_ERROR_TOPIC = 'tls-client-error'; const CLIENT_ERROR_TOPIC = 'client-error'; async function buildMockedEndpointData(endpoint: ServerMockedEndpoint): Promise<MockedEndpointData> { return { id: endpoint.id, explanation: endpoint.toString(true), seenRequests: await endpoint.getSeenRequests(), isPending: await endpoint.isPending() }; } export function buildAdminServerModel( mockServer: MockttpServer, stream: Duplex, ruleParameters: { [key: string]: any } ): IResolvers { const pubsub = new PubSub(); mockServer.on('request-initiated', (evt) => { pubsub.publish(REQUEST_INITIATED_TOPIC, { requestInitiated: evt }) }); mockServer.on('request', (evt) => { pubsub.publish(REQUEST_RECEIVED_TOPIC, { requestReceived: evt }) }); mockServer.on('response', (evt) => { pubsub.publish(RESPONSE_COMPLETED_TOPIC, { responseCompleted: evt }) }); mockServer.on('websocket-request', (evt) => { pubsub.publish(WEBSOCKET_REQUEST_TOPIC, { webSocketRequest: evt }) }); mockServer.on('websocket-accepted', (evt) => { pubsub.publish(WEBSOCKET_ACCEPTED_TOPIC, { webSocketAccepted: evt }) }); mockServer.on('websocket-message-received', (evt) => { pubsub.publish(WEBSOCKET_MESSAGE_RECEIVED_TOPIC, { webSocketMessageReceived: evt }) }); mockServer.on('websocket-message-sent', (evt) => { pubsub.publish(WEBSOCKET_MESSAGE_SENT_TOPIC, { webSocketMessageSent: evt }) }); mockServer.on('websocket-close', (evt) => { pubsub.publish(WEBSOCKET_CLOSE_TOPIC, { webSocketClose: evt }) }); mockServer.on('abort', (evt) => { pubsub.publish(REQUEST_ABORTED_TOPIC, { requestAborted: Object.assign(evt, { // Backward compat: old clients expect this to be present. In future this can be // removed and abort events can lose the 'body' in the schema. body: Buffer.alloc(0) }) }) }); mockServer.on('tls-passthrough-opened', (evt) => { pubsub.publish(TLS_PASSTHROUGH_OPENED_TOPIC, { tlsPassthroughOpened: evt }) }); mockServer.on('tls-passthrough-closed', (evt) => { pubsub.publish(TLS_PASSTHROUGH_CLOSED_TOPIC, { tlsPassthroughClosed: evt }) }); mockServer.on('tls-client-error', (evt) => { pubsub.publish(TLS_CLIENT_ERROR_TOPIC, { failedTlsRequest: evt }) }); mockServer.on('client-error', (evt) => { pubsub.publish(CLIENT_ERROR_TOPIC, { failedClientRequest: evt }) }); return <any> { Query: { mockedEndpoints: async (): Promise<MockedEndpointData[]> => { return Promise.all((await mockServer.getMockedEndpoints()).map(buildMockedEndpointData)); }, pendingEndpoints: async (): Promise<MockedEndpointData[]> => { return Promise.all((await mockServer.getPendingEndpoints()).map(buildMockedEndpointData)); }, mockedEndpoint: async (__: any, { id }: { id: string }): Promise<MockedEndpointData | null> => { let endpoint = _.find(await mockServer.getMockedEndpoints(), (endpoint: MockedEndpoint) => { return endpoint.id === id; }); if (!endpoint) return null; return buildMockedEndpointData(endpoint); } }, Mutation: { addRule: async (__: any, { input }: { input: Serialized<RequestRuleData> }) => { return mockServer.addRequestRule(deserializeRuleData(input, stream, ruleParameters)); }, addRules: async (__: any, { input }: { input: Array<Serialized<RequestRuleData>> }) => { return mockServer.addRequestRules(...input.map((rule) => deserializeRuleData(rule, stream, ruleParameters) )); }, setRules: async (__: any, { input }: { input: Array<Serialized<RequestRuleData>> }) => { return mockServer.setRequestRules(...input.map((rule) => deserializeRuleData(rule, stream, ruleParameters) )); }, setFallbackRule: async (__: any, { input }: { input: Serialized<RequestRuleData> }) => { // Deprecated endpoint, but preserved for API backward compat const ruleData = deserializeRuleData(input, stream, ruleParameters); return mockServer.addRequestRules({ ...ruleData, priority: 0 }).then((rules) => rules[0]); }, addWebSocketRule: async (__: any, { input }: { input: Serialized<WebSocketRuleData> }) => { return mockServer.addWebSocketRule(deserializeWebSocketRuleData(input, stream, ruleParameters)); }, addWebSocketRules: async (__: any, { input }: { input: Array<Serialized<WebSocketRuleData>> }) => { return mockServer.addWebSocketRules(...input.map((rule) => deserializeWebSocketRuleData(rule, stream, ruleParameters) )); }, setWebSocketRules: async (__: any, { input }: { input: Array<Serialized<WebSocketRuleData>> }) => { return mockServer.setWebSocketRules(...input.map((rule) => deserializeWebSocketRuleData(rule, stream, ruleParameters) )); } }, Subscription: { requestInitiated: { subscribe: () => pubsub.asyncIterator(REQUEST_INITIATED_TOPIC) }, requestReceived: { subscribe: () => pubsub.asyncIterator(REQUEST_RECEIVED_TOPIC) }, responseCompleted: { subscribe: () => pubsub.asyncIterator(RESPONSE_COMPLETED_TOPIC) }, webSocketRequest: { subscribe: () => pubsub.asyncIterator(WEBSOCKET_REQUEST_TOPIC) }, webSocketAccepted: { subscribe: () => pubsub.asyncIterator(WEBSOCKET_ACCEPTED_TOPIC) }, webSocketMessageReceived: { subscribe: () => pubsub.asyncIterator(WEBSOCKET_MESSAGE_RECEIVED_TOPIC) }, webSocketMessageSent: { subscribe: () => pubsub.asyncIterator(WEBSOCKET_MESSAGE_SENT_TOPIC) }, webSocketClose: { subscribe: () => pubsub.asyncIterator(WEBSOCKET_CLOSE_TOPIC) }, requestAborted: { subscribe: () => pubsub.asyncIterator(REQUEST_ABORTED_TOPIC) }, tlsPassthroughOpened: { subscribe: () => pubsub.asyncIterator(TLS_PASSTHROUGH_OPENED_TOPIC) }, tlsPassthroughClosed: { subscribe: () => pubsub.asyncIterator(TLS_PASSTHROUGH_CLOSED_TOPIC) }, failedTlsRequest: { subscribe: () => pubsub.asyncIterator(TLS_CLIENT_ERROR_TOPIC) }, failedClientRequest: { subscribe: () => pubsub.asyncIterator(CLIENT_ERROR_TOPIC) } }, Request: { body: (request: CompletedRequest) => { return request.body.buffer; } }, Response: { body: (response: CompletedResponse) => { return response.body.buffer; } }, ClientError: { response: (error: ClientError) => { if (error.response === 'aborted') return undefined; else return error.response; } } }; }