@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
JavaScript
;
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