UNPKG

@serenity-js/rest

Version:

Serenity/JS Screenplay Pattern library for interacting with REST and other HTTP-based services, supporting comprehensive API testing and blended testing scenarios

216 lines 8.15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChangeApiConfig = void 0; const node_url_1 = require("node:url"); const core_1 = require("@serenity-js/core"); const abilities_1 = require("../abilities"); /** * Changes configuration of the [ability](https://serenity-js.org/api/core/class/Ability/) to [`CallAnApi`](https://serenity-js.org/api/rest/class/CallAnApi/) * that the [actor](https://serenity-js.org/api/core/class/Actor/) executing this [interaction](https://serenity-js.org/api/core/class/Interaction/) has been configured with. * * ## Changing API URL for all subsequent requests * * ```ts * import { actorCalled } from '@serenity-js/core'; * import { By Navigate, PageElement, Text } from '@serenity-js/web'; * import { axiosCreate, CallAnApi, ChangeApiConfig, GetRequest, LastResponse, Send } from '@serenity-js/rest' * import { Ensure, equals } from '@serenity-js/assertions'; * * import * as axios from 'axios'; * * // Let's imagine that the website under test displays * // a dynamically generated API URL that we would like to use * const ApiDetailsWidget = { * url: () => PageElement.located(By.id('api-url')).describedAs('API URL'), * } * * await actorCalled('Apisitt') * .whoCan( * BrowseTheWeb.using(protractor.browser), * * // Note: no default base URL is given when the axios instance is created * CallAnApi.using(axiosCreate()), * ) * .attemptsTo( * Navigate.to('/profile'), * * // We change the API URL based on the text displayed in the widget * // (although we could change it to some arbitrary string too). * ChangeApiConfig.setUrlTo(Text.of(ApiDetailsWidget.url())), * * // Any subsequent request will be sent to the newly set URL * Send.a(GetRequest.to('/projects')), * Ensure.that(LastResponse.status(), equals(200)), * ) * ``` * * ## Changing API port for all subsequent requests * * ```ts * import { actorCalled } from '@serenity-js/core' * import { LocalServer, ManageALocalServer, StartLocalServer } from '@serenity-js/local-server' * import { CallAnApi, ChangeApiConfig, GetRequest, LastResponse, Send } from '@serenity-js/rest' * import { Ensure, equals } from '@serenity-js/assertions' * * await actorCalled('Apisitt') * .whoCan( * ManageALocalServer.runningAHttpListener(someServer), * CallAnApi.at('http://localhost'), * ) * .attemptsTo( * StartALocalServer.onRandomPort(), * ChangeApiConfig.setPortTo(LocalServer.port()), * Send.a(GetRequest.to('/api')), * Ensure.that(LastResponse.status(), equals(200)), * ) * ``` * * ## Setting a header for all subsequent requests * * ```ts * import { actorCalled, Question } from '@serenity-js/core' * import { CallAnApi, ChangeApiConfig, GetRequest, LastResponse, Send } from '@serenity-js/rest' * import { Ensure, equals } from '@serenity-js/assertions' * * // A sample Question reading a Node process environment variable * const EnvVar = (var_name: string) => * Question.about(`${ name } environment variable`, actor => process.env[var_name]); * * await actorCalled('Apisitt') * .whoCan( * CallAnApi.at('http://localhost'), * ) * .attemptsTo( * ChangeApiConfig.setHeader('Authorization', EnvVar('TOKEN')), * Send.a(GetRequest.to('/api')), * Ensure.that(LastResponse.status(), equals(200)), * ) * ``` * * ## Handling sensitive information * * By design, any data handled by an actor appears in Serenity/JS reports. * To prevent the exposure of any sensitive information, such as passwords or tokens, you should use [`Masked`](https://serenity-js.org/api/core/class/Masked/). * * ```ts * import { actorCalled, Masked } from '@serenity-js/core' * import { CallAnApi, ChangeApiConfig, GetRequest, LastResponse, Send } from '@serenity-js/rest' * import { Ensure, equals } from '@serenity-js/assertions' * * await actorCalled('Apisitt') * .whoCan( * CallAnApi.at('http://localhost'), * ) * .attemptsTo( * ChangeApiConfig.setHeader('Authorization', Masked.valueOf('secret token')), * Send.a(GetRequest.to('/api')), * Ensure.that(LastResponse.status(), equals(200)), * ) * ``` * * @group Activities */ class ChangeApiConfig { /** * Instructs the [actor](https://serenity-js.org/api/core/class/Actor/) to change the base URL * of their [ability](https://serenity-js.org/api/core/class/Ability/) to [`CallAnApi`](https://serenity-js.org/api/rest/class/CallAnApi/) * * @param newApiUrl */ static setUrlTo(newApiUrl) { return new ChangeApiConfigSetUrl(newApiUrl); } /** * Instructs the [actor](https://serenity-js.org/api/core/class/Actor/) to change the port configured in the base URL * of their [ability](https://serenity-js.org/api/core/class/Ability/) to [`CallAnApi`](https://serenity-js.org/api/rest/class/CallAnApi/) * * @param newApiPort */ static setPortTo(newApiPort) { return new ChangeApiConfigSetPort(newApiPort); } /** * Instructs the [actor](https://serenity-js.org/api/core/class/Actor/) to change the configuration of the [`AxiosInstance`](https://axios-http.com/docs/instance) * used by their [ability](https://serenity-js.org/api/core/class/Ability/) to [`CallAnApi`](https://serenity-js.org/api/rest/class/CallAnApi/) * and set an HTTP request header for any subsequent [HTTP requests](https://serenity-js.org/api/rest/class/HTTPRequest/) * issued via [`Send`](https://serenity-js.org/api/rest/class/Send/). * * @param name * @param value */ static setHeader(name, value) { return new ChangeApiConfigSetHeader(name, value); } } exports.ChangeApiConfig = ChangeApiConfig; /** * @package */ class ChangeApiConfigSetUrl extends core_1.Interaction { newApiUrl; constructor(newApiUrl) { super((0, core_1.d) `#actor changes API url configuration to ${newApiUrl}`); this.newApiUrl = newApiUrl; } performAs(actor) { return actor.answer(this.newApiUrl) .then(newApiUrl => abilities_1.CallAnApi.as(actor).modifyConfig(config => config.baseURL = newApiUrl)); } } /** * @package */ class ChangeApiConfigSetPort extends core_1.Interaction { newPort; constructor(newPort) { super(`#actor changes API port configuration to ${newPort}`); this.newPort = newPort; } performAs(actor) { return actor.answer(this.newPort) .then(newPort => abilities_1.CallAnApi.as(actor).modifyConfig(config => { if (!config.baseURL) { throw new core_1.LogicError(`Can't change the port of a baseURL that has not been set`); } try { const newUrl = new node_url_1.URL(config.baseURL); newUrl.port = `${newPort}`; config.baseURL = newUrl.toString(); } catch (error) { throw new core_1.LogicError(`Could not change the API port`, error); } })); } } /** * @package * * @see https://github.com/axios/axios#custom-instance-defaults */ class ChangeApiConfigSetHeader extends core_1.Interaction { name; value; constructor(name, value) { super(`#actor changes API URL and sets header "${name}" to "${value}"`); this.name = name; this.value = value; } performAs(actor) { return Promise.all([ actor.answer(this.name), actor.answer(this.value), ]). then(([name, value]) => { if (!name) { throw new core_1.LogicError(`Looks like the name of the header is missing, "${name}" given`); } // A header with an empty value might still be valid so we don't validate the value // see: https://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.1 return abilities_1.CallAnApi.as(actor).modifyConfig(config => { config.headers.common[name] = value; }); }); } } //# sourceMappingURL=ChangeApiConfig.js.map