Skip to content
Permalink
Browse files
fix(rest): a full URL of an API request is now reported, instead of j…
…ust the path
  • Loading branch information
jan-molak committed Jan 23, 2021
1 parent c901e4c commit 1996c8a
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 20 deletions.
@@ -90,6 +90,18 @@ describe('CallAnApi', () => {
});
});

/**
* @test {CallAnApi}
* @test {CallAnApi.resolveUrl}
*/
it('provides a way to determine the actual target URL the request will be sent to', () => {
const callaAnApi = CallAnApi.at('https://example.org/api/v4');

const actualUrl = callaAnApi.resolveUrl({ url: 'products/3' });

expect(actualUrl).to.equal('https://example.org/api/v4/products/3')
});

describe('when dealing with errors', () => {
/**
* @test {CallAnApi}
@@ -74,7 +74,7 @@ describe('Send', () => {
it('emits the events so that the details of the HTTP interaction can be reported', () => {
const frozenClock = new Clock(() => new Date('1970-01-01'));
const axiosInstance = axios.create({
url: 'https://myapp.com/api',
baseURL: 'https://myapp.com/api'
});
const mock = new MockAdapter(axiosInstance);
const serenity = new Serenity(frozenClock);
@@ -85,14 +85,14 @@ describe('Send', () => {
crew: [ recorder ],
});

mock.onGet('/products/2').reply(200, {
mock.onGet('products/2').reply(200, { // axios-mock-adapter doesn't resolve baseUrl; it should've really been mock.onGet('/api/products/2')
id: 2,
}, {
'Content-Type': 'application/json',
});

return serenity.theActorCalled('Apisitt').attemptsTo(
Send.a(GetRequest.to('/products/2')),
Send.a(GetRequest.to('products/2')),
).then(() => {
const events = recorder.events;

@@ -103,11 +103,11 @@ describe('Send', () => {

const artifactGenerated = events[ 1 ] as ActivityRelatedArtifactGenerated;

expect(artifactGenerated.name.value).to.equal(`request get /products/2`);
expect(artifactGenerated.name.value).to.equal(`GET https://myapp.com/api/products/2`);
expect(artifactGenerated.artifact.equals(HTTPRequestResponse.fromJSON({
request: {
method: 'get',
url: '/products/2',
url: 'https://myapp.com/api/products/2',
headers: { Accept: 'application/json, text/plain, */*' },
},
response: {
@@ -1,5 +1,7 @@
import { Ability, ConfigurationError, LogicError, TestCompromisedError, UsesAbilities } from '@serenity-js/core';
import axios, { AxiosError, AxiosInstance, AxiosPromise, AxiosRequestConfig, AxiosResponse } from 'axios';
const mergeConfig = require('axios/lib/core/mergeConfig'); // tslint:disable-line:no-var-requires no-submodule-imports
const buildFullPath = require('axios/lib/core/buildFullPath'); // tslint:disable-line:no-var-requires no-submodule-imports

/**
* @desc
@@ -147,6 +149,22 @@ export class CallAnApi implements Ability {
return this.captureResponseOf(this.axiosInstance.request(config));
}

/**
* @desc
* Resolves the final URL, based on the {@link AxiosRequestConfig} provided
* any any defaults {@link AxiosInstance} has been configured with.
*
* @param {AxiosRequestConfig} config}
*
* @see {@link AxiosRequestConfig}
* @see {@link AxiosInstance}
*/
resolveUrl(config: AxiosRequestConfig): string {
const merged = mergeConfig(this.axiosInstance.defaults, config);

return buildFullPath(merged.baseURL, merged.url);
}

/**
* @desc
* Maps the last cached response to another type.
@@ -61,12 +61,18 @@ export class Send extends Interaction {
* @see {@link @serenity-js/core/lib/screenplay/actor~AnswersQuestions}
*/
performAs(actor: UsesAbilities & CollectsArtifacts & AnswersQuestions): Promise<void> {
const callAnApi = CallAnApi.as(actor);

return actor.answer(this.request)
.then(config => CallAnApi.as(actor).request(config))
.then((response: AxiosResponse) => actor.collect(
this.responseToArtifact(response),
this.requestToArtifactName(response.config),
));
.then(config =>
callAnApi.request(config).then((response: AxiosResponse) => {
const resolvedUrl = callAnApi.resolveUrl(config);

actor.collect(
this.responseToArtifact(resolvedUrl, response),
this.requestToArtifactName(response.config.method, resolvedUrl),
);
}));
}

/**
@@ -79,27 +85,27 @@ export class Send extends Interaction {
return `#actor sends ${ this.request.toString() }`;
}

private responseToArtifact(response: AxiosResponse): Artifact {
private responseToArtifact(targetUrl: string, response: AxiosResponse): Artifact {
const
request: AxiosRequestConfig = response.config,
requestAndResponse: RequestAndResponse = {
request: {
method: request.method,
url: request.url,
headers: request.headers,
data: request.data,
method: request.method,
url: targetUrl,
headers: request.headers,
data: request.data,
},
response: {
status: response.status,
headers: response.headers,
data: response.data,
status: response.status,
headers: response.headers,
data: response.data,
},
};

return HTTPRequestResponse.fromJSON(requestAndResponse);
}

private requestToArtifactName(request: AxiosRequestConfig) {
return new Name(`request ${request.method} ${request.url}`);
private requestToArtifactName(method: string, url: string) {
return new Name(`${ method.toUpperCase() } ${ url }`);
}
}

0 comments on commit 1996c8a

Please sign in to comment.