@vendure/testing
Version:
End-to-end testing tools for Vendure projects
258 lines • 9.52 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClientError = exports.SimpleGraphQLClient = void 0;
const shared_constants_1 = require("@vendure/common/lib/shared-constants");
const form_data_1 = __importDefault(require("form-data"));
const fs_1 = __importDefault(require("fs"));
const printer_1 = require("graphql/language/printer");
const graphql_tag_1 = __importDefault(require("graphql-tag"));
const node_fetch_1 = __importDefault(require("node-fetch"));
const querystring_1 = require("querystring");
const create_upload_post_data_1 = require("./utils/create-upload-post-data");
const LOGIN = (0, graphql_tag_1.default) `
mutation ($username: String!, $password: String!) {
login(username: $username, password: $password) {
... on CurrentUser {
id
identifier
channels {
token
}
}
... on ErrorResult {
errorCode
message
}
}
}
`;
/* eslint-disable no-console */
/**
* @description
* A minimalistic GraphQL client for populating and querying test data.
*
* @docsCategory testing
*/
class SimpleGraphQLClient {
constructor(vendureConfig, apiUrl = '') {
this.vendureConfig = vendureConfig;
this.apiUrl = apiUrl;
this.channelToken = null;
this.headers = {
'Apollo-Require-Preflight': 'true',
};
}
/**
* @description
* Sets the authToken to be used in each GraphQL request.
*/
setAuthToken(token) {
this.authToken = token;
this.headers.Authorization = `Bearer ${this.authToken}`;
}
/**
* @description
* Sets the authToken to be used in each GraphQL request.
*/
setChannelToken(token) {
this.channelToken = token;
if (this.vendureConfig.apiOptions.channelTokenKey) {
this.headers[this.vendureConfig.apiOptions.channelTokenKey] = this.channelToken;
}
}
/**
* @description
* Returns the authToken currently being used.
*/
getAuthToken() {
return this.authToken;
}
/**
* @description
* Performs both query and mutation operations.
*/
async query(query, variables, queryParams) {
const response = await this.makeGraphQlRequest(query, variables, queryParams);
const result = await this.getResult(response);
if (response.ok && !result.errors && result.data) {
return result.data;
}
else {
const errorResult = typeof result === 'string' ? { error: result } : result;
throw new ClientError(Object.assign(Object.assign({}, errorResult), { status: response.status }), { query: (0, printer_1.print)(query), variables });
}
}
/**
* @description
* Performs a raw HTTP request to the given URL, but also includes the authToken & channelToken
* headers if they have been set. Useful for testing non-GraphQL endpoints, e.g. for plugins
* which make use of REST controllers.
*/
async fetch(url, options = {}) {
const headers = Object.assign(Object.assign({ 'Content-Type': 'application/json' }, this.headers), options.headers);
const response = await (0, node_fetch_1.default)(url, Object.assign(Object.assign({}, options), { headers }));
const authToken = response.headers.get(this.vendureConfig.authOptions.authTokenHeaderKey || '');
if (authToken != null) {
this.setAuthToken(authToken);
}
return response;
}
/**
* @description
* Performs a query or mutation and returns the resulting status code.
*/
async queryStatus(query, variables) {
const response = await this.makeGraphQlRequest(query, variables);
return response.status;
}
/**
* @description
* Attempts to log in with the specified credentials.
*/
async asUserWithCredentials(username, password) {
var _a;
// first log out as the current user
if (this.authToken) {
await this.query((0, graphql_tag_1.default) `
mutation {
logout {
success
}
}
`);
}
const result = await this.query(LOGIN, { username, password });
if (((_a = result.login.channels) === null || _a === void 0 ? void 0 : _a.length) === 1) {
this.setChannelToken(result.login.channels[0].token);
}
return result.login;
}
/**
* @description
* Logs in as the SuperAdmin user.
*/
async asSuperAdmin() {
var _a, _b;
const { superadminCredentials } = this.vendureConfig.authOptions;
await this.asUserWithCredentials((_a = superadminCredentials === null || superadminCredentials === void 0 ? void 0 : superadminCredentials.identifier) !== null && _a !== void 0 ? _a : shared_constants_1.SUPER_ADMIN_USER_IDENTIFIER, (_b = superadminCredentials === null || superadminCredentials === void 0 ? void 0 : superadminCredentials.password) !== null && _b !== void 0 ? _b : shared_constants_1.SUPER_ADMIN_USER_PASSWORD);
}
/**
* @description
* Logs out so that the client is then treated as an anonymous user.
*/
async asAnonymousUser() {
await this.query((0, graphql_tag_1.default) `
mutation {
logout {
success
}
}
`);
}
async makeGraphQlRequest(query, variables, queryParams) {
const queryString = (0, printer_1.print)(query);
const body = JSON.stringify({
query: queryString,
variables: variables ? variables : undefined,
});
const url = queryParams ? this.apiUrl + `?${(0, querystring_1.stringify)(queryParams)}` : this.apiUrl;
return this.fetch(url, {
method: 'POST',
body,
});
}
async getResult(response) {
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.startsWith('application/json')) {
return response.json();
}
else {
return response.text();
}
}
/**
* @description
* Perform a file upload mutation.
*
* Upload spec: https://github.com/jaydenseric/graphql-multipart-request-spec
*
* Discussion of issue: https://github.com/jaydenseric/apollo-upload-client/issues/32
*
* @param mutation - GraphQL document for a mutation that has input files
* with the Upload type.
* @param filePaths - Array of paths to files, in the same order that the
* corresponding Upload fields appear in the variables for the mutation.
* @param mapVariables - Function that must return the variables for the
* mutation, with `null` as the value for each `Upload` field.
*
* @example
* ```ts
* // Testing a custom mutation:
* const result = await client.fileUploadMutation({
* mutation: gql`
* mutation AddSellerImages($input: AddSellerImagesInput!) {
* addSellerImages(input: $input) {
* id
* name
* }
* }
* `,
* filePaths: ['./images/profile-picture.jpg', './images/logo.png'],
* mapVariables: () => ({
* name: "George's Pans",
* profilePicture: null, // corresponds to filePaths[0]
* branding: {
* logo: null // corresponds to filePaths[1]
* }
* })
* });
* ```
*/
async fileUploadMutation(options) {
const { mutation, filePaths, mapVariables } = options;
const postData = (0, create_upload_post_data_1.createUploadPostData)(mutation, filePaths, mapVariables);
const body = new form_data_1.default();
body.append('operations', JSON.stringify(postData.operations));
body.append('map', '{' +
Object.entries(postData.map)
.map(([i, path]) => `"${i}":["${path}"]`)
.join(',') +
'}');
for (const filePath of postData.filePaths) {
const file = fs_1.default.readFileSync(filePath.file);
body.append(filePath.name, file, { filename: filePath.file });
}
const result = await (0, node_fetch_1.default)(this.apiUrl, {
method: 'POST',
body,
headers: Object.assign({}, this.headers),
});
const response = await result.json();
if (response.errors && response.errors.length) {
const error = response.errors[0];
throw new Error(error.message);
}
return response.data;
}
}
exports.SimpleGraphQLClient = SimpleGraphQLClient;
class ClientError extends Error {
constructor(response, request) {
super(ClientError.extractMessage(response));
this.response = response;
this.request = request;
}
static extractMessage(response) {
if (response.errors) {
return response.errors[0].message;
}
else {
return `GraphQL Error (Code: ${response.status})`;
}
}
}
exports.ClientError = ClientError;
//# sourceMappingURL=simple-graphql-client.js.map