How can I pass header parameter for ando client application in apollo server? - node.js

I am having trouble while trying to use custom header parameter in apollo server. I have an apollo server as below:
import { ApolloServer } from 'apollo-server-lambda'
import { ApolloGateway, IntrospectAndCompose, GraphQLDataSourceProcessOptions, RemoteGraphQLDataSource } from '#apollo/gateway'
import { ApolloServerPluginLandingPageGraphQLPlayground } from 'apollo-server-core'
import { GraphQLRequest } from 'apollo-server-types'
import { SignatureV4 } from '#aws-sdk/signature-v4'
import { Sha256 } from '#aws-crypto/sha256-js'
import { OutgoingHttpHeader } from 'http'
import { defaultProvider } from '#aws-sdk/credential-provider-node'
import { HttpRequest } from '#aws-sdk/protocol-http'
class AuthenticatedDataSource extends RemoteGraphQLDataSource {
/**
* Adds the necessary IAM Authorization headers for AppSync requests
* #param request The request to Authorize
* #returns The headers to pass through to the request
*/
private async getAWSCustomHeaders(request: GraphQLRequest): Promise<{
[key: string]: OutgoingHttpHeader | undefined
}> {
const { http, ...requestWithoutHttp } = request
if (!http) return {}
const url = new URL(http.url)
//check local env
if(url.host.match(/localhost:20002/)) return {'x-api-key':'da2-fakeApiId123456'}
// If the graph service is not AppSync, we should not sign these request.
if (!url.host.match(/appsync-api/)) return {}
const httpRequest = new HttpRequest({
hostname: url.hostname,
path: url.pathname,
method: http.method,
headers: {
Host: url.host,
'Content-Type': 'application/json'
},
body: JSON.stringify(requestWithoutHttp),
})
const signedRequest = await new SignatureV4({
region: 'eu-west-1',
credentials: defaultProvider(),
service: 'appsync',
sha256: Sha256,
}).sign(httpRequest)
return signedRequest.headers || {}
}
/**
* Customize the request to AppSync
* #param options The options to send with the request
*/
public async willSendRequest({ request, context }: GraphQLDataSourceProcessOptions) {
const customHeaders = await this.getAWSCustomHeaders(request)
if (customHeaders) {
Object.keys(customHeaders).forEach((h) => {
request.http?.headers.set(h, customHeaders[h] as string)
})
}
// context not available when introspecting
if (context.event)
Object.keys(context.event.requestContext.authorizer.lambda).forEach((h) => {
request.http?.headers.set(h, context.event.requestContext.authorizer.lambda[h] as string)
})
}
}
const server = new ApolloServer({
gateway: new ApolloGateway({
buildService({ url }) {
return new AuthenticatedDataSource({ url })
},
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'CONFIGURATIONSERVICE', url: process.env.CONFIGURATION_SERVICE_API_URL }
]
})
}),
debug: true,
context: ({ event, context, express}) => ({
headers: event.headers,
functionName: context.functionName,
event,
context,
expressRequest: express.req,
}),
introspection: true,
plugins: [ApolloServerPluginLandingPageGraphQLPlayground()],
})
exports.handler = server.createHandler({
expressGetMiddlewareOptions: {
cors: {
origin: '*',
}
}
})
When I try to execute configurationservice via playground on port 3000, I realized that I do not x-api-key header parameter and therefore I get 401 authorization. I do not understand the reason of missing header parameter that I already added in the codebase and any help would be appreciated. Thank you!

Ok I noticed that I can also add header parameters via playground application. Now it returns no authorization error.

Related

Mocking HTTP Endpoint - How to create multiple private keys with a shared public key?

