mirror of
https://github.com/Kong/insomnia
synced 2024-11-07 14:19:58 +00:00
feat: mock second pass (#7022)
* fall back hidden request * redirect after add new route * add session id * remove custom HAR types * remove any type hack * fix tests * fix tests * fix test * add mock server export * clean console * test request clean up * list routes on imports --------- Co-authored-by: gatzjames <jamesgatzos@gmail.com>
This commit is contained in:
parent
875a8cba1f
commit
9a1545c964
7
package-lock.json
generated
7
package-lock.json
generated
@ -21,6 +21,7 @@
|
||||
"@jest/types": "^28.1.0",
|
||||
"@types/chai": "^4.3.5",
|
||||
"@types/eslint": "^8.4.3",
|
||||
"@types/har-format": "^1.2.15",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^20.3.3",
|
||||
"@types/svgo": "^2.6.3",
|
||||
@ -6954,6 +6955,12 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/har-format": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.15.tgz",
|
||||
"integrity": "sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/hawk": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/hawk/-/hawk-9.0.2.tgz",
|
||||
|
@ -48,6 +48,7 @@
|
||||
"@jest/types": "^28.1.0",
|
||||
"@types/chai": "^4.3.5",
|
||||
"@types/eslint": "^8.4.3",
|
||||
"@types/har-format": "^1.2.15",
|
||||
"@types/mocha": "^10.0.1",
|
||||
"@types/node": "^20.3.3",
|
||||
"@types/svgo": "^2.6.3",
|
||||
|
@ -19,7 +19,6 @@ import type { RunTestsOptions } from './commands/run-tests';
|
||||
import { reporterTypes, runInsomniaTests, TestReporter } from './commands/run-tests';
|
||||
import { getOptions } from './get-options';
|
||||
import { configureLogger, logger } from './logger';
|
||||
import { UNKNOWN_OBJ } from './types';
|
||||
import { exit, getVersion, logErrorExit1 } from './util';
|
||||
|
||||
const prepareCommand = (options: Partial<GenerateConfigOptions>) => {
|
||||
@ -137,7 +136,7 @@ const addScriptCommand = (originalCommand: commander.Command) => {
|
||||
.description('Run scripts defined in .insorc')
|
||||
.allowUnknownOption()
|
||||
// @ts-expect-error this appears to actually be valid, and I don't want to risk changing any behavior
|
||||
.action((scriptName, cmd) => {
|
||||
.action((scriptName: 'lint', cmd) => {
|
||||
// Load scripts
|
||||
let options = getOptions(cmd);
|
||||
options = prepareCommand(options);
|
||||
@ -213,9 +212,6 @@ export const go = (args?: string[], exitOverride?: boolean) => {
|
||||
runWithArgs(cmd, args || process.argv);
|
||||
};
|
||||
|
||||
const runWithArgs = (
|
||||
cmd: UNKNOWN_OBJ,
|
||||
args: string[],
|
||||
) => {
|
||||
const runWithArgs = (cmd: commander.Command, args: string[]) => {
|
||||
cmd.parseAsync(args).catch(logErrorExit1);
|
||||
};
|
||||
|
@ -2,7 +2,6 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import YAML from 'yaml';
|
||||
|
||||
import { UNKNOWN } from '../../types';
|
||||
import type { Database, DbAdapter } from '../index';
|
||||
import { emptyDb } from '../index';
|
||||
|
||||
@ -22,7 +21,7 @@ const gitAdapter: DbAdapter = async (dir, filterTypes) => {
|
||||
// Get contents of each file in type dir and insert into data
|
||||
const contents = await fs.promises.readFile(fileName);
|
||||
const obj = YAML.parse(contents.toString());
|
||||
(db[type] as UNKNOWN[]).push(obj);
|
||||
(db[type] as {}[]).push(obj);
|
||||
};
|
||||
|
||||
const types = filterTypes?.length ? filterTypes : Object.keys(db) as (keyof Database)[];
|
||||
|
@ -3,7 +3,6 @@ import path from 'path';
|
||||
import YAML from 'yaml';
|
||||
|
||||
import { InsoError } from '../../errors';
|
||||
import { UNKNOWN } from '../../types';
|
||||
import { DbAdapter } from '../index';
|
||||
import { emptyDb } from '../index';
|
||||
import { BaseModel } from '../models/types';
|
||||
@ -110,7 +109,7 @@ const insomniaAdapter: DbAdapter = async (filePath, filterTypes) => {
|
||||
const obj = parseRaw(model);
|
||||
|
||||
// Store it, only if the key value exists
|
||||
(db[obj.type] as UNKNOWN[])?.push(obj);
|
||||
(db[obj.type] as {}[])?.push(obj);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -3,7 +3,6 @@ import { stat } from 'node:fs/promises';
|
||||
import NeDB from '@seald-io/nedb';
|
||||
import path from 'path';
|
||||
|
||||
import { UNKNOWN, UNKNOWN_OBJ } from '../../types';
|
||||
import { Database, DbAdapter, emptyDb } from '../index';
|
||||
import type { BaseModel } from '../models/types';
|
||||
|
||||
@ -25,12 +24,12 @@ const neDbAdapter: DbAdapter = async (dir, filterTypes) => {
|
||||
filename: filePath,
|
||||
corruptAlertThreshold: 0.9,
|
||||
});
|
||||
collection.find({}, (err: UNKNOWN, docs: BaseModel[]) => {
|
||||
collection.find({}, (err: Error, docs: BaseModel[]) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
(db[t] as UNKNOWN_OBJ[]).push(...docs);
|
||||
(db[t] as {}[]).push(...docs);
|
||||
resolve(null);
|
||||
});
|
||||
}),
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { cosmiconfigSync } from 'cosmiconfig';
|
||||
|
||||
import { GenerateConfigOptions } from './commands/generate-config';
|
||||
import { UNKNOWN_OBJ } from './types';
|
||||
|
||||
interface ConfigFileOptions {
|
||||
__configFile?: {
|
||||
options?: UNKNOWN_OBJ;
|
||||
scripts?: UNKNOWN_OBJ;
|
||||
options?: GlobalOptions;
|
||||
scripts?: {
|
||||
lint: string;
|
||||
};
|
||||
filePath: string;
|
||||
};
|
||||
}
|
||||
@ -21,7 +22,7 @@ export type GlobalOptions = {
|
||||
src?: string;
|
||||
} & ConfigFileOptions;
|
||||
|
||||
const OptionsSupportedInConfigFile: (keyof GlobalOptions)[] = [
|
||||
export const OptionsSupportedInConfigFile: (keyof GlobalOptions)[] = [
|
||||
'appDataDir',
|
||||
'workingDir',
|
||||
'ci',
|
||||
@ -36,7 +37,7 @@ export const loadCosmiConfig = (configFile?: string): Partial<ConfigFileOptions>
|
||||
const results = configFile ? explorer.load(configFile) : explorer.search();
|
||||
|
||||
if (results && !results?.isEmpty) {
|
||||
const options: UNKNOWN_OBJ = {};
|
||||
const options: GlobalOptions = {};
|
||||
OptionsSupportedInConfigFile.forEach(key => {
|
||||
const value = results.config?.options?.[key];
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
/** TypeScript version of "TODO" but for types */
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- This is a utility type explicitly intended for the sake of typescript conversions
|
||||
export type UNKNOWN = any;
|
||||
|
||||
/** TypeScript version of "TODO" but for Objects */
|
||||
export interface UNKNOWN_OBJ {
|
||||
[key: string]: UNKNOWN;
|
||||
}
|
@ -100,7 +100,6 @@ describe('export', () => {
|
||||
queryString: [],
|
||||
postData: {
|
||||
mimeType: 'application/json',
|
||||
params: [],
|
||||
text: '{}',
|
||||
},
|
||||
headersSize: -1,
|
||||
@ -472,7 +471,6 @@ describe('export', () => {
|
||||
method: 'POST',
|
||||
postData: {
|
||||
mimeType: '',
|
||||
params: [],
|
||||
text: 'foo bar',
|
||||
},
|
||||
queryString: [
|
||||
@ -482,7 +480,6 @@ describe('export', () => {
|
||||
},
|
||||
],
|
||||
url: 'http://google.com/',
|
||||
settingEncodeUrl: true,
|
||||
});
|
||||
});
|
||||
|
||||
@ -557,11 +554,9 @@ describe('export', () => {
|
||||
fileName: '/tmp/my_file_2',
|
||||
},
|
||||
],
|
||||
text: '',
|
||||
},
|
||||
queryString: [],
|
||||
url: 'http://example.com/post',
|
||||
settingEncodeUrl: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -27,6 +27,7 @@ import { showAlert, showError, showModal } from '../ui/components/modals';
|
||||
import { AskModal } from '../ui/components/modals/ask-modal';
|
||||
import { SelectModal } from '../ui/components/modals/select-modal';
|
||||
import { Insomnia4Data } from '../utils/importers/importers';
|
||||
import { invariant } from '../utils/invariant';
|
||||
import {
|
||||
EXPORT_TYPE_API_SPEC,
|
||||
EXPORT_TYPE_COOKIE_JAR,
|
||||
@ -56,7 +57,6 @@ const getDocWithDescendants = (includePrivateDocs = false) => async (parentDoc:
|
||||
doc => !doc?.isPrivate || includePrivateDocs,
|
||||
);
|
||||
};
|
||||
|
||||
export async function exportWorkspacesHAR(
|
||||
workspaces: Workspace[],
|
||||
includePrivateDocs = false,
|
||||
@ -422,6 +422,64 @@ export const exportProjectToFile = (activeProjectName: string, workspacesForActi
|
||||
},
|
||||
});
|
||||
};
|
||||
const exportMockServer = async (workspace: Workspace, selectedFormat: 'json' | 'yaml') => {
|
||||
const data: Insomnia4Data = {
|
||||
_type: 'export',
|
||||
__export_format: EXPORT_FORMAT,
|
||||
__export_date: new Date(),
|
||||
__export_source: `insomnia.desktop.app:v${getAppVersion()}`,
|
||||
resources: [],
|
||||
};
|
||||
const mockServer = await models.mockServer.getByParentId(workspace._id);
|
||||
invariant(mockServer, 'expected mock server to be defined');
|
||||
const mockRoutes = await models.mockRoute.findByParentId(mockServer._id);
|
||||
|
||||
// unclear why we need a _type here, or if they should match prefix or not
|
||||
data.resources.push({ ...workspace, _type: 'workspace' });
|
||||
data.resources.push({ ...mockServer, _type: 'mock' });
|
||||
mockRoutes.map(mockRoute => data.resources.push({ ...mockRoute, _type: 'mock_route' }));
|
||||
if (selectedFormat === 'yaml') {
|
||||
return YAML.stringify(data);
|
||||
}
|
||||
return JSON.stringify(data);
|
||||
};
|
||||
export const exportMockServerToFile = async (workspace: Workspace) => {
|
||||
const options = [{ name: 'Insomnia v4 (JSON)', value: VALUE_JSON }, { name: 'Insomnia v4 (YAML)', value: VALUE_YAML }];
|
||||
const lastFormat = window.localStorage.getItem('insomnia.lastExportFormat');
|
||||
const defaultValue = options.find(({ value }) => value === lastFormat) ? lastFormat : VALUE_JSON;
|
||||
|
||||
showModal(SelectModal, {
|
||||
title: 'Select Export Type',
|
||||
value: defaultValue,
|
||||
options,
|
||||
message: 'Which format would you like to export as?',
|
||||
onDone: async selectedFormat => {
|
||||
invariant(selectedFormat, 'expected selected format to be defined');
|
||||
invariant(selectedFormat === 'json' || selectedFormat === 'yaml', 'unexpected selected format');
|
||||
window.localStorage.setItem('insomnia.lastExportFormat', selectedFormat);
|
||||
const fileName = await showSaveExportedFileDialog({
|
||||
exportedFileNamePrefix: workspace.name,
|
||||
selectedFormat,
|
||||
});
|
||||
if (!fileName) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const stringifiedExport = await exportMockServer(workspace, 'json');
|
||||
writeExportedFileToFileSystem(fileName, stringifiedExport, err => err && console.warn('Export failed', err));
|
||||
window.main.trackSegmentEvent({ event: SegmentEvent.dataExport, properties: { type: selectedFormat, scope: 'mock-server' } });
|
||||
} catch (err) {
|
||||
showError({
|
||||
title: 'Export Failed',
|
||||
error: err,
|
||||
message: 'Export failed due to an unexpected error',
|
||||
});
|
||||
return;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
};
|
||||
export const exportRequestsToFile = (requestIds: string[]) => {
|
||||
showSelectExportTypeModal({
|
||||
onDone: async selectedFormat => {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import clone from 'clone';
|
||||
import fs from 'fs';
|
||||
import * as Har from 'har-format';
|
||||
import { Cookie as ToughCookie } from 'tough-cookie';
|
||||
|
||||
import * as models from '../models';
|
||||
@ -18,164 +19,13 @@ import { filterHeaders, getSetCookieHeaders, hasAuthHeader } from './misc';
|
||||
import type { RenderedRequest } from './render';
|
||||
import { getRenderedRequestAndContext } from './render';
|
||||
|
||||
export interface HarCookie {
|
||||
name: string;
|
||||
value: string;
|
||||
path?: string;
|
||||
domain?: string;
|
||||
expires?: string;
|
||||
httpOnly?: boolean;
|
||||
secure?: boolean;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarHeader {
|
||||
name: string;
|
||||
value: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarQueryString {
|
||||
name: string;
|
||||
value: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarPostParam {
|
||||
name: string;
|
||||
value?: string;
|
||||
fileName?: string;
|
||||
contentType?: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarPostData {
|
||||
mimeType: string;
|
||||
params: HarPostParam[];
|
||||
text: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarRequest {
|
||||
method: string;
|
||||
url: string;
|
||||
httpVersion: string;
|
||||
cookies: HarCookie[];
|
||||
headers: HarHeader[];
|
||||
queryString: HarQueryString[];
|
||||
postData?: HarPostData;
|
||||
headersSize: number;
|
||||
bodySize: number;
|
||||
comment?: string;
|
||||
settingEncodeUrl: boolean;
|
||||
}
|
||||
|
||||
export interface HarContent {
|
||||
size: number;
|
||||
compression?: number;
|
||||
mimeType: string;
|
||||
text?: string;
|
||||
encoding?: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarResponse {
|
||||
status: number;
|
||||
statusText: string;
|
||||
httpVersion: string;
|
||||
cookies: HarCookie[];
|
||||
headers: HarHeader[];
|
||||
content: HarContent;
|
||||
redirectURL: string;
|
||||
headersSize: number;
|
||||
bodySize: number;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarRequestCache {
|
||||
expires?: string;
|
||||
lastAccess: string;
|
||||
eTag: string;
|
||||
hitCount: number;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarCache {
|
||||
beforeRequest?: HarRequestCache;
|
||||
afterRequest?: HarRequestCache;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarEntryTimings {
|
||||
blocked?: number;
|
||||
dns?: number;
|
||||
connect?: number;
|
||||
send: number;
|
||||
wait: number;
|
||||
receive: number;
|
||||
ssl?: number;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarEntry {
|
||||
pageref?: string;
|
||||
startedDateTime: string;
|
||||
time: number;
|
||||
request: HarRequest;
|
||||
response: HarResponse;
|
||||
cache: HarCache;
|
||||
timings: HarEntryTimings;
|
||||
serverIPAddress?: string;
|
||||
connection?: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarPageTimings {
|
||||
onContentLoad?: number;
|
||||
onLoad?: number;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarPage {
|
||||
startedDateTime: string;
|
||||
id: string;
|
||||
title: string;
|
||||
pageTimings: HarPageTimings;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarCreator {
|
||||
name: string;
|
||||
version: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarBrowser {
|
||||
name: string;
|
||||
version: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface HarLog {
|
||||
version: string;
|
||||
creator: HarCreator;
|
||||
browser?: HarBrowser;
|
||||
pages?: HarPage[];
|
||||
entries: HarEntry[];
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface Har {
|
||||
log: HarLog;
|
||||
}
|
||||
|
||||
export interface ExportRequest {
|
||||
requestId: string;
|
||||
environmentId: string | null;
|
||||
responseId?: string;
|
||||
}
|
||||
|
||||
export async function exportHarCurrentRequest(request: Request, response: Response): Promise<Har> {
|
||||
export async function exportHarCurrentRequest(request: Request, response: Response): Promise<Har.Har> {
|
||||
const ancestors = await database.withAncestors(request, [
|
||||
models.workspace.type,
|
||||
models.requestGroup.type,
|
||||
@ -204,7 +54,7 @@ export async function exportHarCurrentRequest(request: Request, response: Respon
|
||||
export async function exportHar(exportRequests: ExportRequest[]) {
|
||||
// Export HAR entries with the same start time in order to keep their workspace sort order.
|
||||
const startedDateTime = new Date().toISOString();
|
||||
const entries: HarEntry[] = [];
|
||||
const entries: Har.Entry[] = [];
|
||||
|
||||
for (const exportRequest of exportRequests) {
|
||||
const request: Request | null = await models.request.getById(exportRequest.requestId);
|
||||
@ -255,7 +105,7 @@ export async function exportHar(exportRequests: ExportRequest[]) {
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
const har: Har = {
|
||||
const har: Har.Har = {
|
||||
log: {
|
||||
version: '1.2',
|
||||
creator: {
|
||||
@ -286,7 +136,7 @@ export async function exportHarResponse(response: Response | null) {
|
||||
};
|
||||
}
|
||||
|
||||
const harResponse: HarResponse = {
|
||||
const harResponse: Har.Response = {
|
||||
status: response.statusCode,
|
||||
statusText: response.statusMessage,
|
||||
httpVersion: 'HTTP/1.1',
|
||||
@ -392,7 +242,7 @@ export async function exportHarWithRenderedRequest(
|
||||
}
|
||||
}
|
||||
|
||||
const harRequest: HarRequest = {
|
||||
const harRequest: Har.Request = {
|
||||
method: renderedRequest.method,
|
||||
url,
|
||||
httpVersion: 'HTTP/1.1',
|
||||
@ -402,7 +252,6 @@ export async function exportHarWithRenderedRequest(
|
||||
postData: getRequestPostData(renderedRequest),
|
||||
headersSize: -1,
|
||||
bodySize: -1,
|
||||
settingEncodeUrl: renderedRequest.settingEncodeUrl,
|
||||
};
|
||||
return harRequest;
|
||||
}
|
||||
@ -410,11 +259,11 @@ export async function exportHarWithRenderedRequest(
|
||||
function getRequestCookies(renderedRequest: RenderedRequest) {
|
||||
const jar = jarFromCookies(renderedRequest.cookieJar.cookies);
|
||||
const domainCookies = renderedRequest.url ? jar.getCookiesSync(renderedRequest.url) : [];
|
||||
const harCookies: HarCookie[] = domainCookies.map(mapCookie);
|
||||
const harCookies: Har.Cookie[] = domainCookies.map(mapCookie);
|
||||
return harCookies;
|
||||
}
|
||||
|
||||
export function getResponseCookiesFromHeaders(headers: HarCookie[]) {
|
||||
export function getResponseCookiesFromHeaders(headers: Har.Cookie[]) {
|
||||
return getSetCookieHeaders(headers)
|
||||
.reduce((accumulator, harCookie) => {
|
||||
let cookie: null | undefined | ToughCookie = null;
|
||||
@ -431,7 +280,7 @@ export function getResponseCookiesFromHeaders(headers: HarCookie[]) {
|
||||
...accumulator,
|
||||
mapCookie(cookie),
|
||||
];
|
||||
}, [] as HarCookie[]);
|
||||
}, [] as Har.Cookie[]);
|
||||
}
|
||||
|
||||
function getResponseCookies(response: Response) {
|
||||
@ -440,7 +289,7 @@ function getResponseCookies(response: Response) {
|
||||
}
|
||||
|
||||
function mapCookie(cookie: ToughCookie) {
|
||||
const harCookie: HarCookie = {
|
||||
const harCookie: Har.Cookie = {
|
||||
name: cookie.key,
|
||||
value: cookie.value,
|
||||
};
|
||||
@ -487,7 +336,7 @@ function getResponseContent(response: Response) {
|
||||
if (body === null) {
|
||||
body = Buffer.alloc(0);
|
||||
}
|
||||
const harContent: HarContent = {
|
||||
const harContent: Har.Content = {
|
||||
size: Buffer.byteLength(body),
|
||||
mimeType: response.contentType,
|
||||
text: body.toString('utf8'),
|
||||
@ -498,7 +347,7 @@ function getResponseContent(response: Response) {
|
||||
function getResponseHeaders(response: Response) {
|
||||
return response.headers
|
||||
.filter(header => header.name)
|
||||
.map<HarHeader>(header => ({
|
||||
.map<Har.Header>(header => ({
|
||||
name: header.name,
|
||||
value: header.value,
|
||||
}));
|
||||
@ -507,20 +356,20 @@ function getResponseHeaders(response: Response) {
|
||||
function getRequestHeaders(renderedRequest: RenderedRequest) {
|
||||
return renderedRequest.headers
|
||||
.filter(header => header.name)
|
||||
.map<HarHeader>(header => ({
|
||||
.map<Har.Header>(header => ({
|
||||
name: header.name,
|
||||
value: header.value,
|
||||
}));
|
||||
}
|
||||
|
||||
function getRequestQueryString(renderedRequest: RenderedRequest): HarQueryString[] {
|
||||
return renderedRequest.parameters.map<HarQueryString>(parameter => ({
|
||||
function getRequestQueryString(renderedRequest: RenderedRequest): Har.QueryString[] {
|
||||
return renderedRequest.parameters.map<Har.QueryString>(parameter => ({
|
||||
name: parameter.name,
|
||||
value: parameter.value,
|
||||
}));
|
||||
}
|
||||
|
||||
function getRequestPostData(renderedRequest: RenderedRequest): HarPostData | undefined {
|
||||
function getRequestPostData(renderedRequest: RenderedRequest): Har.PostData | undefined {
|
||||
let body;
|
||||
if (renderedRequest.body.fileName) {
|
||||
try {
|
||||
@ -536,27 +385,20 @@ function getRequestPostData(renderedRequest: RenderedRequest): HarPostData | und
|
||||
body = renderedRequest.body;
|
||||
}
|
||||
|
||||
let params: any[] = [];
|
||||
|
||||
if (body.params) {
|
||||
params = body.params.map(param => {
|
||||
if (param.type === 'file') {
|
||||
return {
|
||||
name: param.name,
|
||||
fileName: param.fileName,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name: param.name,
|
||||
value: param.value,
|
||||
};
|
||||
});
|
||||
return {
|
||||
mimeType: body.mimeType || '',
|
||||
params: body.params.map(({ name, value, fileName, type }) => ({
|
||||
name,
|
||||
...(type === 'file'
|
||||
? { fileName }
|
||||
: { value }),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
mimeType: body.mimeType || '',
|
||||
text: body.text || '',
|
||||
params: params,
|
||||
};
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { BaseEnvironment, Environment, isEnvironment } from '../models/environme
|
||||
import { GrpcRequest, isGrpcRequest } from '../models/grpc-request';
|
||||
import { BaseModel, getModel } from '../models/index';
|
||||
import * as models from '../models/index';
|
||||
import { isMockRoute, MockRoute } from '../models/mock-route';
|
||||
import { isRequest, Request } from '../models/request';
|
||||
import { isUnitTest, UnitTest } from '../models/unit-test';
|
||||
import { isUnitTestSuite, UnitTestSuite } from '../models/unit-test-suite';
|
||||
@ -72,6 +73,7 @@ export interface ScanResult {
|
||||
cookieJars?: CookieJar[];
|
||||
unitTests?: UnitTest[];
|
||||
unitTestSuites?: UnitTestSuite[];
|
||||
mockRoutes?: MockRoute[];
|
||||
type?: InsomniaImporter;
|
||||
errors: string[];
|
||||
}
|
||||
@ -129,6 +131,7 @@ export async function scanResources({
|
||||
const apiSpecs = resources.filter(isApiSpec);
|
||||
const workspaces = resources.filter(isWorkspace);
|
||||
const cookieJars = resources.filter(isCookieJar);
|
||||
const mockRoutes = resources.filter(isMockRoute);
|
||||
|
||||
return {
|
||||
type,
|
||||
@ -139,6 +142,7 @@ export async function scanResources({
|
||||
environments,
|
||||
apiSpecs,
|
||||
cookieJars,
|
||||
mockRoutes,
|
||||
errors: [],
|
||||
};
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ export const name = 'Mock Route';
|
||||
|
||||
export const type = 'MockRoute';
|
||||
|
||||
export const prefix = 'mock_route';
|
||||
export const prefix = 'mock-route';
|
||||
|
||||
export const canDuplicate = true;
|
||||
|
||||
|
@ -233,12 +233,10 @@ describe('app.export.*', () => {
|
||||
method: 'GET',
|
||||
postData: {
|
||||
mimeType: '',
|
||||
params: [],
|
||||
text: '',
|
||||
},
|
||||
queryString: [],
|
||||
url: 'https://insomnia.rest/',
|
||||
settingEncodeUrl: true,
|
||||
},
|
||||
response: {
|
||||
bodySize: -1,
|
||||
|
@ -4,6 +4,7 @@ import { useFetcher, useParams } from 'react-router-dom';
|
||||
|
||||
import { parseApiSpec } from '../../../common/api-specs';
|
||||
import { getProductName } from '../../../common/constants';
|
||||
import { exportMockServerToFile } from '../../../common/export';
|
||||
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
|
||||
import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render';
|
||||
import type { ApiSpec } from '../../../models/api-spec';
|
||||
@ -158,7 +159,9 @@ export const WorkspaceCardDropdown: FC<Props> = props => {
|
||||
<ItemContent
|
||||
label="Export"
|
||||
icon="file-export"
|
||||
onClick={() => setIsExportModalOpen(true)}
|
||||
onClick={() => workspace.scope !== 'mock-server'
|
||||
? setIsExportModalOpen(true)
|
||||
: exportMockServerToFile(workspace)}
|
||||
/>
|
||||
</DropdownItem>
|
||||
<DropdownItem aria-label='Settings'>
|
||||
|
@ -6,6 +6,7 @@ import { useFetcher, useParams, useRouteLoaderData } from 'react-router-dom';
|
||||
import { isLoggedIn } from '../../../account/session';
|
||||
import { getProductName } from '../../../common/constants';
|
||||
import { database as db } from '../../../common/database';
|
||||
import { exportMockServerToFile } from '../../../common/export';
|
||||
import { getWorkspaceLabel } from '../../../common/get-workspace-label';
|
||||
import { RENDER_PURPOSE_NO_RENDER } from '../../../common/render';
|
||||
import { isRemoteProject } from '../../../models/project';
|
||||
@ -158,7 +159,9 @@ export const WorkspaceDropdown: FC = () => {
|
||||
id: 'export',
|
||||
name: 'Export',
|
||||
icon: <Icon icon='file-export' />,
|
||||
action: () => setIsExportModalOpen(true),
|
||||
action: () => activeWorkspace.scope !== 'mock-server'
|
||||
? setIsExportModalOpen(true)
|
||||
: exportMockServerToFile(activeWorkspace),
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
import * as Har from 'har-format';
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import { useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { getMockServiceURL, PREVIEW_MODE_SOURCE } from '../../../common/constants';
|
||||
import { HarRequest } from '../../../common/har';
|
||||
import { ResponseTimelineEntry } from '../../../main/network/libcurl-promise';
|
||||
import * as models from '../../../models';
|
||||
import { MockRouteLoaderData } from '../../routes/mock-route';
|
||||
@ -26,7 +26,7 @@ interface MockbinLogOutput {
|
||||
{
|
||||
startedDateTime: string;
|
||||
clientIPAddress: string;
|
||||
request: HarRequest;
|
||||
request: Har.Request;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
@ -780,6 +780,17 @@ const ImportResourcesForm = ({
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
{scanResult.mockRoutes &&
|
||||
scanResult.mockRoutes?.length > 0 && (
|
||||
<tr className="table--no-outline-row">
|
||||
<td>
|
||||
{scanResult.mockRoutes?.length}{' '}
|
||||
{scanResult.mockRoutes?.length === 1
|
||||
? 'Mock Route'
|
||||
: 'Mock Routes'}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
@ -1199,14 +1199,14 @@ export const reorderCollectionAction: ActionFunction = async ({ request, params
|
||||
return null;
|
||||
};
|
||||
|
||||
export const createMockRouteAction: ActionFunction = async ({ request }) => {
|
||||
export const createMockRouteAction: ActionFunction = async ({ request, params }) => {
|
||||
const { organizationId, projectId, workspaceId } = params;
|
||||
|
||||
const patch = await request.json();
|
||||
invariant(typeof patch.name === 'string', 'Name is required');
|
||||
invariant(typeof patch.parentId === 'string', 'parentId is required');
|
||||
const mockRoute = await models.mockRoute.create(patch);
|
||||
// create a single hidden request under the mock route for testing the mock endpoint
|
||||
await models.request.create({ parentId: mockRoute._id, isPrivate: true });
|
||||
return null;
|
||||
return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/mock-server/mock-route/${mockRoute._id}`);
|
||||
};
|
||||
export const updateMockRouteAction: ActionFunction = async ({ request, params }) => {
|
||||
const { mockRouteId } = params;
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { AxiosResponse } from 'axios';
|
||||
import * as Har from 'har-format';
|
||||
import React from 'react';
|
||||
import { LoaderFunction, useFetcher, useParams, useRouteLoaderData } from 'react-router-dom';
|
||||
|
||||
import { getCurrentSessionId } from '../../account/session';
|
||||
import { CONTENT_TYPE_JSON, CONTENT_TYPE_PLAINTEXT, CONTENT_TYPE_XML, CONTENT_TYPE_YAML, contentTypesMap, getMockServiceURL, RESPONSE_CODE_REASONS } from '../../common/constants';
|
||||
import { database as db } from '../../common/database';
|
||||
import { getResponseCookiesFromHeaders, HarResponse } from '../../common/har';
|
||||
import { getResponseCookiesFromHeaders } from '../../common/har';
|
||||
import * as models from '../../models';
|
||||
import { MockRoute } from '../../models/mock-route';
|
||||
import { MockServer } from '../../models/mock-server';
|
||||
@ -57,7 +59,7 @@ const mockContentTypes = [
|
||||
CONTENT_TYPE_YAML,
|
||||
];
|
||||
// mockbin expect a HAR response structure
|
||||
export const mockRouteToHar = ({ statusCode, statusText, mimeType, headersArray, body }: { statusCode: number; statusText: string; mimeType: string; headersArray: RequestHeader[]; body: string }): HarResponse => {
|
||||
export const mockRouteToHar = ({ statusCode, statusText, mimeType, headersArray, body }: { statusCode: number; statusText: string; mimeType: string; headersArray: RequestHeader[]; body: string }): Har.Response => {
|
||||
const validHeaders = headersArray.filter(({ name }) => !!name);
|
||||
return {
|
||||
status: +statusCode,
|
||||
@ -103,7 +105,6 @@ export const MockRouteRoute = () => {
|
||||
|
||||
const upsertBinOnRemoteFromResponse = async (compoundId: string | null): Promise<string> => {
|
||||
try {
|
||||
|
||||
const res: AxiosResponse<MockbinResult | MockbinError> = await window.main.axiosRequest({
|
||||
url: mockbinUrl + `/bin/upsert/${compoundId}`,
|
||||
method: 'put',
|
||||
@ -114,6 +115,9 @@ export const MockRouteRoute = () => {
|
||||
mimeType: mockRoute.mimeType,
|
||||
body: mockRoute.body,
|
||||
}),
|
||||
headers: {
|
||||
'X-Session-ID': getCurrentSessionId(),
|
||||
},
|
||||
});
|
||||
if (typeof res?.data === 'object' && 'errors' in res?.data && typeof res?.data?.errors === 'string') {
|
||||
console.error('error response', res?.data?.errors);
|
||||
|
@ -435,8 +435,9 @@ export const createAndSendToMockbinAction: ActionFunction = async ({ request })
|
||||
invariant(typeof patch.parentId === 'string', 'mock route ID is required');
|
||||
const mockRoute = await models.mockRoute.getById(patch.parentId);
|
||||
invariant(mockRoute, 'mock route not found');
|
||||
// Get or create a testing request for this mock route
|
||||
const childRequests = await models.request.findByParentId(mockRoute._id);
|
||||
const testRequest = childRequests[0];
|
||||
const testRequest = childRequests[0] || (await models.request.create({ parentId: mockRoute._id, isPrivate: true }));
|
||||
invariant(testRequest, 'mock route is missing a testing request');
|
||||
const req = await models.request.update(testRequest, patch);
|
||||
|
||||
|
@ -1,12 +1,7 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- this is a temporary hold-me-over while we get the types into better condition
|
||||
export type UNKNOWN = any;
|
||||
|
||||
export interface UNKNOWN_OBJ {
|
||||
[key: string]: UNKNOWN;
|
||||
}
|
||||
import * as Har from 'har-format';
|
||||
|
||||
export interface Comment {
|
||||
comment?: UNKNOWN;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export type Variable = `{{ ${string} }}`;
|
||||
@ -52,12 +47,6 @@ export interface Header extends Comment {
|
||||
value: UNKNOWN;
|
||||
}
|
||||
|
||||
export interface PostData {
|
||||
params?: Parameter[];
|
||||
mimeType?: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
export interface QueryString extends Comment {
|
||||
name: string;
|
||||
}
|
||||
@ -75,7 +64,7 @@ export interface ImportRequest<T extends {} = {}> extends Comment {
|
||||
authentication?: Authentication;
|
||||
body?: Body;
|
||||
cookies?: Cookie[];
|
||||
environment?: UNKNOWN_OBJ;
|
||||
environment?: {};
|
||||
headers?: Header[];
|
||||
httpVersion?: string;
|
||||
method?: string;
|
||||
@ -84,7 +73,7 @@ export interface ImportRequest<T extends {} = {}> extends Comment {
|
||||
description?: string;
|
||||
parameters?: Parameter[];
|
||||
parentId?: string | null;
|
||||
postData?: PostData;
|
||||
postData?: Har.PostData;
|
||||
variable?: UNKNOWN;
|
||||
queryString?: QueryString[];
|
||||
url?: string;
|
||||
|
@ -37,7 +37,7 @@ declare module 'apiconnect-wsdl' {
|
||||
|
||||
export function getWSDLServices(
|
||||
allWSDLs: WSDL[],
|
||||
options?: UNKNOWN_OBJ
|
||||
options?: {}
|
||||
): ServiceData;
|
||||
|
||||
export function findWSDLForServiceName(
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ControlOperator, parse, ParseEntry } from 'shell-quote';
|
||||
import { URL } from 'url';
|
||||
|
||||
import { Converter, ImportRequest, Parameter, PostData } from '../entities';
|
||||
import { Converter, ImportRequest, Parameter } from '../entities';
|
||||
|
||||
export const id = 'curl';
|
||||
export const name = 'cURL';
|
||||
@ -200,25 +200,31 @@ const importCommand = (parseEntries: ParseEntry[]): ImportRequest => {
|
||||
});
|
||||
|
||||
/// /////// Body //////////
|
||||
const body: PostData = mimeType ? { mimeType } : {};
|
||||
let body = {};
|
||||
const bodyAsGET = getPairValue(pairsByName, false, ['G', 'get']);
|
||||
|
||||
if (dataParameters.length !== 0 && bodyAsGET) {
|
||||
parameters.push(...dataParameters);
|
||||
} else if (dataParameters && mimeType === 'application/x-www-form-urlencoded') {
|
||||
body.params = dataParameters.map(parameter => {
|
||||
return {
|
||||
body = {
|
||||
mimeType,
|
||||
params: dataParameters.map(parameter => ({
|
||||
...parameter,
|
||||
name: decodeURIComponent(parameter.name || ''),
|
||||
value: decodeURIComponent(parameter.value || ''),
|
||||
};
|
||||
});
|
||||
})),
|
||||
};
|
||||
|
||||
} else if (dataParameters.length !== 0) {
|
||||
body.text = dataParameters.map(parameter => `${parameter.name}${parameter.value}`).join('&');
|
||||
body.mimeType = mimeType || '';
|
||||
body = {
|
||||
text: dataParameters.map(parameter => `${parameter.name}${parameter.value}`).join('&'),
|
||||
mimeType: mimeType || '',
|
||||
};
|
||||
} else if (formDataParams.length) {
|
||||
body.params = formDataParams;
|
||||
body.mimeType = mimeType || 'multipart/form-data';
|
||||
body = {
|
||||
params: formDataParams,
|
||||
mimeType: mimeType || 'multipart/form-data',
|
||||
};
|
||||
}
|
||||
|
||||
/// /////// Method //////////
|
||||
@ -227,8 +233,8 @@ const importCommand = (parseEntries: ParseEntry[]): ImportRequest => {
|
||||
'request',
|
||||
]).toUpperCase();
|
||||
|
||||
if (method === '__UNSET__') {
|
||||
method = body.text || body.params ? 'POST' : 'GET';
|
||||
if (method === '__UNSET__' && body) {
|
||||
method = ('text' in body || 'params' in body) ? 'POST' : 'GET';
|
||||
}
|
||||
|
||||
const count = requestCount++;
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Body, Converter, ImportRequest, PostData, UNKNOWN } from '../entities';
|
||||
import * as Har from 'har-format';
|
||||
|
||||
import { Body, Converter, ImportRequest } from '../entities';
|
||||
|
||||
export const id = 'har';
|
||||
export const name = 'HAR 1.2';
|
||||
@ -6,18 +8,13 @@ export const description = 'Importer for HTTP Archive 1.2';
|
||||
|
||||
let requestCount = 1;
|
||||
|
||||
interface Entry {
|
||||
comment: UNKNOWN;
|
||||
request: ImportRequest;
|
||||
}
|
||||
|
||||
interface HarRoot {
|
||||
log: {
|
||||
entries: Entry[];
|
||||
entries: Har.Entry[];
|
||||
};
|
||||
httpVersion: UNKNOWN;
|
||||
method: UNKNOWN;
|
||||
url: UNKNOWN;
|
||||
httpVersion: string;
|
||||
method: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const extractRequests = (harRoot: HarRoot): ImportRequest[] => {
|
||||
@ -37,12 +34,12 @@ const extractRequests = (harRoot: HarRoot): ImportRequest[] => {
|
||||
});
|
||||
};
|
||||
|
||||
const removeComment = <T extends { comment?: UNKNOWN }>(obj: T) => {
|
||||
const removeComment = <T extends { comment?: string }>(obj: T) => {
|
||||
const { comment, ...newObject } = obj;
|
||||
return newObject;
|
||||
};
|
||||
|
||||
const importPostData = (postData?: PostData): Body => {
|
||||
const importPostData = (postData?: Har.PostData): Body => {
|
||||
if (!postData) {
|
||||
return {};
|
||||
}
|
||||
|
@ -3,8 +3,6 @@ import {
|
||||
Header,
|
||||
ImportRequest,
|
||||
Parameter,
|
||||
UNKNOWN,
|
||||
UNKNOWN_OBJ,
|
||||
} from '../entities';
|
||||
|
||||
export const id = 'insomnia-1';
|
||||
@ -14,10 +12,10 @@ export const description = 'Legacy Insomnia format';
|
||||
type Format = 'form' | 'json' | 'text' | 'xml';
|
||||
|
||||
interface Item {
|
||||
requests: UNKNOWN[];
|
||||
requests: any[];
|
||||
name?: string;
|
||||
environments?: {
|
||||
base: UNKNOWN_OBJ;
|
||||
base: {};
|
||||
};
|
||||
__insomnia?: {
|
||||
format: Format;
|
||||
|
Loading…
Reference in New Issue
Block a user