mirror of
https://github.com/Kong/insomnia.git
synced 2026-10-26 16:49:11 -04:00
🧹 Remove axios, use generics for send-request in insomnia-testing (#3949)
This commit is contained in:
1 parent
422d5a809c
commit
39eb725a55
11 files changed
+95
-269
No files matched your search
@@ -53,7 +53,7 @@ async function sendAndTransform(requestId: string, environmentId?: string) {
|
||||
try {
|
||||
plugins.ignorePlugin('insomnia-plugin-kong-bundle');
|
||||
const res = await send(requestId, environmentId);
|
||||
const headersObj = {};
|
||||
const headersObj: Record<string, string> = {};
|
||||
|
||||
for (const h of res.headers || []) {
|
||||
const name = h.name || '';
|
||||
|
||||
@@ -255,7 +255,7 @@ class WrapperUnitTest extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
async _runTests(unitTests: UnitTest[]) {
|
||||
const { requests, activeWorkspace, activeEnvironment } = this.props.wrapperProps;
|
||||
const { activeWorkspace, activeEnvironment } = this.props.wrapperProps;
|
||||
|
||||
if (!activeWorkspace) {
|
||||
return;
|
||||
@@ -283,14 +283,14 @@ class WrapperUnitTest extends PureComponent<Props, State> {
|
||||
},
|
||||
]);
|
||||
const sendRequest = getSendRequestCallback(activeEnvironment?._id);
|
||||
let results;
|
||||
|
||||
try {
|
||||
results = await runTests(src, {
|
||||
requests,
|
||||
// @ts-expect-error -- TSCONVERSION
|
||||
sendRequest,
|
||||
const results = await runTests(src, { sendRequest });
|
||||
await models.unitTestResult.create({
|
||||
results,
|
||||
parentId: activeWorkspace._id,
|
||||
});
|
||||
this.setState({ testsRunning: null });
|
||||
} catch (err) {
|
||||
// Set the state after a timeout so the user still sees the loading state
|
||||
setTimeout(() => {
|
||||
@@ -301,14 +301,6 @@ class WrapperUnitTest extends PureComponent<Props, State> {
|
||||
}, 400);
|
||||
return;
|
||||
}
|
||||
|
||||
await models.unitTestResult.create({
|
||||
results,
|
||||
parentId: activeWorkspace._id,
|
||||
});
|
||||
this.setState({
|
||||
testsRunning: null,
|
||||
});
|
||||
}
|
||||
|
||||
buildSelectableRequests(): {
|
||||
|
||||
-13
@@ -451,14 +451,6 @@
|
||||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
@@ -1789,11 +1781,6 @@
|
||||
"readable-stream": "^2.3.6"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz",
|
||||
"integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA=="
|
||||
},
|
||||
"for-in": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"chai": "^4.3.4",
|
||||
"mkdirp": "^1.0.4",
|
||||
"mocha": "^8.3.2"
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import type { AxiosRequestConfig, AxiosResponse, AxiosStatic, Method } from 'axios';
|
||||
|
||||
const mockResponses: { [key: string]: AxiosResponse } = {};
|
||||
|
||||
const key = (method?: Method, url?: string) => (
|
||||
`${method?.toLowerCase()}:${url?.toLowerCase()}`
|
||||
);
|
||||
|
||||
type SetResponse = <T>(method?: Method, url?: string, response?: AxiosResponse<T>) => void;
|
||||
|
||||
const axios = jest.genMockFromModule<AxiosStatic & { __setResponse: SetResponse }>('axios');
|
||||
|
||||
const __setResponse: SetResponse = (method, url, response) => {
|
||||
if (!method) {
|
||||
return;
|
||||
}
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
if (!response) {
|
||||
return;
|
||||
}
|
||||
|
||||
mockResponses[key(method, url)] = response;
|
||||
};
|
||||
|
||||
axios.__setResponse = __setResponse;
|
||||
|
||||
const request = async <T, U = AxiosResponse<T>>({ method, url }: AxiosRequestConfig) => {
|
||||
const k = key(method, url);
|
||||
const resp = mockResponses[k] as unknown as U;
|
||||
if (!resp) {
|
||||
throw new Error(
|
||||
`Could not find mock axios response for ${k}. Options are [${Object.keys(mockResponses).join(
|
||||
', ',
|
||||
)}]`,
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve<U>(resp);
|
||||
};
|
||||
|
||||
axios.request = request;
|
||||
|
||||
export type AxiosMock = typeof axios;
|
||||
|
||||
export default axios;
|
||||
@@ -1,12 +1,7 @@
|
||||
import _axios from 'axios';
|
||||
|
||||
import { AxiosMock } from '../__mocks__/axios';
|
||||
import { generate } from '../generate';
|
||||
import { runTests } from '../run';
|
||||
|
||||
const axios = _axios as AxiosMock;
|
||||
|
||||
jest.mock('axios');
|
||||
import { mockedSendRequest, mockedSendRequestMultiple } from '../test-helpers/send-request-mock';
|
||||
|
||||
describe('integration', () => {
|
||||
it('generates and runs basic tests', async () => {
|
||||
@@ -29,7 +24,10 @@ describe('integration', () => {
|
||||
],
|
||||
},
|
||||
]);
|
||||
const { stats } = await runTests(testSrc);
|
||||
const sendRequest = mockedSendRequest();
|
||||
|
||||
const { stats, failures } = await runTests(testSrc, { sendRequest });
|
||||
expect(failures).toEqual([]);
|
||||
expect(stats.tests).toBe(2);
|
||||
expect(stats.failures).toBe(0);
|
||||
expect(stats.passes).toBe(2);
|
||||
@@ -55,44 +53,16 @@ describe('integration', () => {
|
||||
],
|
||||
},
|
||||
]);
|
||||
const { stats, failures } = await runTests(testSrc);
|
||||
expect(failures).toEqual([]);
|
||||
expect(stats.tests).toBe(2);
|
||||
expect(stats.failures).toBe(0);
|
||||
expect(stats.passes).toBe(2);
|
||||
const { stats: stats2 } = await runTests(testSrc);
|
||||
expect(failures).toEqual([]);
|
||||
expect(stats2.tests).toBe(2);
|
||||
expect(stats2.failures).toBe(0);
|
||||
expect(stats2.passes).toBe(2);
|
||||
});
|
||||
|
||||
it('generates and runs more than once', async () => {
|
||||
const testSrc = generate([
|
||||
{
|
||||
name: 'Example TestSuite',
|
||||
suites: [],
|
||||
tests: [
|
||||
{
|
||||
name: 'should return -1 when the value is not present',
|
||||
code:
|
||||
'expect([1, 2, 3].indexOf(4)).to.equal(-1);\nexpect(true).to.be.true;',
|
||||
defaultRequestId: null,
|
||||
},
|
||||
{
|
||||
name: 'is an empty test',
|
||||
code: '',
|
||||
defaultRequestId: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const { stats, failures } = await runTests(testSrc);
|
||||
const sendRequest = mockedSendRequest();
|
||||
|
||||
const { stats, failures } = await runTests(testSrc, { sendRequest });
|
||||
expect(failures).toEqual([]);
|
||||
expect(stats.tests).toBe(2);
|
||||
expect(stats.failures).toBe(0);
|
||||
expect(stats.passes).toBe(2);
|
||||
const { stats: stats2, failures: failures2 } = await runTests(testSrc);
|
||||
|
||||
const { stats: stats2, failures: failures2 } = await runTests(testSrc, { sendRequest });
|
||||
expect(failures2).toEqual([]);
|
||||
expect(stats2.tests).toBe(2);
|
||||
expect(stats2.failures).toBe(0);
|
||||
@@ -100,27 +70,17 @@ describe('integration', () => {
|
||||
});
|
||||
|
||||
it('sends an HTTP request', async () => {
|
||||
axios.__setResponse('GET', '200.insomnia.rest', {
|
||||
const response1 = {
|
||||
status: 200,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
data: {
|
||||
foo: 'bar',
|
||||
},
|
||||
statusText: '',
|
||||
config: {},
|
||||
});
|
||||
statusMessage: 'abc',
|
||||
};
|
||||
|
||||
axios.__setResponse('GET', '301.insomnia.rest', {
|
||||
const response2 = {
|
||||
status: 301,
|
||||
headers: {
|
||||
location: '/blog',
|
||||
},
|
||||
data: undefined,
|
||||
statusText: '',
|
||||
config: {},
|
||||
});
|
||||
statusMessage: 'def',
|
||||
};
|
||||
|
||||
const sendRequest = mockedSendRequestMultiple(response1, response2);
|
||||
|
||||
const testSrc = generate([
|
||||
{
|
||||
@@ -131,36 +91,33 @@ describe('integration', () => {
|
||||
name: 'Tests referencing request by ID',
|
||||
defaultRequestId: null,
|
||||
code: [
|
||||
'const resp = await insomnia.send(\'req_123\');',
|
||||
'expect(resp.status).to.equal(301);',
|
||||
'const resp = await insomnia.send(\'foo\');',
|
||||
'expect(resp.status).to.equal(200);',
|
||||
'expect(resp.statusMessage).to.equal(\'abc\');',
|
||||
].join('\n'),
|
||||
},
|
||||
{
|
||||
name: 'Tests referencing default request',
|
||||
defaultRequestId: 'req_123',
|
||||
defaultRequestId: 'bar',
|
||||
code: [
|
||||
'const resp = await insomnia.send();',
|
||||
'expect(resp.status).to.equal(301);',
|
||||
'expect(resp.statusMessage).to.equal(\'def\');',
|
||||
].join('\n'),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const { stats, failures, passes } = await runTests(testSrc, {
|
||||
requests: [
|
||||
{
|
||||
_id: 'req_123',
|
||||
url: '301.insomnia.rest',
|
||||
method: 'get',
|
||||
},
|
||||
],
|
||||
});
|
||||
const { stats, failures, passes } = await runTests(testSrc, { sendRequest });
|
||||
|
||||
expect(failures).toEqual([]);
|
||||
expect(passes.length).toBe(2);
|
||||
expect(stats.tests).toBe(2);
|
||||
expect(stats.failures).toBe(0);
|
||||
expect(stats.passes).toBe(2);
|
||||
|
||||
expect(sendRequest).toHaveBeenNthCalledWith(1, 'foo');
|
||||
expect(sendRequest).toHaveBeenNthCalledWith(2, 'bar');
|
||||
});
|
||||
});
|
||||
@@ -1,38 +1,7 @@
|
||||
import axios, { Method } from 'axios';
|
||||
// @ts-expect-error we're pulling straight out of the lib here in a way that is not intended, but axios does not directly expose this adapter
|
||||
import httpAdapter from 'axios/lib/adapters/http';
|
||||
export type SendRequestCallback<TResponse> = (requestId: string) => Promise<TResponse>;
|
||||
|
||||
export interface Request {
|
||||
_id: string;
|
||||
url?: string;
|
||||
method?: string;
|
||||
body?: {
|
||||
text?: string;
|
||||
};
|
||||
headers?: {
|
||||
name: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface Response {
|
||||
status: number;
|
||||
statusText: string;
|
||||
data: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
headers: {
|
||||
[key: string]: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type SendRequestCallback = (requestId: string) => Promise<Response>;
|
||||
|
||||
export interface InsomniaOptions {
|
||||
requests?: Request[];
|
||||
sendRequest?: SendRequestCallback;
|
||||
export interface InsomniaOptions<TResponse> {
|
||||
sendRequest: SendRequestCallback<TResponse>;
|
||||
bail?: boolean;
|
||||
keepFile?: boolean;
|
||||
testFilter?: string;
|
||||
@@ -43,81 +12,38 @@ export interface InsomniaOptions {
|
||||
* tests, and will provide a bunch of utility functions for sending
|
||||
* requests, etc.
|
||||
*/
|
||||
export default class Insomnia {
|
||||
requests: Request[];
|
||||
export class Insomnia<TResponse = {}> {
|
||||
activeRequestId: string | null;
|
||||
activeEnvironmentId: string | null = null;
|
||||
sendRequest: SendRequestCallback | null;
|
||||
sendRequest: SendRequestCallback<TResponse>;
|
||||
|
||||
constructor(options: InsomniaOptions = {}) {
|
||||
this.requests = options.requests || [];
|
||||
this.sendRequest = options.sendRequest || null; // Things that are set per test
|
||||
constructor(options: InsomniaOptions<TResponse>) {
|
||||
this.sendRequest = options.sendRequest; // Things that are set per test
|
||||
|
||||
this.activeRequestId = null;
|
||||
}
|
||||
|
||||
setActiveRequestId(id: string): void {
|
||||
setActiveRequestId(id: string) {
|
||||
this.activeRequestId = id;
|
||||
}
|
||||
|
||||
clearActiveRequest(): void {
|
||||
clearActiveRequest() {
|
||||
this.activeRequestId = null;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param reqId - request ID to send. Specifying nothing will send the active request
|
||||
* @returns {Promise<{headers: *, data: *, statusText: (string|string), status: *}>}
|
||||
*/
|
||||
async send(reqId: string | null = null): Promise<Response> {
|
||||
async send(reqId: string | null = null) {
|
||||
// Default to active request if nothing is specified
|
||||
reqId = reqId || this.activeRequestId;
|
||||
const { sendRequest } = this;
|
||||
const requestId = reqId || this.activeRequestId;
|
||||
|
||||
if (typeof sendRequest === 'function' && typeof reqId === 'string') {
|
||||
return sendRequest(reqId);
|
||||
if (!requestId) {
|
||||
throw new Error('No selected request');
|
||||
}
|
||||
|
||||
const request = this.requests.find(request => request._id === reqId);
|
||||
|
||||
if (!request) {
|
||||
throw new Error('Request not provided to test');
|
||||
}
|
||||
|
||||
const { headers, url = '', method, body } = request;
|
||||
|
||||
const axiosHeaders = headers?.reduce((accumulator, { disabled, name, value }) => {
|
||||
return {
|
||||
...accumulator,
|
||||
...(disabled ? {} : { [name]: value }),
|
||||
};
|
||||
});
|
||||
|
||||
const response = await axios.request({
|
||||
url,
|
||||
method: (method as Method) || 'GET',
|
||||
data: body?.text,
|
||||
headers: axiosHeaders,
|
||||
|
||||
// Don't follow redirects,
|
||||
maxRedirects: 0,
|
||||
|
||||
// Don't throw errors on status != 200
|
||||
validateStatus: () => true,
|
||||
|
||||
// Force NodeJS adapter or Electron will default to XMLHttpRequest
|
||||
adapter: httpAdapter,
|
||||
});
|
||||
|
||||
console.log('[tests] Received response', {
|
||||
response: response,
|
||||
});
|
||||
|
||||
return {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: response.data,
|
||||
headers: response.headers,
|
||||
};
|
||||
const result = await this.sendRequest(requestId);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { TestResults } from './entities';
|
||||
import { mocked } from 'ts-jest/utils';
|
||||
|
||||
import { SendRequestCallback } from './insomnia';
|
||||
import { runTests } from './run';
|
||||
|
||||
@@ -27,44 +28,37 @@ describe('Example', () => {
|
||||
`;
|
||||
|
||||
describe('run', () => {
|
||||
const getMockedSendRequest = () => mocked<SendRequestCallback<{status: number}>>(jest.fn().mockResolvedValue({ status: 200 }));
|
||||
|
||||
it('runs a mocha suite', async () => {
|
||||
const { stats } = await runTests(exampleTest);
|
||||
const { stats } = await runTests(exampleTest, { sendRequest: getMockedSendRequest() });
|
||||
expect(stats.passes).toBe(1);
|
||||
expect(stats.tests).toBe(2);
|
||||
expect(stats.failures).toBe(1);
|
||||
});
|
||||
|
||||
it('runs empty mocha suite', async () => {
|
||||
const { stats } = await runTests(exampleEmptySuite);
|
||||
const { stats } = await runTests(exampleEmptySuite, { sendRequest: getMockedSendRequest() });
|
||||
expect(stats.passes).toBe(0);
|
||||
expect(stats.tests).toBe(0);
|
||||
expect(stats.failures).toBe(0);
|
||||
});
|
||||
|
||||
it('works on multiple files', async () => {
|
||||
const { stats } = await runTests([exampleTest, exampleTest]);
|
||||
const { stats } = await runTests([exampleTest, exampleTest], { sendRequest: getMockedSendRequest() });
|
||||
expect(stats.passes).toBe(2);
|
||||
expect(stats.tests).toBe(4);
|
||||
expect(stats.failures).toBe(2);
|
||||
});
|
||||
|
||||
it('calls sendRequest() callback', async () => {
|
||||
const sendRequest = jest.fn(() =>
|
||||
Promise.resolve({
|
||||
status: 200,
|
||||
}),
|
||||
) as unknown as SendRequestCallback;
|
||||
const { stats }: TestResults = await runTests(
|
||||
const sendRequest = getMockedSendRequest();
|
||||
|
||||
const { stats } = await runTests(
|
||||
exampleTestWithRequest,
|
||||
{
|
||||
requests: [
|
||||
{
|
||||
_id: 'req_123',
|
||||
},
|
||||
],
|
||||
sendRequest,
|
||||
},
|
||||
{ sendRequest },
|
||||
);
|
||||
|
||||
expect(sendRequest).toHaveBeenCalledWith('req_123');
|
||||
expect(stats.passes).toBe(1);
|
||||
});
|
||||
@@ -73,7 +67,7 @@ describe('run', () => {
|
||||
let err;
|
||||
|
||||
try {
|
||||
await runTests('this is invalid');
|
||||
await runTests('this is invalid', { sendRequest: getMockedSendRequest() });
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@ import Mocha, { Reporter, ReporterConstructor } from 'mocha';
|
||||
import { tmpdir } from 'os';
|
||||
import { join } from 'path';
|
||||
|
||||
import Insomnia, { InsomniaOptions } from './insomnia';
|
||||
import { TestResults } from './entities';
|
||||
import { Insomnia, InsomniaOptions } from './insomnia';
|
||||
import { JavaScriptReporter } from './javascript-reporter';
|
||||
|
||||
declare global {
|
||||
@@ -17,13 +18,12 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
const runInternal = async <T>(
|
||||
const runInternal = async <TReturn, TNetworkResponse>(
|
||||
testSrc: string | string[],
|
||||
options: InsomniaOptions,
|
||||
options: InsomniaOptions<TNetworkResponse>,
|
||||
reporter: Reporter | ReporterConstructor = 'spec',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- type not available, and postponing anyway until the impending move to all jest (and no mocha)
|
||||
extractResult: (runner: { [key: string]: any }) => T,
|
||||
): Promise<T> => new Promise((resolve, reject) => {
|
||||
extractResult: (runner: Mocha.Runner) => TReturn,
|
||||
) => new Promise<TReturn>((resolve, reject) => {
|
||||
const { bail, keepFile, testFilter } = options;
|
||||
|
||||
// Add global `insomnia` helper.
|
||||
@@ -83,32 +83,33 @@ const writeTempFile = (sourceCode: string) => {
|
||||
return path;
|
||||
};
|
||||
|
||||
type CliOptions = InsomniaOptions & {
|
||||
type CliOptions<TNetworkResponse> = InsomniaOptions<TNetworkResponse> & {
|
||||
reporter?: Reporter
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a test file using Mocha
|
||||
*/
|
||||
export const runTestsCli = async (
|
||||
export const runTestsCli = async <TNetworkResponse>(
|
||||
testSrc: string | string[],
|
||||
{ reporter, ...options }: CliOptions = {},
|
||||
{ reporter, ...options }: CliOptions<TNetworkResponse>,
|
||||
) => runInternal(
|
||||
testSrc,
|
||||
options,
|
||||
reporter,
|
||||
runner => !runner.stats.failures,
|
||||
runner => !Boolean(runner.stats?.failures),
|
||||
);
|
||||
|
||||
/**
|
||||
* Run a test file using Mocha and returns JS results
|
||||
*/
|
||||
export const runTests = async <T>(
|
||||
export const runTests = async <TNetworkResponse>(
|
||||
testSrc: string | string[],
|
||||
options: InsomniaOptions = {},
|
||||
) => runInternal<T>(
|
||||
options: InsomniaOptions<TNetworkResponse>,
|
||||
) => runInternal(
|
||||
testSrc,
|
||||
options,
|
||||
JavaScriptReporter,
|
||||
runner => runner.testResults,
|
||||
// @ts-expect-error the `testResults` property is added onto the runner by the JavascriptReporter
|
||||
runner => runner.testResults as TestResults,
|
||||
);
|
||||
@@ -0,0 +1,17 @@
|
||||
import { mocked } from 'ts-jest/utils';
|
||||
|
||||
import { SendRequestCallback } from '../run/insomnia';
|
||||
|
||||
export const mockedSendRequest = <TResp extends {status: number}>(response?: TResp) => {
|
||||
return mocked<SendRequestCallback<TResp>>(jest.fn()).mockResolvedValue(response || { status: 200 } as TResp);
|
||||
};
|
||||
|
||||
export const mockedSendRequestMultiple = <TResp>(...responses: TResp[]) => {
|
||||
let mock = mocked<SendRequestCallback<TResp>>(jest.fn());
|
||||
|
||||
responses.forEach(response => {
|
||||
mock = mock.mockResolvedValueOnce(response);
|
||||
});
|
||||
|
||||
return mock;
|
||||
};
|
||||
@@ -10,7 +10,7 @@
|
||||
],
|
||||
"exclude": [
|
||||
"**/fixtures",
|
||||
"**/__mocks__",
|
||||
"**/test-helpers",
|
||||
"**/*.test.ts"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user