Background
I'm attempting to resolve some existing technical debt in a code library I own. That tech debt is in the form of a unit test. Inside the library is an HTTP Client (originally just a wrapper class around a 3rd-party library). The unit tests for this HTTP Client were written to hit a protected endpoint within our private network. The problem is I'm now migrating said library to a new cloud project that doesn't have access to the same subnet, and will break.
Question
Edit: To clarify, since apparently my question is worded incorrectly, how do I create two key pairs (one server, one client) that will correctly perform an mTLS handshake? Preferably, how can I use my existing client certificates, and generate server-side certificates that will correctly decrypt the incoming client request?
How do I use the Node.js Crypto module to generate a set of TLS certificates for use by a mocking server and the requesting client-side code? I have a signed CA certificate that I'd like to use for generating new private keys (preferably in PKCS#12 format, but also need a test in PKCS#8 format). Short of that, how would I use generateKeyPair to then also make a second private key for the client to use? Or can I have the same key pair server-side and client-side?
Code Examples
Interfaces
IAgentOptions
// ./interfaces/http/IAgentOptions.ts
/**
* #typedef IAgentOptions
* #property {string|null} cert Public Client Cert file contents
* #property {string|null} key Private RSA Key file contents
* #property {Buffer|null} pfx Compiled keystore file
* #property {string} ca Public Certificate Chain that authorizes the client
* #property {string} passphrase Passphrase used to initialize private keystore
* #property {boolean} rejectUnauthorized Should SSL be strictly enforced
*/
export interface IAgentOptions {
/**
* Optionally override the trusted CA certificates. Default is to trust
* the well-known CAs curated by Mozilla. Mozilla's CAs are completely
* replaced when CAs are explicitly specified using this option.
*/
ca?: string;
/**
* Cert chains in PEM format. One cert chain should be provided per
* private key. Each cert chain should consist of the PEM formatted
* certificate for a provided private key, followed by the PEM
* formatted intermediate certificates (if any), in order, and not
* including the root CA (the root CA must be pre-known to the peer,
* see ca). When providing multiple cert chains, they do not have to
* be in the same order as their private keys in key. If the
* intermediate certificates are not provided, the peer will not be
* able to validate the certificate, and the handshake will fail.
*/
cert?: string;
/**
* Private keys in PEM format. PEM allows the option of private keys
* being encrypted. Encrypted keys will be decrypted with
* options.passphrase. Multiple keys using different algorithms can be
* provided either as an array of unencrypted key strings or buffers,
* or an array of objects in the form {pem: <string|buffer>[,
* passphrase: <string>]}. The object form can only occur in an array.
* object.passphrase is optional. Encrypted keys will be decrypted with
* object.passphrase if provided, or options.passphrase if it is not.
*/
key?: string;
/**
* Shared passphrase used for a single private key and/or a PFX.
*/
passphrase?: string;
/**
* PFX or PKCS12 encoded private key and certificate chain. pfx is an
* alternative to providing key and cert individually. PFX is usually
* encrypted, if it is, passphrase will be used to decrypt it. Multiple
* PFX can be provided either as an array of unencrypted PFX buffers,
* or an array of objects in the form {buf: <string|buffer>[,
* passphrase: <string>]}. The object form can only occur in an array.
* object.passphrase is optional. Encrypted PFX will be decrypted with
* object.passphrase if provided, or options.passphrase if it is not.
*/
pfx?: Buffer;
/**
* Flag to determine if failure to certify the signer of a certificate should result in an exception to be thrown.
* #default true
*/
rejectUnauthorized?: boolean;
}
IRequest
// ./interfaces/http/IRequest.ts
import { ISingleSignOn } from '../ISingleSignOn';
import { IAgentOptions } from './IAgentOptions';
export interface IRequest {
/**
* Config params for Http(s) agent initialization (overridden with clientAuth)
*/
agentOptions?: IAgentOptions;
/**
* Indicator if NTLM responses should have response body deserialized from JSON into Object
*/
body?: string | Record<string, unknown>;
/**
* Flag to disable passing client-side TLS authentication requests (true by default)
*/
clientAuth?: boolean;
/**
* Dictionary of various headers passed in the request
*/
headers?: Record<string, string>;
/**
* Indicator if NTLM responses should have response body deserialized from JSON into Object
*/
json?: boolean;
/**
* HTTP verb to execute request with
*/
method?: string;
/**
* Only used in NTLM requests
*/
// eslint-disable-next-line camelcase
ntlm_domain?: string;
/**
* Query-string parameters to be URL-encoded
* See documentation on {#link https://www.npmjs.com/package/request#requestoptions-callback|request}
*/
qs?: Record<string, unknown>;
/**
* Make sure the full response is available so we can check the statusCode
*/
resolveWithFullResponse?: boolean;
/**
* Throw an error when non-2xx codes received
*/
simple?: boolean;
/**
* Dictionary of values used to authenticate via single sign-on
*/
sso?: ISingleSignOn;
/**
* Raise an exception if SSL checks fail
*/
strictSSL?: boolean;
/**
* Resource locator for web requests
*/
url?: string | URL;
/**
* Flag to switch request library to NTLM support
*/
useNtlm?: boolean;
/**
* Only used in NTLM requests
*/
workstation?: string;
}
IResponse
// ./interfaces/http/IResponse.ts
import { IRequest } from './IRequest';
import { IResponseBody } from './IResponseBody';
import { IResponseTimingPhases } from './IResponseTimingPhases';
import { IHttpArchive } from './IHttpArchive';
export interface IResponse {
body?: IResponseBody | string;
har?: IHttpArchive;
headers?: Map<string, string>;
request?: IRequest;
status: number;
statusCode: number;
statusText: string;
statusMessage: string;
timingPhases?: IResponseTimingPhases;
toJSON(): object;
toString(): string;
}
HTTP Module
HttpClient (stub)
// ./src/http/http-client.d.ts
import { IRequest, IResponse } from '../../interfaces';
/**
* HTTP Client primary method of execution. Handles all standard web requests
*/
export default class HttpClient {
constructor(config: IRequest);
static handleJson(isJsonBody: boolean, response: IResponse): IResponse;
handleResponse(response: IResponse): Promise<IResponse>;
static ntlmRequest(config: IRequest): Promise<IResponse>;
static request(config: IRequest): Promise<IResponse>;
private useNtlm: boolean;
}
HttpClient (unit tests)
A little additional background here. These are the original unit tests from when I inherited this project some years ago. It's been a sticking point for me, because these are objectively not unit tests, but the team I answer to for changes to the code base are hesitant to change the existing contract of what's expected to pass in a unit test. Obviously, we have come to an impasse, where the unit tests change or we can't move forward, so I'm wanting to do as much as I can to alleviate the existing tech debt burden.
// ./src/http/http-client.test.js
const { beforeAll, describe, expect, it } = require('#jest/globals');
const { Credentials } = require('../../test');
const { authenticate } = require('../api');
const { request: http } = require('./http-client');
describe('http client', () => {
let credentials = Object.create(Credentials);
beforeAll(async () => {
credentials = await Credentials.create('example', 'role-example', 'secret');
});
it('should get a 503 from an undefined end point', async () => {
const response = await http({
method: 'GET',
url: 'https://missing.nonprod.domain.net'
});
expect(response.statusCode).toBe(503);
});
it('should get a 503 from an undefined end point and overriden properties', async () => {
const response = await http({
method: 'GET',
url: 'https://missing.nonprod.domain.net',
simple: false,
resolveWithFullResponse: true,
strictSSL: false,
qs: 'test'
});
expect(response.statusCode).toBe(503);
});
it('should receive the login page when unauthorized and hitting a protected URL', async () => {
const response = await http({
method: 'GET',
url: 'https://dev.apps.company.net/v1/users/me'
});
expect(response).toHaveProperty('statusCode', 200);
expect(response.body).toContain('<title>Log In to company®</title>');
expect(response.body).toContain('<label for="userId">User ID:</label>');
expect(response.body).toContain('<input type="text" class="userId" value="" name="userId" id="userId"/>');
expect(response.body).toContain('<label for="password">Password:</label>');
expect(response.body).toContain('<input id="password" type="password" class="input loginPassword" value="" name="password"/>');
});
it('should get a 200 when hitting a protected URL after logging in', async () => {
process.env.ENV = 'DEV';
const { sso } = credentials;
const { body, statusCode } = await http({
method: 'GET',
url: 'https://dev.apps.company.net/v1/users/me',
headers: {
accept: 'application/json'
},
json: true,
sso
});
expect(statusCode).toBe(200);
expect(body).toHaveProperty('userId', 'default-auto-user');
expect(body).toHaveProperty('currentRegion', 'ZZ');
});
it('should get a 200 when hitting a protected URL after logging in even with another cookie in the request', async () => {
process.env.ENV = 'DEV';
const { sso } = credentials;
const response = await http({
method: 'GET',
url: 'https://dev.apps.company.net/v1/users/me',
headers: {
accept: 'application/json',
Cookie: 'hello=world'
},
sso
});
expect(response.statusCode).toBe(200);
});
it('should get a 200 when hitting a protected URL after logging in even no header is provided', async () => {
process.env.ENV = 'DEV';
const { sso } = credentials;
const response = await http({
method: 'GET',
url: 'https://dev.apps.company.net/v1/users/me',
sso
});
expect(response.statusCode).toBe(200);
});
it('should save the response when a state is given', async () => {
const state = {};
const response = await http({
method: 'GET',
url: 'https://missing.nonprod.domain.net',
state
});
expect(state.response).toBe(response);
});
it('should save the response body when a state is given', async () => {
const state = {};
const response = await http({
method: 'GET',
url: 'https://missing.nonprod.domain.net',
state
});
expect(state.response.body).toBe(response.body);
});
it('should attach response when a state is given and attach is defined', async () => {
const state = {
str: '',
attach: v => {
state.str = `${state.str}\n${v}`;
}
};
const request = {
method: 'GET',
url: 'https://missing.nonprod.domain.net',
state
};
await http(request);
expect(state.str).toMatch(/REQUEST:\s+{/);
expect(state.str).toMatch(/RESPONSE:\s+{/);
});
it('should attach base64 encoded response when a state is given with encodeAttachments and attach is defined', async () => {
const state = {
str: '',
encodeAttachments: true,
attach: v => {
// state.str = Buffer.from(v, 'base64').toString('ascii');
state.str = `${state.str}\n${v}`;
}
};
// Truncating the ending because the shortened data stream results in different values
// In testing, the shortened value for comparison ended with ==, which seems to be a terminator of sorts
// By removing the trailing two characters, we get a near match without knowing the exact contents
const expectedRequest = Buffer.from('REQUEST: {').toString('base64').slice(0, -2);
const expectedResponse = Buffer.from('RESPONSE: {').toString('base64').slice(0, -2);
const request = {
method: 'GET',
url: 'https://missing.nonprod.domain.net',
state
};
await http(request);
expect(state.str).toMatch(new RegExp(expectedRequest));
expect(state.str).toMatch(new RegExp(expectedResponse));
});
it('should get a 401 when hitting a protected URL before authenticating', async () => {
process.env.ENV = 'DEV';
const response = await http({
method: 'GET',
url: 'https://dev.api.company.net/proxy/external/v1/hello-world'
});
expect(response).toMatchObject({ statusCode: 401 });
});
it('should get a 202 when hitting a protected URL after authenticating', async () => {
process.env.ENV = 'DEV';
const { clientId, clientSecret } = credentials.api;
const auth = await authenticate(clientId, clientSecret);
const url = new URL('https://stage.api.company.net/proxy/external/v1/hello-world');
const {
body: { id },
statusCode
} = await http({
method: 'GET',
url: url.toString(),
auth
});
expect(statusCode).toBe(202);
url.searchParams.set('ticket', id);
const { headers } = await http({
method: 'GET',
url: url.toString(),
auth
});
expect(headers).toHaveProperty('X-Ratelimit-Remaining', expect.stringContaining('name=throttle,'));
});
it('should return an HTTP archive (HAR) output bound to the response', async () => {
process.env.ENV = 'DEV';
const { sso } = credentials;
const expected = {
log: {
version: '1.2',
creator: {
name: '#scope/request-capture-har',
version: '1.0.0'
},
pages: [
{
id: '#scope/request-capture-har',
title: '#scope/request-capture-har',
pageTimings: {}
}
],
entries: [
{
cache: {},
timings: {},
request: {
method: 'GET',
url: 'https://dev.apps.company.net/v1/users/me',
httpVersion: 'HTTP/1.1',
cookies: [{ name: 'XSRF-TOKEN' }],
headers: [{ name: 'X-XSRF-TOKEN' }]
},
response: {
status: 200,
statusText: 'OK',
httpVersion: 'HTTP/1.1'
}
}
]
}
};
const response = await http({
method: 'GET',
url: 'https://dev.apps.company.net/v1/users/me',
sso
});
expect(response).toHaveProperty('har');
expect(response.har).toMatchObject(expected);
});
});
Thanks
I appreciate you've read this far. I know this is a very long question, and it likely doesn't require this much detail. However, I know the expected response here will be "Don't test that HTTP works". I wanted to explain why I need to test it, and demonstrate my starting point. I'm not trying to see if Node.js will encrypt HTTPS traffic. I'm trying to make sure that the existing logic inside the HTTP Client passes TLS information to Node.js effectively. There's also some more complicated unit tests that need to be rewritten, involving partial stream chunking across multiple requests, and I'm hoping the initial work here can be leveraged in that effort.
Again, thanks for your time!

Axios mock adapter error when adding new routes for mock not working but existing is working

I have an issue when trying to add a new mock route. Cannot hit API mock with the new route but existing route is working.
Currently /route/existing is working but /route/new is not working
Is there any solution to this? Anyone also had experienced this before?
Project details:
Vue.js version 2.5
Node.js version 14
Axios mock adapter version 1.17.0
Axios version 0.19.2
Thanks before
My mock implementation (index.js)
import MockRoutes from './mock-routes'
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
let routes = MockRoutes
// axios mock method
const mock = new MockAdapter(axios)
const methodMap = {
GET: 'onGet',
PUT: 'onPut',
POST: 'onPost',
DELETE: 'onDelete'
}
function applyMock (data) {
data.forEach(d => {
const params = [d.url]
switch (d.method) {
case 'GET': params.push({ params: d.param_values })
break
case 'PUT' || 'POST': params.push(d.param_values)
break
}
mock[methodMap[d.method]](...params).reply(d.status || 200, d.response)
})
}
applyMock(routes)
if (process.env.NODE_ENV !== 'production') {
window.concatMockRoutes = function (newRoutes) {
applyMock(newRoutes)
routes = routes.concat(newRoutes)
if (process.env.NODE_ENV !== 'production') {
return routes
}
}
}
export default routes
Mock routes (mock-routes.js)
var routes = [
{
method: 'GET',
url: '/route/existing',
response: {
"code": 200,
"status": "OK",
"data": {
mock: "existing"
}
}
},
{
method: 'GET',
url: '/route/new',
response: {
"code": 200,
"status": "OK",
"data": {
mock: "new"
}
}
}]

500: Internal Server Error with Next Js in some routes

Live Demo
After deploying a project written in Next JS to Vercel, some page paths, such as (xxx.vercel.app/first or / second) pages, display "500: Internal Server Error". All Page Routes do not receive any errors except these two Routes.
There is no solution yet, so I came to StackOverFlow to ask.
In my local development, everything ran smoothly. However, this is an error I get when I run on Vercel, so I would like to get some suggestions on how to fix it.
The following is the code for each page path I wrote for that two routes:
#config/ To run my Server
const dev = process.env.NODE_ENV !== 'production'
export const server = dev
? 'http://localhost:3001'
: 'https://kochenlay.vercel.app'
This is for the first page. route path will be like this (xxx.vercel.app/history)
import LiveHistories from '#components/LiveHistories'
import MainLayout from '#components/MainLayout'
import { server } from '#config/index'
import { GoBackHome } from '#utils/index'
import axios from 'axios'
// * Client Side
export default function History({ data }) {
const myData = JSON.parse(data)
return (
<MainLayout title='Live Histories'>
{myData?.map((result, index) => (
<LiveHistories key={result.id} data={result} index={index} />
))}
{/* Go back */}
<GoBackHome />
</MainLayout>
)
}
// * Server Side
export async function getServerSideProps() {
const { data } = await axios({
method: 'GET',
url: `${server}/api/twod`,
responseType: 'json',
headers: {
'Content-Type': 'application/json',
},
})
const result = data.data.slice(0, 7) || []
return {
props: {
data: JSON.stringify(result),
},
}
}
This is for the second page. route path will be like this (xxx.vercel.app/lottery)
import MainLayout from '#components/MainLayout'
import ThreeDLive from '#components/ThreeDLive'
import ThreeDLiveCard from '#components/ThreeDLive/ThreeDLiveCard'
import { server } from '#config/index'
import { GoBackHome } from '#utils/index'
import axios from 'axios'
export default function ThaiLottery({ calendar, live }) {
const calendarData = JSON.parse(calendar)
const liveResult = JSON.parse(live)
return (
<MainLayout title='3D Live'>
<ThreeDLiveCard data={liveResult} />
<ThreeDLive data={calendarData} />
<GoBackHome />
</MainLayout>
)
}
export async function getServerSideProps() {
const { data } = await axios({
method: 'GET',
url: `${server}/api/threed`,
responseType: 'json',
headers: {
'Content-Type': 'application/json',
},
})
const calendarThreeD = data.data || null
const threeDLive = data.data[0] || null
return {
props: {
calendar: JSON.stringify(calendarThreeD),
live: JSON.stringify(threeDLive),
},
}
}
Finally, I solved the issues where I replaced to work with getStaticProps instead of getServerSide rendering and alongside with using SWR npm packages.

Nodejs - Axios not using Cookie for post request

I'm struggling with AXIOS: it seems that my post request is not using my Cookie.
First of all, I'm creating an Axios Instance as following:
const api = axios.create({
baseURL: 'http://mylocalserver:myport/api/',
header: {
'Content-type' : 'application/json',
},
withCredentials: true,
responseType: 'json'
});
The API I'm trying to interact with is requiring a password, thus I'm defining a variable containing my password:
const password = 'mybeautifulpassword';
First, I need to post a request to create a session, and get the cookie:
const createSession = async() => {
const response = await api.post('session', { password: password});
return response.headers['set-cookie'];
}
Now, by using the returned cookie (stored in cookieAuth variable), I can interact with the API.
I know there is an endpoint allowing me to retrieve informations:
const readInfo = async(cookieAuth) => {
return await api.get('endpoint/a', {
headers: {
Cookie: cookieAuth,
}
})
}
This is working properly.
It's another story when I want to launch a post request.
const createInfo = async(cookieAuth, infoName) => {
try {
const data = JSON.stringify({
name: infoName
})
return await api.post('endpoint/a', {
headers: {
Cookie: cookieAuth,
},
data: data,
})
} catch (error) {
console.log(error);
}
};
When I launch the createInfo method, I got a 401 status (Unauthorized). It looks like Axios is not using my cookieAuth for the post request...
If I'm using Postman to make the same request, it works...
What am I doing wrong in this code? Thanks a lot for your help
I finally found my mistake.
As written in the Axios Doc ( https://axios-http.com/docs/instance )
The specified config will be merged with the instance config.
after creating the instance, I must follow the following structure to perform a post requests:
axios#post(url[, data[, config]])
My requests is working now :
await api.post('endpoint/a', {data: data}, {
headers: {
'Cookie': cookiesAuth
}
});

Nock does not intercept requests - Error: getaddrinfo ENOTFOUND

Let me first explain how everything is set up.
The tests are run using Jest.
I have a jest.config.json with the following line:
"globalSetup": "<rootDir>/__tests__/setup.js",
In setup.js I have the following content:
As you can see, I put the fake URL in the environment variable AMA_API.
After that, I require ./nock.ts and log the line Nock init done. The rest of the content of this file seems irrelevant to me.
module.exports = async function() {
console.log('[JEST SETUP] setting env var')
process.env.AMA_API = 'http://fake.local'
require('tsconfig-paths').register()
require('ts-node').register({
lazy: true,
fast: true,
project: 'tsconfig.json'
})
require('./nock.ts')
console.log('Nock init done.')
const connections = await require('src/db/connection.ts').initConnection()
await require('./clearDb.ts').clearDB()
await require('./initDb.ts').initializeDB()
await Promise.all(connections.map(con => con.close()))
console.log(`Connection setup complete [${connections.length} Connection${connections.length > 1 ? 's' : ''}]`)
return true
};
In nock.ts, I have the following content:
import * as nock from 'nock';
const FAKE_ANALYST_USER_CREATED = {
... (some large object)
}
console.log('NOCK URL ' + process.env.AMA_API);
nock(process.env.AMA_API)
.persist()
.log(console.log)
.post('api/analyst/create-analyst-user')
.reply(200, FAKE_ANALYST_USER_CREATED)
That is all the setup for nock I have. Then in the teams.controller.spec.ts, I have the following test:
describe('Team Endpoint', () => {
let connections: Connection[];
beforeAll(async () => {
connections = await initConnection();
});
afterAll(async () => {
await Promise.all(connections.map(con => con.close()));
console.log('connection closed');
return true;
});
describe('Team', () => {
test.only('POST / should return [200 - OK] and create a new team', async () => {
const newTeam = {
...
};
let response = await request(app)
.post('/')
.set('Authorization', adminUserToken())
.send(newTeam);
expect(response.status).toBe(201);
expect(response.body).toHaveProperty('name', newTeam.name);
expect(response.body).toHaveProperty('slug', newTeam.slug);
expect(response.body).toHaveProperty('apiKey');
expect(response.body.apiKey).toBeDefined();
response = await request(app)
.delete(`/${response.body.id}`)
.set('Authorization', adminUserToken())
expect(response.status).toBe(200);
});
});
});
Then finally, this test triggers a function in teams.controller.ts with the following content:
import { Request, Response } from 'express';
import * as jwt from 'jsonwebtoken';
import * as rp from 'request-promise';
import { config } from 'src/config';
import { Brackets } from 'typeorm/query-builder/Brackets';
import { isUUID } from 'validator';
import { withConnection, withTransaction } from '../../db/connection';
import { Team } from '../../models/team';
/**
* Create a new user
*/
export async function create(req: Request, res: Response) {
const result = await withTransaction(async em => {
const teamRepository = em.getRepository(Team)
... (irrelevant code)
console.log('AMA URL ' + process.env.AMA_API)
const response = await rp({
uri: `${process.env.AMA_API}/api/analyst/create-analyst-user`,
method: 'POST',
json: true,
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'authorization': 'Bearer ' + jwt.sign({
id: -1,
role: 'admin',
team: '*',
},
config.secrets.session,
{
expiresIn: 60 * 60
})
},
body: {username: newTeam.name, id: newTeam.id}
});
return response
})
if (result) {
return res.status(201).send(result)
}
}
So with that all out of the way..
All the setup code is reached when running the tests (based on the console output that I see).
However, when I run the tests, the request in the last code block is not intercepted, I get the following output:
Determining test suites to run...[JEST SETUP] setting env var
NOCK URL http://fake.local
Nock init done.
Starting server for profile test...
[Team] Adding initial teams
Connection setup complete [1 Connection]
RUNS src/api/teams/teams.controller.spec.ts
RUNS __tests__/server.spec.ts
Test Suites: 0 of 2 total
Tests: 0 total
Snapshots: 0 total
PASS __tests__/server.spec.ts
● Console console.log __tests__/jest.js:1 [JEST SETUP] setting timeout to 10s
FAIL src/api/teams/teams.controller.spec.ts (6.488s) ● Console
console.log __tests__/jest.js:1
[JEST SETUP] setting timeout to 10s console.log src/config/index.ts:8
Starting server for profile test... console.log src/api/teams/teams.controller.ts:90
AMA URL http://fake.local console.log src/api/teams/teams.controller.spec.ts:16
connection closed
● Team Endpoint › Team › POST / should return [200 - OK] and create a new
team
RequestError: Error: getaddrinfo ENOTFOUND fake.local fake.local:80
at new RequestError (node_modules/request-promise/node_modules/request
-promise-core/lib/errors.js:14:15)
at Request.plumbing.callback (node_modules/request-promise/node_module
s/request-promise-core/lib/plumbing.js:87:29)
at Request.RP$callback [as _callback] (node_modules/request-promise/no
de_modules/request-promise-core/lib/plumbing.js:46:31)
at self.callback (node_modules/request/request.js:185:22)
at Request.Object.<anonymous>.Request.onRequestError (node_modules/req
uest/request.js:881:8)
I have already spent hours trying to find what is going wrong here.. with no success. Any help would be very much appreciated.
In case anyone has the same problem. Moving the nock initialization to beforeall fixed it for me.
This is a really good time to use Nock Recorder. It will record you HTTP calls and let you see exactly why Nock is not matching to the request.
Try this in your teams.controller.ts:
import { Request, Response } from 'express';
import * as jwt from 'jsonwebtoken';
import * as rp from 'request-promise';
import { config } from 'src/config';
import { Brackets } from 'typeorm/query-builder/Brackets';
import { isUUID } from 'validator';
import { withConnection, withTransaction } from '../../db/connection';
import { Team } from '../../models/team';
const nock = require('nock') // add Nock
nock.recorder.rec({
output_objects: true,
}) // Setup Nock to Record
/**
* Create a new user
*/
export async function create(req: Request, res: Response) {
const result = await withTransaction(async em => {
const teamRepository = em.getRepository(Team)
... (irrelevant code)
console.log('AMA URL ' + process.env.AMA_API)
const response = await rp({
uri: `${process.env.AMA_API}/api/analyst/create-analyst-user`,
method: 'POST',
json: true,
headers: {
'accept': 'application/json',
'content-type': 'application/json',
'authorization': 'Bearer ' + jwt.sign({
id: -1,
role: 'admin',
team: '*',
},
config.secrets.session,
{
expiresIn: 60 * 60
})
},
body: {username: newTeam.name, id: newTeam.id}
});
nock.restore() // stop nock recording
const nockCalls = nock.recorder.play() // "play" the recording into a variable
console.log(`Nock Captured Calls: \n${JSON.stringify(nockCalls,null,2)}`) // inspect calls that Nock recorder
return response
})
if (result) {
return res.status(201).send(result)
}
}

Resources