I've been working with Axios to get content from Dynamic Yield. We were working with axios 0.28. Everything was working in frontend. We also had tests in typescript (jest + nock) that worked. But we've upgraded axios to latest (1.5) version. And now tests don't work any more.
I think that the problem is with the OPTIONS preflight request.
I've tried to cope with that, by intercepting OPTIONS request (before real POST request). But it doesn't work, whatever I try.
The tests look like this:
import { describe, expect, it } from '@jest/globals';
import nock from 'nock';
import type { Config } from './dependency-injection/config';
import type { ChooseApiRequest } from './api/contract/choose/ChooseApiRequest';
import type { Choice, ChooseApiResponse } from './api/contract/choose/ChooseApiResponse';
import { dy } from './services';
const API_KEY = '6394b44ef96243bf8e6e2aad7f59d389';
const CHOOSE_API_URL = 'https://direct.dy-api.eu/v2/serve/user/choose';
window.dynamicYieldConfig = {
apiKey: API_KEY,
chooseEndpointUrl: CHOOSE_API_URL,
} satisfies Config;
describe('Dynamic Yield: services.dy - the DynamicYield facade', () => {
const CURRENT_URL = 'https://shop.de/';
const server = nock('https://direct.dy-api.eu:443/')
.matchHeader('Content-Type', 'application/json')
.matchHeader('DY-API-KEY', API_KEY);
it('should provide an abstraction over the `choose` endpoint (independent from cookies)', async () => {
// nock.disableNetConnect();
const CAMPAIGN_NAME = 'campaign';
// given
window.pageType = 'Home';
window.location.assign(CURRENT_URL);
const expectedRequest: ChooseApiRequest = {
context: {
page: {
type: 'HOMEPAGE',
data: [],
location: CURRENT_URL,
},
},
selector: { names: [CAMPAIGN_NAME] },
options: { isImplicitImpressionMode: true },
};
const choiceReturnedByServer: Choice = {
id: 9999,
name: CAMPAIGN_NAME,
type: 'DECISION',
decisionId: 'decision-id',
variations: [
{
id: 8888,
payload: {
type: 'CUSTOM_JSON',
data: { 'custom-key': 'custom-value' },
},
},
],
};
server
.persist()
.intercept('/v2/serve/user/choose', 'OPTIONS').reply(204, '', {
"Access-Control-Allow-Origin": "*",
"Content-Type": "application:json"
})
// @ts-ignore
.post('/v2/serve/user/choose', expectedRequest)
.reply(200, {
choices: [choiceReturnedByServer],
cookies: [
{
name: '_dyjsession',
value: 'new-session-id-from-server',
maxAge: '3600',
},
],
warnings: [],
} satisfies ChooseApiResponse);
await expect(
(async () => {
// when
const result = await dy().choose({
selector: { names: [CAMPAIGN_NAME] },
options: { isImplicitImpressionMode: true },
});
// then
expect(result.get('DECISION', CAMPAIGN_NAME)).toEqual(choiceReturnedByServer);
})(),
).resolves.not.toThrowError();
// without consent, DY shall not have session persistence with cookies
expect(document.cookie).not.toContain('_dyjsession');
});
});
Of course the expected result is for nock to mock the response and return the values defined in test.
When I'm using nock.recorder.rec() I'm getting following logs:
console.log
<<<<<<-- cut here -->>>>>>
{
"scope": "https://direct.dy-api.eu:443",
"method": "OPTIONS",
"path": "/v2/serve/user/choose",
"body": "",
"status": 204,
"response": "",
"rawHeaders": [
"Server",
"DynamicYieldAPI/2.0",
"Date",
"Tue, 05 Sep 2023 10:17:36 GMT",
"Content-Length",
"0",
"Connection",
"keep-alive",
"Expires",
"Tue, 05 Sep 2023 11:17:36 GMT",
"Cache-Control",
"max-age=3600",
"Access-Control-Allow-Origin",
"http://localhost",
"Access-Control-Allow-Credentials",
"true",
"Access-Control-Allow-Methods",
"GET, POST, PUT, PATCH, DELETE",
"Access-Control-Allow-Headers",
"DY-API-Key, Content-Type, Cache-Control, Authorization, Postman-Token, DY-Explain",
"Access-Control-Max-Age",
"1728000",
"Allow",
"POST,OPTIONS",
"Vary",
"Origin",
"DY-Trace-ID",
"25a4ebc36407952cdce0b83ad0e8d910",
"P3P",
"policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID CUR ADM DEV OUR BUS\"",
"Content-Type",
"application/json; charset=utf-8"
],
"reqheaders": {
"referer": "http://localhost/",
"origin": "http://localhost",
"access-control-request-method": "POST",
"access-control-request-headers": "DY-API-KEY",
"host": "direct.dy-api.eu"
},
"responseIsBinary": false
}
<<<<<<-- cut here -->>>>>>
at IncomingMessage.<anonymous> (node_modules/nock/lib/recorder.js:280:13)
console.log
<<<<<<-- cut here -->>>>>>
{
"scope": "https://direct.dy-api.eu:443",
"method": "POST",
"path": "/v2/serve/user/choose",
"body": {
"context": {
"page": {
"type": "HOMEPAGE",
"data": [],
"location": "https://shop.de/"
}
},
"selector": {
"names": [
"campaign"
]
},
"options": {
"isImplicitImpressionMode": true
}
},
"status": 401,
"response": {
"error": 401
},
"rawHeaders": [
"Server",
"DynamicYieldAPI/2.0",
"Date",
"Tue, 05 Sep 2023 10:17:36 GMT",
"Transfer-Encoding",
"chunked",
"Connection",
"keep-alive",
"Access-Control-Allow-Origin",
"http://localhost",
"Access-Control-Allow-Credentials",
"true",
"Access-Control-Allow-Methods",
"GET, POST, PUT, PATCH, DELETE",
"Access-Control-Allow-Headers",
"DY-API-Key, Content-Type, Cache-Control, Authorization, Postman-Token, DY-Explain",
"Access-Control-Max-Age",
"1728000",
"Allow",
"POST,OPTIONS",
"DY-Trace-ID",
"2e6eea7fe13ace0dd81abeaa565b4bd5",
"P3P",
"policyref=\"/w3c/p3p.xml\", CP=\"NOI DSP COR NID CUR ADM DEV OUR BUS\"",
"Content-Type",
"application/json; charset=utf-8"
],
"reqheaders": {
"accept": "application/json, text/plain, */*",
"content-type": "application/json",
"dy-api-key": "6394b44ef96243bf8e6e2aad7f59d389",
"referer": "http://localhost/",
"accept-language": "en",
"origin": "http://localhost",
"content-length": 158,
"accept-encoding": "gzip, deflate",
"host": "direct.dy-api.eu"
},
"responseIsBinary": false
}
<<<<<<-- cut here -->>>>>>
at IncomingMessage.<anonymous> (node_modules/nock/lib/recorder.js:280:13)
Error: expect(received).resolves.not.toThrowError()
Received promise rejected instead of resolved
Rejected to value: [AxiosError: Request failed with status code 401]
I'd say that the OPTIONS preflight request isn't intercepted ... but if I knew what's the problem I wouldn't be here right? :p