Skip to content

Commit

Permalink
test coverage for spec patch and runtime ops
Browse files Browse the repository at this point in the history
  • Loading branch information
Mazuh committed Feb 26, 2024
1 parent 74868fd commit 1a68cfa
Show file tree
Hide file tree
Showing 3 changed files with 389 additions and 12 deletions.
4 changes: 2 additions & 2 deletions src/features/project-workspace/ProjectWorkspacePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function WorkspaceBody() {
selected={selectedSpec}
setSelected={setSelectedSpec}
/>
<Runtime key={selectedSpec} specUuid={selectedSpec} />
{!!selectedSpec && <Runtime key={selectedSpec} specUuid={selectedSpec} />}
</WorkspaceContainer>
);
}
Expand Down Expand Up @@ -141,7 +141,7 @@ function RequestsSpecsList(props: {
onMouseLeave={() => setHovered(null)}
onClick={handleSpecClickFn(spec)}
className={cn(
"cursor-pointer flex justify-between items-center px-3",
"cursor-pointer flex justify-between items-center px-3 select-none",
"hover:bg-accent",
props.selected === spec.uuid ? "bg-accent" : ""
)}
Expand Down
377 changes: 377 additions & 0 deletions src/features/project-workspace/Runtime.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,377 @@
import "@testing-library/jest-dom";
import { act, fireEvent, render, screen } from "@testing-library/react";
import * as OPFSSharedInternalsService from "@/services/opfs-projects-shared-internals";
import { RequestsSpecsContextProvider } from "./RequestsSpecsContextProvider";
import { Runtime } from "./Runtime";

jest.mock("wouter", () => ({
useParams: jest.fn().mockReturnValue({}),
}));

jest.mock("@/services/opfs-projects-shared-internals", () => ({
retrieveProject: jest.fn(),
persistProject: jest.fn(),
}));

interface MockedGlobalWithFetch {
fetch: (url: string) => Promise<MockedResponse>;
}

interface MockedResponse {
text: () => Promise<string>;
status: number;
ok: boolean;
headers: [string, string][];
}

describe("Runtime component, given a selected specification", () => {
beforeAll(() => {
(global as unknown as MockedGlobalWithFetch).fetch = async () => ({
text: async () => "",
status: -1,
ok: false,
headers: [],
});
});

beforeEach(async () => {
jest.clearAllMocks();

jest
.spyOn(OPFSSharedInternalsService, "retrieveProject")
.mockResolvedValue({
uuid: "7fde4f8e-b6ac-4218-ae20-1b866e61ec56",
name: "Zippopotamus",
sections: [],
specs: [
{
uuid: "0b761507-a24c-4a81-8391-9cee4a6e7c34",
url: "https://api.zippopotam.us/us/33162",
method: "GET",
headers: [
{ key: "Accept", value: "application/json", isEnabled: true },
],
body: "",
},
{
uuid: "f62f869f-cb12-4997-b679-ceec1096040b",
url: "https://api.zippopotam.us/br/70150904",
method: "GET",
headers: [
{ key: "Accept", value: "application/json", isEnabled: true },
],
body: "",
},
{
uuid: "304f0780-33a3-45e4-b9ea-e099f4306832",
url: "https://api.zippopotam.us/blabla",
method: "GET",
headers: [
{ key: "Accept", value: "application/json", isEnabled: true },
],
body: "",
},
{
uuid: "a78ee247-4bf2-4393-bec0-57a1b0a8a23d",
url: "https://catfact.ninja/non-existing",
method: "GET",
headers: [
{ key: "Accept", value: "application/json", isEnabled: true },
],
body: "",
},
],
});

jest
.spyOn(OPFSSharedInternalsService, "persistProject")
.mockResolvedValue();

jest
.spyOn(global as unknown as MockedGlobalWithFetch, "fetch")
.mockImplementation(async (url: string) => {
switch (url) {
case "https://api.zippopotam.us/us/33162":
return {
text: async () =>
JSON.stringify({
"post code": "33162",
country: "United States",
"country abbreviation": "US",
places: [
{
"place name": "Miami",
longitude: "-80.183",
state: "Florida",
"state abbreviation": "FL",
latitude: "25.9286",
},
],
}),
status: 200,
ok: true,
headers: [
["content-type", "application/json"],
["x-custom-header", "my-custom-h-value"],
],
};

case "https://api.zippopotam.us/br/70150904":
return {
text: async () => JSON.stringify({}),
status: 400,
ok: false,
headers: [["content-type", "application/json"]],
};

case "https://api.zippopotam.us/blabla":
return {
text: async () => `
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head>
<title>Error: 404 Not Found</title>
<style type="text/css">
html {background-color: #eee; font-family: sans;} body {background-color: #fff; border: 1px solid #ddd; padding: 15px; margin: 15px;} pre {background-color: #eee; border: 1px solid #ddd; padding: 5px;}
</style>
</head>
<body>
<h1>Error: 404 Not Found</h1> <p>Sorry, the requested URL <tt>&#039;http://api.zippopotam.us/blabla&#039;</tt> caused an error:</p> <pre>Not found: &#039;/blabla&#039;</pre>
</body>
</html>
`,
status: 404,
ok: false,
headers: [["content-type", "text/html; charset=UTF-8"]],
};

case "https://catfact.ninja/non-existing":
throw new Error("CORS Error (mocked).");

default:
throw new Error(
"Unexpected `fetch` call during test scenario. Unmocked URL."
);
}
});
});

it("displays such selected spec, with already editable url", async () => {
await act(async () =>
render(
<RequestsSpecsContextProvider projectUuid="7fde4f8e-b6ac-4218-ae20-1b866e61ec56">
<Runtime specUuid="0b761507-a24c-4a81-8391-9cee4a6e7c34" />
</RequestsSpecsContextProvider>
)
);

const url = screen.getByLabelText(/URL/i);
expect(url).toHaveValue("https://api.zippopotam.us/us/33162");
});

it("can edit only (and really only) its URL while typing", async () => {
await act(async () =>
render(
<RequestsSpecsContextProvider projectUuid="7fde4f8e-b6ac-4218-ae20-1b866e61ec56">
<Runtime specUuid="0b761507-a24c-4a81-8391-9cee4a6e7c34" />
</RequestsSpecsContextProvider>
)
);

const url = screen.getByLabelText(/URL/i);
await act(async () =>
fireEvent.change(url, {
target: { value: "https://api-completetly-different" },
})
);

// (waiting a little bit, cause patch calls are debounced)
await new Promise((resolve) => setTimeout(resolve, 500));

expect(OPFSSharedInternalsService.persistProject).toHaveBeenCalledTimes(1);
expect(OPFSSharedInternalsService.persistProject).toHaveBeenCalledWith({
uuid: "7fde4f8e-b6ac-4218-ae20-1b866e61ec56",
name: "Zippopotamus",
sections: [],
specs: [
{
uuid: "0b761507-a24c-4a81-8391-9cee4a6e7c34",
url: "https://api-completetly-different",
method: "GET",
headers: [
{ key: "Accept", value: "application/json", isEnabled: true },
],
body: "",
},
{
uuid: "f62f869f-cb12-4997-b679-ceec1096040b",
url: "https://api.zippopotam.us/br/70150904",
method: "GET",
headers: [
{ key: "Accept", value: "application/json", isEnabled: true },
],
body: "",
},
{
uuid: "304f0780-33a3-45e4-b9ea-e099f4306832",
url: "https://api.zippopotam.us/blabla",
method: "GET",
headers: [
{ key: "Accept", value: "application/json", isEnabled: true },
],
body: "",
},
{
uuid: "a78ee247-4bf2-4393-bec0-57a1b0a8a23d",
url: "https://catfact.ninja/non-existing",
method: "GET",
headers: [
{ key: "Accept", value: "application/json", isEnabled: true },
],
body: "",
},
],
});
});

it("can perform and display a http 200 with json response", async () => {
await act(async () =>
render(
<RequestsSpecsContextProvider projectUuid="7fde4f8e-b6ac-4218-ae20-1b866e61ec56">
<Runtime specUuid="0b761507-a24c-4a81-8391-9cee4a6e7c34" />
</RequestsSpecsContextProvider>
)
);

const run = screen.getByRole("button", { name: /Run/i });
await act(async () => fireEvent.click(run));

expect(screen.getByText(/HTTP success/i)).toBeVisible();
expect(screen.getByText("200")).toBeVisible();
expect(
screen.getByText(
JSON.stringify({
"post code": "33162",
country: "United States",
"country abbreviation": "US",
places: [
{
"place name": "Miami",
longitude: "-80.183",
state: "Florida",
"state abbreviation": "FL",
latitude: "25.9286",
},
],
})
)
).toBeVisible();
});

it("can show detailed request headers", async () => {
await act(async () =>
render(
<RequestsSpecsContextProvider projectUuid="7fde4f8e-b6ac-4218-ae20-1b866e61ec56">
<Runtime specUuid="0b761507-a24c-4a81-8391-9cee4a6e7c34" />
</RequestsSpecsContextProvider>
)
);

const run = screen.getByRole("button", { name: /Run/i });
await act(async () => fireEvent.click(run));

const requestHeadersBtn = screen.getByRole("button", {
name: "Request headers (1)",
});
await act(async () => fireEvent.click(requestHeadersBtn));

expect(screen.getByText(/Accept.*:.*application\/json/i)).toBeVisible();
});

it("can show detailed response headers", async () => {
await act(async () =>
render(
<RequestsSpecsContextProvider projectUuid="7fde4f8e-b6ac-4218-ae20-1b866e61ec56">
<Runtime specUuid="0b761507-a24c-4a81-8391-9cee4a6e7c34" />
</RequestsSpecsContextProvider>
)
);

const run = screen.getByRole("button", { name: /Run/i });
await act(async () => fireEvent.click(run));

const requestHeadersBtn = screen.getByRole("button", {
name: "Response headers (2)",
});
await act(async () => fireEvent.click(requestHeadersBtn));

expect(
screen.getByText(/content-type.*:.*application\/json/i)
).toBeVisible();
expect(
screen.getByText(/x-custom-header.*:.*my-custom-h-value/i)
).toBeVisible();
});

it("can perform and display a http 400 with json response instead of regular success", async () => {
await act(async () =>
render(
<RequestsSpecsContextProvider projectUuid="7fde4f8e-b6ac-4218-ae20-1b866e61ec56">
<Runtime specUuid="f62f869f-cb12-4997-b679-ceec1096040b" />
</RequestsSpecsContextProvider>
)
);

const run = screen.getByRole("button", { name: /Run/i });
await act(async () => fireEvent.click(run));

expect(screen.queryByText(/HTTP success/i)).toBe(null);
expect(screen.queryByText("200")).toBe(null);

expect(screen.getByText(/HTTP bad status/i)).toBeVisible();
expect(screen.getByText("400")).toBeVisible();
expect(screen.getByText(JSON.stringify({}))).toBeVisible();
});

it("can perform and display a http 404 with any response instead of regular success", async () => {
await act(async () =>
render(
<RequestsSpecsContextProvider projectUuid="7fde4f8e-b6ac-4218-ae20-1b866e61ec56">
<Runtime specUuid="304f0780-33a3-45e4-b9ea-e099f4306832" />
</RequestsSpecsContextProvider>
)
);

const run = screen.getByRole("button", { name: /Run/i });
await act(async () => fireEvent.click(run));

expect(screen.queryByText(/HTTP success/i)).toBe(null);
expect(screen.queryByText("200")).toBe(null);

expect(screen.getByText(/HTTP bad status/i)).toBeVisible();
expect(screen.getByText("404")).toBeVisible();
expect(
screen.getByText(/Sorry, the requested URL .* caused an error/im)
).toBeVisible();
});

it("can display unknown exceptions/errors differently than http-related errors", async () => {
await act(async () =>
render(
<RequestsSpecsContextProvider projectUuid="7fde4f8e-b6ac-4218-ae20-1b866e61ec56">
<Runtime specUuid="a78ee247-4bf2-4393-bec0-57a1b0a8a23d" />
</RequestsSpecsContextProvider>
)
);

const run = screen.getByRole("button", { name: /Run/i });
await act(async () => fireEvent.click(run));

expect(screen.queryByText(/HTTP success/i)).toBe(null);
expect(screen.queryByText("200")).toBe(null);
expect(screen.queryByText(/HTTP bad status/i)).toBe(null);

expect(screen.getByText("Error")).toBeVisible();
expect(screen.getByText(/CORS Error/im)).toBeVisible();
});
});
Loading

0 comments on commit 1a68cfa

Please sign in to comment.