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:
Jack Kavanagh 2024-01-29 13:00:24 +01:00 committed by GitHub
parent 875a8cba1f
commit 9a1545c964
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 177 additions and 274 deletions

7
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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);
};

View File

@ -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)[];

View File

@ -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);
}
});

View File

@ -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);
});
}),

View File

@ -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];

View File

@ -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;
}

View File

@ -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,
});
});
});

View File

@ -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 => {

View File

@ -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,
};
}

View File

@ -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: [],
};
}

View File

@ -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;

View File

@ -233,12 +233,10 @@ describe('app.export.*', () => {
method: 'GET',
postData: {
mimeType: '',
params: [],
text: '',
},
queryString: [],
url: 'https://insomnia.rest/',
settingEncodeUrl: true,
},
response: {
bodySize: -1,

View File

@ -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'>

View File

@ -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',

View File

@ -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;
}
];
};

View File

@ -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>

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -37,7 +37,7 @@ declare module 'apiconnect-wsdl' {
export function getWSDLServices(
allWSDLs: WSDL[],
options?: UNKNOWN_OBJ
options?: {}
): ServiceData;
export function findWSDLForServiceName(

View File

@ -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++;

View File

@ -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 {};
}

View File

@ -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;