// // E2E Test Setup // e2e_helpers.js // import { getMainDefinition } from "@apollo/client/utilities"; import { WebSocketLink } from "@apollo/client/link/ws"; import fetch from "cross-fetch"; import * as ws from "ws"; import axios from "axios"; import { split, HttpLink, ApolloClient, InMemoryCache, DocumentNode, SubscriptionOptions, } from "@apollo/client"; const host = "127.0.0.1"; const port = process.env.PORT === undefined ? "42772" : process.env.PORT; export type TestServerInfo = { port: string; host: string; }; /** * Our test apollo client <3 * * @export * @class TestApolloClient * @extends {ApolloClient} */ export class TestApolloClient extends ApolloClient { private wsLink: WebSocketLink; constructor(serverInfo: TestServerInfo) { const { host } = serverInfo; const wsLink = new WebSocketLink({ // @TODO: Update WS url uri: `ws://${host}:${port}/graphql`, webSocketImpl: ws, options: { reconnect: false, connectionParams: { Authorization: "Bearer dummy", }, }, }); const httpLink = new HttpLink({ // @TODO: update HTTP url uri: `http://${host}:${port}/graphql`, fetch: fetch, }); const splitLink = split( ({ query }) => { const definition = getMainDefinition(query); return ( definition.kind === "OperationDefinition" && definition.operation === "subscription" ); }, wsLink, httpLink ); super({ link: splitLink, cache: new InMemoryCache(), }); this.wsLink = wsLink; } stop() { super.stop(); // // This is important, or else your test session will never close. // Also the reason it inherits from ApolloClient instead of just creating it // That way we can sneak in that WS closing // (this.wsLink as any).subscriptionClient.close(); } } // // // A very rough helper for testing subscription // // @TODO: look into finding better alternatives // // type SubscriptionTestParams = { client: TestApolloClient; query: DocumentNode; variables?: SubscriptionOptions; }; type WaitCondition = () => boolean; /** * Helper to create a subscription with a bunch of handy methods * * @export * @param {SubscriptionTestParams} params * @returns */ export function createSubscription(params: SubscriptionTestParams) { const results: any[] = []; let error: any; let observer = params.client .subscribe({ errorPolicy: "all", query: params.query, variables: params.variables || {}, }) .subscribe({ next(data) { results.push(data); }, error: (err) => { error = err; }, }); return { results, observer, disconnect() { observer.unsubscribe(); }, wait(ms: number = 100) { return new Promise((done) => { setTimeout(() => { done(null); }, ms); }); }, waitForResults(opts: { len?: number; timeout?: number } = {}) { return new Promise((done, fail) => { let step = 50; let sum = 0; let timeout = opts.timeout ?? 3000; let len = opts.len ?? 1; const interval = setInterval(() => { if (!error && results.length >= len) { clearInterval(interval); return done(results); } if (error || sum >= timeout) { error = error || new Error( `Timeout: subscription did not receive the expected results after ${timeout}ms` ); clearInterval(interval); return fail(error); } sum += step; }, step); }); }, get triggerCount() { return results.length; }, get error() { return error; }, }; } export async function runGQL(query: string, headersOrToken?: string | any, variables?: string ): Promise<{[key: string]: any}> { return new Promise((resolve, reject) => { let headers = headersOrToken; // Except stringToken if (typeof headersOrToken === "string") { headers = { Authorization: `${headersOrToken}`, }; } axios .post(`http://${host}:${port}/graphql`, { query, variables }, { headers }) .then((response) => { if(response.data.errors) { console.log(response.data.errors) return resolve(response.data) } return resolve(response.data); }) .catch((error) => { console.log(error.response.data.errors) return resolve(error.response.data); }); }); }