Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 5 additions & 12 deletions packages/insomnia-smoke-test/tests/smoke/mock.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
import { loadFixture } from '../../playwright/paths';
import { test } from '../../playwright/test';

test('can make a mock route', async ({ app, page }) => {
test('can make a mock route', async ({ page }) => {
test.slow(process.platform === 'darwin' || process.platform === 'win32', 'Slow app start on these platforms');
const text = await loadFixture('smoke-test-collection.yaml');
await app.evaluate(async ({ clipboard }, text) => clipboard.writeText(text), text);

await page.getByRole('button', { name: 'Create in project' }).click();
await page.getByRole('menuitemradio', { name: 'Import' }).click();
await page.locator('[data-test-id="import-from-clipboard"]').click();
await page.getByRole('button', { name: 'Scan' }).click();
await page.getByRole('dialog').getByRole('button', { name: 'Import' }).click();

await page.getByLabel('New Mock Server').click();
await page.getByRole('button', { name: 'Create', exact: true }).click();
await page.getByRole('button', { name: 'New Mock Route' }).click();
await page.getByText('GET/').click();
await page.getByTestId('CodeEditor').getByRole('textbox').fill('123');
await page.getByLabel('Project Actions').click();
await page.getByText('Rename').click();
await page.locator('#prompt-input').fill('/123');
await page.getByRole('button', { name: 'Rename' }).click();

await page.getByRole('button', { name: 'Test' }).click();
await page.getByText('No body returned for response').click();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,32 @@ import fs from 'fs/promises';
import React, { useState } from 'react';
import { Button } from 'react-aria-components';
import { useNavigate, useParams } from 'react-router-dom';
import { useRouteLoaderData } from 'react-router-dom';
import {
useFetcher,
useRouteLoaderData,
} from 'react-router-dom';

import { invariant } from '../../../utils/invariant';
import { useMockRoutePatcher } from '../../routes/mock-route';
import { RequestLoaderData } from '../../routes/request';
import { WorkspaceLoaderData } from '../../routes/workspace';
import { HelpTooltip } from '../help-tooltip';
import { Icon } from '../icon';
import { showPrompt } from '../modals';

export const MockResponseExtractor = () => {
const {
activeWorkspace,
} = useRouteLoaderData(':workspaceId') as WorkspaceLoaderData;
const { mockServerAndRoutes, activeResponse } = useRouteLoaderData('request/:requestId') as RequestLoaderData;
const patchMockRoute = useMockRoutePatcher();
const navigate = useNavigate();
const {
organizationId,
projectId,
workspaceId,
} = useParams();
const fetcher = useFetcher();
const [selectedMockServer, setSelectedMockServer] = useState('');
const [selectedMockRoute, setSelectedMockRoute] = useState('');
return (
Expand All @@ -30,21 +41,84 @@ export const MockResponseExtractor = () => {
<form
onSubmit={async e => {
e.preventDefault();
if (!selectedMockServer || !selectedMockRoute) {
if (selectedMockServer && selectedMockRoute) {
if (activeResponse) {
// TODO: move this out of the renderer, and upsert mock
const body = await fs.readFile(activeResponse.bodyPath);

patchMockRoute(selectedMockRoute, {
body: body.toString(),
mimeType: activeResponse.contentType,
statusCode: activeResponse.statusCode,
headers: activeResponse.headers,
});
}
return;
}
let path = '/new-route';
try {
path = activeResponse ? new URL(activeResponse.url).pathname : '/new-route';
} catch (e) {
console.log(e);
}
if (!selectedMockServer) {
showPrompt({
title: 'Create Mock Route',
defaultValue: path,
label: 'Name',
onComplete: async name => {
invariant(activeResponse, 'Active response must be defined');
const body = await fs.readFile(activeResponse.bodyPath);
// TODO: consider setting selected mock server here rather than redirecting
fetcher.submit(
JSON.stringify({
name: name,
body: body.toString(),
mimeType: activeResponse.contentType,
statusCode: activeResponse.statusCode,
headers: activeResponse.headers,
mockServerName: activeWorkspace.name,
}),
{
encType: 'application/json',
method: 'post',
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/mock-server/mock-route/new`,
}
);
},
});
return;
}
if (!selectedMockRoute) {
showPrompt({
title: 'Create Mock Route',
defaultValue: path,
label: 'Name',
onComplete: async name => {
invariant(activeResponse, 'Active response must be defined');
const body = await fs.readFile(activeResponse.bodyPath);

if (activeResponse) {
// TODO: move this out of the renderer, and upsert mock
const body = await fs.readFile(activeResponse.bodyPath);
// setSelectedMockRoute(newRoute._id);

patchMockRoute(selectedMockRoute, {
body: body.toString(),
mimeType: activeResponse.contentType,
statusCode: activeResponse.statusCode,
headers: activeResponse.headers,
fetcher.submit(
JSON.stringify({
name: name,
parentId: selectedMockServer,
body: body.toString(),
mimeType: activeResponse.contentType,
statusCode: activeResponse.statusCode,
headers: activeResponse.headers,
}),
{
encType: 'application/json',
method: 'post',
action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/mock-server/mock-route/new`,
}
);
},
});
}

}}
>
<div className="form-row">
Expand All @@ -61,7 +135,7 @@ export const MockResponseExtractor = () => {
setSelectedMockServer(selected);
}}
>
<option value="">-- Select... --</option>
<option value="">-- Create new... --</option>
{mockServerAndRoutes
.map(w => (
<option key={w._id} value={w._id}>
Expand All @@ -87,7 +161,7 @@ export const MockResponseExtractor = () => {
setSelectedMockRoute(selected);
}}
>
<option value="">-- Select... --</option>
<option value="">-- Create new... --</option>
{mockServerAndRoutes.find(s => s._id === selectedMockServer)?.routes
.map(w => (
<option key={w._id} value={w._id}>
Expand All @@ -112,10 +186,9 @@ export const MockResponseExtractor = () => {
</Button>
<Button
type="submit"
isDisabled={!selectedMockServer || !selectedMockRoute}
className="hover:no-underline bg-[--color-surprise] hover:bg-opacity-90 border border-solid border-[--hl-md] py-2 px-3 text-[--color-font-surprise] transition-colors rounded-sm"
>
Export
Extract to mock route
</Button>
</div>
</form>
Expand Down
21 changes: 19 additions & 2 deletions packages/insomnia/src/ui/routes/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1211,9 +1211,26 @@ export const createMockRouteAction: ActionFunction = async ({ request, params })

const patch = await request.json();
invariant(typeof patch.name === 'string', 'Name is required');
invariant(typeof patch.parentId === 'string', 'parentId is required');
// TODO: remove this hack
if (patch.mockServerName) {
const activeWorkspace = await models.workspace.getById(workspaceId);
invariant(activeWorkspace, 'Active workspace not found');
const workspace = await models.workspace.create({
name: activeWorkspace.name,
scope: 'mock-server',
parentId: projectId,
});
invariant(workspace, 'Workspace not found');
// create a mock server under the workspace with the same name
const newServer = await models.mockServer.getOrCreateForParentId(workspace._id, { name: activeWorkspace.name });
// TODO: filterout the mockServerName from the patch, or use an alternate method to create new workspace and server
const mockRoute = await models.mockRoute.create({ ...patch, parentId: newServer._id });
return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${newServer.parentId}/mock-server/mock-route/${mockRoute._id}`);
}
const mockServer = await models.mockServer.getById(patch.parentId);
invariant(mockServer, 'Mock server not found');
const mockRoute = await models.mockRoute.create(patch);
return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/mock-server/mock-route/${mockRoute._id}`);
return redirect(`/organization/${organizationId}/project/${projectId}/workspace/${mockServer.parentId}/mock-server/mock-route/${mockRoute._id}`);
};
export const updateMockRouteAction: ActionFunction = async ({ request, params }) => {
const { mockRouteId } = params;
Expand Down
24 changes: 23 additions & 1 deletion packages/insomnia/src/ui/routes/mock-route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import { CodeEditor } from '../components/codemirror/code-editor';
import { MockResponseHeadersEditor } from '../components/editors/mock-response-headers-editor';
import { MockResponsePane } from '../components/mocks/mock-response-pane';
import { MockUrlBar } from '../components/mocks/mock-url-bar';
import { showAlert } from '../components/modals';
import { showAlert, showModal } from '../components/modals';
import { AlertModal } from '../components/modals/alert-modal';
import { EmptyStatePane } from '../components/panes/empty-state-pane';
import { Pane, PaneBody, PaneHeader } from '../components/panes/pane';
import { SvgIcon } from '../components/svg-icon';
import { MockServerLoaderData } from './mock-server';
import { useRootLoaderData } from './root';

export interface MockRouteLoaderData {
Expand Down Expand Up @@ -91,6 +93,8 @@ export const useMockRoutePatcher = () => {

export const MockRouteRoute = () => {
const { mockServer, mockRoute } = useRouteLoaderData(':mockRouteId') as MockRouteLoaderData;
const { mockRoutes } = useRouteLoaderData('mock-server') as MockServerLoaderData;

const { userSession } = useRootLoaderData();
const patchMockRoute = useMockRoutePatcher();
const mockbinUrl = mockServer.useInsomniaCloud ? getMockServiceURL() : mockServer.url;
Expand Down Expand Up @@ -142,6 +146,15 @@ export const MockRouteRoute = () => {
});

const upsertMockbinHar = async (pathInput?: string) => {
const hasRouteInServer = mockRoutes.filter(m => m._id !== mockRoute._id).find(m => m.name === pathInput);
if (hasRouteInServer) {
showModal(AlertModal, {
title: 'Error',
message: `Path "${pathInput}" must be unique. Please enter a different name.`,
});

return;
};
const compoundId = mockRoute.parentId + pathInput;
const error = await upsertBinOnRemoteFromResponse(compoundId);
if (error) {
Expand All @@ -163,6 +176,15 @@ export const MockRouteRoute = () => {
});
};
const onSend = async (pathInput: string) => {
const hasRouteInServer = mockRoutes.filter(m => m._id !== mockRoute._id).find(m => m.name === pathInput);
if (hasRouteInServer) {
showModal(AlertModal, {
title: 'Error',
message: `Path "${pathInput}" must be unique. Please enter a different name.`,
});

return;
};
await upsertMockbinHar(pathInput);
const compoundId = mockRoute.parentId + pathInput;
createandSendPrivateRequest({
Expand Down
23 changes: 20 additions & 3 deletions packages/insomnia/src/ui/routes/mock-server.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ import { WorkspaceSyncDropdown } from '../components/dropdowns/workspace-sync-dr
import { EditableInput } from '../components/editable-input';
import { Icon } from '../components/icon';
import { showModal, showPrompt } from '../components/modals';
import { AlertModal } from '../components/modals/alert-modal';
import { AskModal } from '../components/modals/ask-modal';
import { EmptyStatePane } from '../components/panes/empty-state-pane';
import { SidebarLayout } from '../components/sidebar-layout';
import { SvgIcon } from '../components/svg-icon';
import { formatMethodName } from '../components/tags/method-tag';
import { MockRouteResponse, MockRouteRoute, useMockRoutePatcher } from './mock-route';
interface LoaderData {
export interface MockServerLoaderData {
mockServerId: string;
mockRoutes: MockRoute[];
}
export const loader: LoaderFunction = async ({ params }): Promise<LoaderData> => {
export const loader: LoaderFunction = async ({ params }): Promise<MockServerLoaderData> => {
const { organizationId, projectId, workspaceId } = params;
invariant(organizationId, 'Organization ID is required');
invariant(projectId, 'Project ID is required');
Expand All @@ -46,7 +47,7 @@ const MockServerRoute = () => {
workspaceId: string;
mockRouteId: string;
};
const { mockServerId, mockRoutes } = useLoaderData() as LoaderData;
const { mockServerId, mockRoutes } = useLoaderData() as MockServerLoaderData;
const fetcher = useFetcher();
const navigate = useNavigate();
const patchMockRoute = useMockRoutePatcher();
Expand All @@ -66,6 +67,14 @@ const MockServerRoute = () => {
defaultValue: mockRoutes.find(s => s._id === id)?.name,
submitName: 'Rename',
onComplete: name => {
const hasRouteInServer = mockRoutes.filter(m => m._id !== id).find(m => m.name === name);
if (hasRouteInServer) {
showModal(AlertModal, {
title: 'Error',
message: `Path "${name}" must be unique. Please enter a different name.`,
});
return;
};
name && patchMockRoute(id, { name });
},
});
Expand Down Expand Up @@ -195,6 +204,14 @@ const MockServerRoute = () => {
});
}}
onSubmit={name => {
const hasRouteInServer = mockRoutes.filter(m => m._id !== item._id).find(m => m.name === name);
if (hasRouteInServer) {
showModal(AlertModal, {
title: 'Error',
message: `Path "${name}" must be unique. Please enter a different name.`,
});
return;
};
name && fetcher.submit(
{ name },
{
Expand Down