UNPKG

pactum

Version:

REST API Testing Tool for all levels in a Test Pyramid

605 lines (533 loc) 15.2 kB
const fs = require('fs'); const cl = require('cookie-lite'); const override = require('deep-override'); const path = require('path'); const Tosser = require('./Tosser'); const Expect = require('./expect'); const State = require('./State'); const helper = require('../helpers/helper'); const log = require('../plugins/logger'); const th = require('../helpers/toss.helper'); const utils = require('../helpers/utils'); const { PactumRequestError } = require('../helpers/errors'); const responseExpect = require('../exports/expect'); const hr = require('../helpers/handler.runner'); const rlc = require('../helpers/reporter.lifeCycle'); const config = require('../config'); const { findFile } = require('../exports/utils'); const stash = require('../exports/stash'); const { memorize_spec, is_spec_memoized } = require('../helpers/memo'); class Spec { constructor(name, data, opts) { this.id = helper.getRandomId(); this.flow = ''; this._name = ''; this.status = 'N/A'; this.failure = ''; this.recorded = {}; this._request = {}; this._response = null; this._returns = []; this._recorders = []; this._stores = []; this._expect = new Expect(); this._state = new State(); this.previousLogLevel = null; this.interactions = []; this._wait = null; this._save = null; this._data_maps = []; this._response_handlers = []; this._specHandlerData = data; this._sleep = ''; hr.spec(name, data, this); this._opts = opts || {}; this._opts.handler_name = name; this._expect.setDefaultResponseExpectations(); } sleep(ms) { this._sleep = ms; return this; } name(value) { this._name = value; this._expect.name = value; return this; } setState(name, data) { this._state.add(name, data); return this; } use(name, data) { this._specHandlerData = data; hr.spec(name, data, this); return this; } useInteraction(interaction, data) { if (config.request.disable_use_interaction) { return this; } this.interactions.push({ interaction, data }); return this; } useDataMap(key, value) { this._data_maps.push({ key, value }); return this; } useResponseHandler(key) { this._response_handlers.push(key); return this; } get(url) { validateRequestUrl(this._request, url); this._request.url = url; this._request.method = 'GET'; return this; } head(url) { validateRequestUrl(this._request, url); this._request.url = url; this._request.method = 'HEAD'; return this; } patch(url) { validateRequestUrl(this._request, url); this._request.url = url; this._request.method = 'PATCH'; return this; } post(url) { validateRequestUrl(this._request, url); this._request.url = url; this._request.method = 'POST'; return this; } put(url) { validateRequestUrl(this._request, url); this._request.url = url; this._request.method = 'PUT'; return this; } delete(url) { validateRequestUrl(this._request, url); this._request.url = url; this._request.method = 'DELETE'; return this; } options(url) { validateRequestUrl(this._request, url); this._request.url = url; this._request.method = 'OPTIONS'; return this; } trace(url) { validateRequestUrl(this._request, url); this._request.url = url; this._request.method = 'TRACE'; return this; } withMethod(method) { this._request.method = method; return this; } withPath(url) { validateRequestUrl(this._request, url); this._request.url = url; return this; } withPathParams(key, value) { if (!this._request.pathParams) { this._request.pathParams = {}; } if (typeof key === 'string') { this._request.pathParams[key] = value; } else { Object.assign(this._request.pathParams, key); } return this; } withQueryParams(key, value) { if (!this._request.queryParams) { this._request.queryParams = {}; } if (typeof key === 'string') { if (!helper.isValidString(key)) { throw new PactumRequestError('`key` is required'); } if (Object.keys(this._request.queryParams).includes(key)) { if (!Array.isArray(this._request.queryParams[key])) { this._request.queryParams[key] = [this._request.queryParams[key]]; } this._request.queryParams[key].push(value); } else { this._request.queryParams[key] = value; } } else { if (!helper.isValidObject(key) || Object.keys(key).length === 0) { throw new PactumRequestError('`params` are required'); } Object.assign(this._request.queryParams, key); } return this; } withGraphQLQuery(query) { if (typeof query !== 'string') { throw new PactumRequestError(`Invalid graphQL query - ${query}`); } if (!this._request.data) { this._request.data = {}; } this._request.data.query = query; return this; } withGraphQLVariables(variables) { if (!helper.isValidObject(variables)) { throw new PactumRequestError(`Invalid graphQL variables - ${variables}`); } if (!this._request.data) { this._request.data = {}; } this._request.data.variables = variables; return this; } withJson(json) { if (typeof json === 'string') { json = getJsonFromTemplateOrFile(json); } else if (typeof json !== 'object') { throw new PactumRequestError(`Invalid json in request - ${json}`); } this._request.data = json; return this; } withHeaders(key, value) { if (!this._request.headers) { this._request.headers = {}; } if (typeof key === 'string') { this._request.headers[key] = value; } else { if (!helper.isValidObject(key)) { throw new PactumRequestError('`headers` are required'); } Object.assign(this._request.headers, key); } return this; } withCookies(key, value) { if (!this._request.headers) { this._request.headers = {}; } let cookie; if (typeof key === 'object') { cookie = cl.serialize(key); } else { if (value) { cookie = `${key}=${value}`; } else { cookie = key; } } const headers = this._request.headers; if (headers['cookie'] !== undefined) { headers['cookie'] = headers['cookie'] + ';' + cookie; } else { headers['cookie'] = cookie; } return this; } withBody(body) { if (typeof body === 'object' && body && body.file) { this._request.data = fs.readFileSync(body.file) } else { this._request.data = body; } return this; } withForm(key, value) { if (!this._request._forms) this._request._forms = []; this._request._forms.push({ key, value }); return this; } withMultiPartFormData(key, value, options) { if (!this._request._multi_parts) this._request._multi_parts = []; this._request._multi_parts.push({ key, value, options }); return this; } withFile(key, filePath, options) { if (typeof filePath === 'object') { options = filePath; filePath = key; key = 'file'; } if (!filePath) { filePath = key; key = 'file'; } if (!options) { options = {}; } options.filename = options.filename ? options.filename : path.basename(filePath); return this.withMultiPartFormData(key, fs.readFileSync(filePath), options); } withCore(options) { this._request.core = this._request.core || {}; override(this._request.core, options); return this; } withAuth(username, password) { if (!this._request.core) { this._request.core = {}; } this._request.core.auth = `${username}:${password}`; return this; } withBearerToken(token) { if (typeof token !== 'string') { throw new PactumRequestError('`token` is required'); } if (!this._request.headers) { this._request.headers = {}; } this._request.headers["Authorization"] = "Bearer " + token; return this; } withFollowRedirects(follow) { if (typeof follow !== 'number' && typeof follow !== 'boolean') { throw new PactumRequestError('Follow redirects should be number or boolean'); } if (typeof follow == 'number' && follow >=0 ) { this._request.followRedirects = follow; return this; } this._request.followRedirects = follow; return this; } withCompression() { this._request.compression = true; return this; } retry(options, delay) { if (typeof options === 'undefined' || typeof options === 'number') { options = { count: options, delay: delay }; } this._request.retryOptions = options; return this; } useLogLevel(level) { this.previousLogLevel = log.level; log.setLevel(level); return this; } withRequestTimeout(timeout) { if (typeof timeout !== 'number') { throw new PactumRequestError(`Invalid timeout provided - ${timeout}`); } this._request.timeout = timeout; return this; } expect(handler, data) { this._expect.customExpectHandlers.push({ handler, data }); return this; } expectStatus(statusCode, message = '') { this._expect.statusCode = statusCode; this._expect.customMessage = message; return this; } expectHeader(header, value) { utils.upsertValues(this._expect.headers, { key: header, value, }); return this; } expectHeaderContains(header, value) { utils.upsertValues(this._expect.headerContains, { key: header, value, }); return this; } expectCookies(key, value) { this._expect.cookies.push(utils.createCookieObject(key, value)); return this; } expectCookiesLike(key, value) { this._expect.cookiesLike.push(utils.createCookieObject(key, value)); return this; } expectBody(body) { this._expect.body = body; return this; } expectBodyContains(value) { this._expect.bodyContains.push(value); return this; } expectJson(path, value) { typeof value === 'undefined' ? this._expect.json.push(getJsonFromTemplateOrFile(path)) : this._expect.jsonQuery.push({ path, value }); return this; } expectJsonAt(...args) { return this.expectJson(...args); } expectJsonLike(path, value) { typeof value === 'undefined' ? this._expect.jsonLike.push(getJsonFromTemplateOrFile(path)) : this._expect.jsonQueryLike.push({ path, value }); return this; } expectJsonLikeAt(...args) { return this.expectJsonLike(...args); } expectJsonSchema(path, value, options) { if (typeof options === 'object') { this._expect.jsonSchemaQuery.push({ path, value, options }); } else { if (typeof value === 'undefined') { this._expect.jsonSchema.push({ value: getJsonFromTemplateOrFile(path) }); } else { if (typeof path === 'object' && typeof value === 'object') { this._expect.jsonSchema.push({ value: path, options: value }); } else { this._expect.jsonSchemaQuery.push({ path, value }); } } } return this; } expectJsonSchemaAt(...args) { return this.expectJsonSchema(...args); } expectJsonMatch(path, value) { typeof value === 'undefined' ? this._expect.jsonMatch.push(getJsonFromTemplateOrFile(path)) : this._expect.jsonMatchQuery.push({ path, value }); return this; } expectJsonMatchAt(...args) { return this.expectJsonMatch(...args); } expectJsonMatchStrict(path, value) { typeof value === 'undefined' ? this._expect.jsonMatchStrict.push(getJsonFromTemplateOrFile(path)) : this._expect.jsonMatchStrictQuery.push({ path, value }); return this; } expectJsonMatchStrictAt(...args) { return this.expectJsonMatchStrict(...args); } expectJsonSnapshot(name, value) { typeof name === 'string' ? this._expect.jsonSnapshots.push({ name, value }) : this._expect.jsonSnapshots.push({ value: name }); return this; } expectJsonLength(path, value) { typeof value === 'undefined' ? this._expect.jsonLength.push(path) : this._expect.jsonLengthQuery.push({ path, value }); return this; } expectError(error) { this._expect.errors.push(error); return this; } updateSnapshot() { this._expect.updateSnapshot = true; return this; } expectResponseTime(value) { this._expect.responseTime = value; return this; } returns(handler) { if (this._response) { return th.getOutput(this, [handler]); } else { this._returns.push(handler); } return this; } stores(...args) { if (args.length === 0) { return this; } /** * @type {import('../internal.types').ISpecStore} */ const spec_store = {}; if (typeof args[0] === 'function') { spec_store.cb = args[0]; } else { spec_store.name = args[0]; spec_store.path = args[1]; spec_store.options = args[2]; } if (this._response) { th.storeSpecData(this, [spec_store]); } else { this._stores.push(spec_store); } return this; } records(name, path) { if (this._response) { th.recordSpecData(this, [{ name, path }]); } else { this._recorders.push({ name, path }); } return this; } wait(arg1, arg2) { this._wait = { arg1, arg2 }; return this; } inspect(inspect_path) { if (typeof inspect_path === 'string') { if (!Array.isArray(this._inspect)) { this._inspect = []; } this._inspect.push(inspect_path); } else if (typeof inspect_path === 'boolean') { this._inspect = inspect_path; } else { this._inspect = true; } return this; } save(path) { this._save = path; return this; } async toss() { if (this._opts.handler_name && typeof this._opts.memo !== 'undefined') { if (is_spec_memoized(this._opts.handler_name, this._opts.memo)) { return Promise.resolve(); } memorize_spec(this._opts.handler_name, this._opts.memo); } const tosser = new Tosser(this); return tosser.toss(); } then(resolve, reject) { this.toss() .then(res => resolve(res)) .catch(err => reject(err)); } response() { if (!this._response) { throw new PactumRequestError( `'response()' should be called after resolving 'toss()'` ); } return responseExpect(this._response, this); } end() { rlc.afterSpecReport(this); return this; } } function validateRequestUrl(request, url) { if (request.url && request.method) { throw new PactumRequestError( `Duplicate request initiated. Existing request - ${request.method} ${request.url}` ); } if (!helper.isValidString(url)) { throw new PactumRequestError(`Invalid request url - ${url}`); } } function getJsonFromTemplateOrFile(path) { if (typeof path === 'string') { if (stash.getDataTemplate()[path]) { return { '@DATA:TEMPLATE@': path }; } else { return JSON.parse(findFile(path)); } } return path; } module.exports = Spec;