mockttp-mvs
Version:
Mock HTTP server for testing HTTP clients and stubbing webservices
254 lines (224 loc) • 9.61 kB
text/typescript
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;
}
}
};
}