supertest-graphql
Version:
Extends supertest to test a GraphQL endpoint
265 lines (264 loc) • 9.31 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SuperTestExecutionStreamingResultPool = exports.SuperTestExecutionStreamingResult = exports.SuperTestExecutionNextResult = exports.LEGACY_WEBSOCKET_PROTOCOL = void 0;
const graphql_1 = require("graphql");
const graphql_ws_1 = require("graphql-ws");
const subscriptions_transport_ws_1 = require("subscriptions-transport-ws");
const ws_1 = __importDefault(require("ws"));
const utils_1 = require("./utils");
/**
* The protocol implemented by the library `subscriptions-transport-ws` and
* that is now considered legacy.
*/
exports.LEGACY_WEBSOCKET_PROTOCOL = "graphql-ws";
class SuperTestExecutionNextResult {
constructor(pop) {
this.pop = pop;
this._asserts = [];
}
then(onfulfilled, onrejected) {
return __awaiter(this, void 0, void 0, function* () {
const res = yield this.pop;
yield this.assert(res);
if (onfulfilled)
return onfulfilled(res);
// @ts-expect-error no idea why
return res;
});
}
/**
* Assert that there is no errors (`.errors` field) in response returned from the GraphQL API.
*/
expectNoErrors() {
this._asserts.push((0, utils_1.wrapAssertFn)(utils_1.asserNoError));
return this;
}
assert(result) {
return __awaiter(this, void 0, void 0, function* () {
for (const assertFn of this._asserts) {
const maybeError = yield assertFn(result);
if (maybeError instanceof Error)
throw maybeError;
}
});
}
}
exports.SuperTestExecutionNextResult = SuperTestExecutionNextResult;
class SuperTestExecutionStreamingResult {
constructor(client, subscriber) {
this.client = client;
this.queue = new utils_1.BlockingQueue();
subscriber({
next: (res) => this.queue.push(res),
complete: () => {
// do something
},
error: () => {
// do something
},
closed: false,
});
}
/**
* Get the next result that the operation is emitting.
*/
next() {
return new SuperTestExecutionNextResult(this.queue.pop());
}
/**
* Flush the pending results from the queue.
*/
flush() {
return this.queue.flush();
}
/**
* Assert that no more results are pending.
*/
expectNoPending() {
if (this.queue.length > 0) {
throw new Error(`expect no pending, but got ${this.queue.length}`);
}
return this;
}
/**
* Close the operation and the connection.
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
yield this.client.dispose();
});
}
}
exports.SuperTestExecutionStreamingResult = SuperTestExecutionStreamingResult;
class SuperTestExecutionStreamingResultPool {
constructor() {
this._subscriptions = [];
}
endAll() {
return __awaiter(this, void 0, void 0, function* () {
yield Promise.all(this._subscriptions.map((c) => c.close()));
this._subscriptions = [];
});
}
add(sub) {
this._subscriptions.push(sub);
}
}
exports.SuperTestExecutionStreamingResultPool = SuperTestExecutionStreamingResultPool;
class SuperTestWSGraphQL {
constructor(_hostname, _pool) {
this._hostname = _hostname;
this._pool = _pool;
this._path = "/graphql";
this._protocol = "graphql-transport-ws";
}
/**
* Send a GraphQL Query Document to the GraphQL server for execution.
* @param operation - the query to execute as string or `DocumentNode`
* @param variables - the variables for this query
*/
subscribe(operation, variables) {
this.operation(operation, variables);
return this;
}
/**
* Send a GraphQL Query Document to the GraphQL server for execution.
* @param query - the query to execute as string or `DocumentNode`
* @param variables - the variables for this query
*/
query(query, variables) {
return this.operation(query, variables);
}
/**
* Send a GraphQL Query Document to the GraphQL server for execution.
* @param mutation - the mutation to execute as string or `DocumentNode`
* @param variables - the variables for this mutation
*/
mutate(mutation, variables) {
return this.operation(mutation, variables);
}
/**
* Send a GraphQL Query Document to the GraphQL server for execution.
* @param operation - the operation to execute as string or `DocumentNode`
* @param variables - the variables for this operation
*/
operation(operation, variables) {
if (typeof operation !== "string") {
this._operationName = (0, utils_1.getOperationName)(operation);
}
this._query = typeof operation === "string" ? operation : (0, graphql_1.print)(operation);
this._variables = variables;
return this;
}
/**
* Set variables.
* @param - variables
*/
variables(variables) {
this._variables = variables;
return this;
}
/**
* Set the GraphQL endpoint path.
*
* @default "/graphql"
*/
path(path) {
this._path = path;
return this;
}
/**
* Set the GraphQL WebSocket porotocol.
* You can set the legacy protocol with the variable `LEGACY_WEBSOCKET_PROTOCOL`.
*/
protocol(wsProtocol) {
this._protocol = wsProtocol;
return this;
}
/**
* Set connection params.
*/
connectionParams(params) {
this._connectionParams = params;
return this;
}
then(onfulfilled, onrejected) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
try {
const url = new URL(this._path, this._hostname).toString();
const connect = this._protocol === "graphql-ws"
? connectAndAdaptLegacyClient
: connectClient;
const client = yield connect(url, this._connectionParams);
if (!this._query)
throw new Error("Missing a query");
const query = this._query;
const streamingResult = new SuperTestExecutionStreamingResult(client, (observer) => client.subscribe({
query,
variables: this._variables,
operationName: this._operationName,
}, observer));
(_a = this._pool) === null || _a === void 0 ? void 0 : _a.add(streamingResult);
if (onfulfilled)
return onfulfilled(streamingResult);
// @ts-expect-error no idea why
return streamingResult;
}
catch (e) {
if (onrejected)
return onrejected(e);
throw new Error("No rejection");
}
});
}
}
exports.default = SuperTestWSGraphQL;
const connectClient = (url, connectionParams) => __awaiter(void 0, void 0, void 0, function* () {
return yield new Promise((res, reject) => {
const client = (0, graphql_ws_1.createClient)({
url,
connectionParams,
lazy: false,
onNonLazyError: (error) => reject(error),
webSocketImpl: ws_1.default,
});
client.on("connected", () => res(client));
});
});
const connectAndAdaptLegacyClient = (url, connectionParams) => __awaiter(void 0, void 0, void 0, function* () {
const legacyClient = yield new Promise((res, reject) => {
const client = new subscriptions_transport_ws_1.SubscriptionClient(url, {
connectionParams,
connectionCallback: (error) => {
if (error) {
client.close();
return reject(error);
}
res(client);
},
}, ws_1.default);
});
return {
dispose: () => {
legacyClient.close();
},
subscribe: (args, observer) => {
// @ts-expect-error not exact but fine
const { unsubscribe } = legacyClient.request(args).subscribe(observer);
return unsubscribe;
},
};
});