Skip to content

Commit

Permalink
chore(tests): add tests for provider forms
Browse files Browse the repository at this point in the history
  • Loading branch information
huwshimi committed Dec 11, 2024
1 parent 3bb0555 commit 37598e4
Show file tree
Hide file tree
Showing 7 changed files with 356 additions and 40 deletions.
129 changes: 129 additions & 0 deletions ui/src/pages/providers/ProviderCreate/ProviderCreate.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { screen, waitFor } from "@testing-library/dom";
import { faker } from "@faker-js/faker";
import userEvent from "@testing-library/user-event";
import { Location } from "react-router-dom";
import MockAdapter from "axios-mock-adapter";
import * as reactQuery from "@tanstack/react-query";

import { renderComponent } from "test/utils";
import { urls } from "urls";
import { axiosInstance } from "api/axios";

import ProviderCreate from "./ProviderCreate";
import { ProviderFormLabel } from "../ProviderForm";
import { Label } from "./types";
import { initialValues } from "./ProviderCreate";
import {
NotificationProvider,
NotificationConsumer,
} from "@canonical/react-components";
import { queryKeys } from "util/queryKeys";

vi.mock("@tanstack/react-query", async () => {
const actual = await vi.importActual("@tanstack/react-query");
return {
...actual,
useQueryClient: vi.fn(),
};
});

const mock = new MockAdapter(axiosInstance);

beforeEach(() => {
mock.reset();
vi.spyOn(reactQuery, "useQueryClient").mockReturnValue({
invalidateQueries: vi.fn(),
} as unknown as reactQuery.QueryClient);
mock.onPost("/idps").reply(200);
});

test("can cancel", async () => {
let location: Location | null = null;
renderComponent(<ProviderCreate />, {
url: "/",
setLocation: (newLocation) => {
location = newLocation;
},
});
await userEvent.click(screen.getByRole("button", { name: Label.CANCEL }));
expect((location as Location | null)?.pathname).toBe(urls.providers.index);
});

test("calls the API on submit", async () => {
const values = {
id: faker.word.sample(),
};
renderComponent(<ProviderCreate />);
const input = screen.getByRole("textbox", { name: ProviderFormLabel.NAME });
await userEvent.click(input);
await userEvent.clear(input);
await userEvent.type(input, values.id);
await userEvent.click(screen.getByRole("button", { name: Label.SUBMIT }));
expect(mock.history.post[0].url).toBe("/idps");
expect(JSON.parse(mock.history.post[0].data as string)).toMatchObject({
...initialValues,
scope: initialValues.scope.split(","),
...values,
});
});

test("handles API success", async () => {
let location: Location | null = null;
const invalidateQueries = vi.fn();
vi.spyOn(reactQuery, "useQueryClient").mockReturnValue({
invalidateQueries,
} as unknown as reactQuery.QueryClient);
mock.onPost("/idps").reply(200);
const values = {
id: faker.word.sample(),
};
renderComponent(
<NotificationProvider>
<ProviderCreate />
<NotificationConsumer />
</NotificationProvider>,
{
url: "/",
setLocation: (newLocation) => {
location = newLocation;
},
},
);
await userEvent.type(
screen.getByRole("textbox", { name: ProviderFormLabel.NAME }),
values.id,
);
await userEvent.click(screen.getByRole("button", { name: Label.SUBMIT }));
await waitFor(() =>
expect(invalidateQueries).toHaveBeenCalledWith({
queryKey: [queryKeys.providers],
}),
);
expect(document.querySelector(".p-notification--positive")).toHaveTextContent(
Label.SUCCESS,
),
expect((location as Location | null)?.pathname).toBe(urls.providers.index);
});

test("handles API failure", async () => {
mock.onPost("/idps").reply(400, {
message: "oops",
});
const values = {
id: faker.word.sample(),
};
renderComponent(
<NotificationProvider>
<ProviderCreate />
<NotificationConsumer />
</NotificationProvider>,
);
await userEvent.type(
screen.getByRole("textbox", { name: ProviderFormLabel.NAME }),
values.id,
);
await userEvent.click(screen.getByRole("button", { name: Label.SUBMIT }));
expect(document.querySelector(".p-notification--negative")).toHaveTextContent(
`${Label.ERROR}oops`,
);
});
36 changes: 21 additions & 15 deletions ui/src/pages/providers/ProviderCreate/ProviderCreate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ import SidePanel from "components/SidePanel";
import ScrollableContainer from "components/ScrollableContainer";
import { TestId } from "./test-types";
import { testId } from "test/utils";
import { Label } from "./types";

export const initialValues = {
provider: "generic",
id: "",
client_id: "",
client_secret: "",
mapper_url: "",
scope: "email",
subject_source: "userinfo",
} as const;

const ProviderCreate: FC = () => {
const navigate = useNavigate();
Expand All @@ -31,15 +42,7 @@ const ProviderCreate: FC = () => {
});

const formik = useFormik<ProviderFormTypes>({
initialValues: {
provider: "generic",
id: "",
client_id: "",
client_secret: "",
mapper_url: "",
scope: "email",
subject_source: "userinfo",
},
initialValues,
validationSchema: ProviderCreateSchema,
onSubmit: (values) => {
createProvider(
Expand All @@ -49,12 +52,15 @@ const ProviderCreate: FC = () => {
void queryClient.invalidateQueries({
queryKey: [queryKeys.providers],
});
const msg = `Provider created.`;
navigate("/provider", notify.queue(notify.success(msg)));
navigate("/provider", notify.queue(notify.success(Label.SUCCESS)));
})
.catch((e) => {
.catch((error: unknown) => {
formik.setSubmitting(false);
notify.failure("Provider creation failed", e);
notify.failure(
Label.ERROR,
error instanceof Error ? error : null,
typeof error === "string" ? error : null,
);
});
},
});
Expand Down Expand Up @@ -85,15 +91,15 @@ const ProviderCreate: FC = () => {
<Row className="u-align-text--right">
<Col size={12}>
<Button appearance="base" onClick={() => navigate("/provider")}>
Cancel
{Label.CANCEL}
</Button>
<ActionButton
appearance="positive"
loading={formik.isSubmitting}
disabled={!formik.isValid}
onClick={submitForm}
>
Save
{Label.SUBMIT}
</ActionButton>
</Col>
</Row>
Expand Down
6 changes: 6 additions & 0 deletions ui/src/pages/providers/ProviderCreate/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export enum Label {
CANCEL = "Cancel",
ERROR = "Provider creation failed",
SUBMIT = "Save",
SUCCESS = "Provider created.",
}
167 changes: 167 additions & 0 deletions ui/src/pages/providers/ProviderEdit/ProviderEdit.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { screen, waitFor } from "@testing-library/dom";
import { faker } from "@faker-js/faker";
import userEvent from "@testing-library/user-event";
import { Location } from "react-router-dom";
import MockAdapter from "axios-mock-adapter";
import * as reactQuery from "@tanstack/react-query";

import { renderComponent } from "test/utils";
import { urls } from "urls";

Check failure on line 9 in ui/src/pages/providers/ProviderEdit/ProviderEdit.test.tsx

View workflow job for this annotation

GitHub Actions / Lint (20.x)

'urls' is defined but never used
import { axiosInstance } from "api/axios";

import ProviderEdit from "./ProviderEdit";
import { ProviderFormLabel } from "../ProviderForm";
import { Label } from "./types";
import {
NotificationProvider,
NotificationConsumer,
} from "@canonical/react-components";
import { queryKeys } from "util/queryKeys";
import { mockIdentityProvider } from "test/mocks/providers";
import { IdentityProvider } from "types/provider";

vi.mock("@tanstack/react-query", async () => {
const actual = await vi.importActual("@tanstack/react-query");
return {
...actual,
useQueryClient: vi.fn(),
};
});

const mock = new MockAdapter(axiosInstance);

let provider: IdentityProvider;

beforeEach(() => {
vi.spyOn(reactQuery, "useQueryClient").mockReturnValue({
invalidateQueries: vi.fn(),
} as unknown as reactQuery.QueryClient);
mock.reset();
provider = mockIdentityProvider({
id: faker.word.sample(),
apple_private_key: faker.word.sample(),
apple_private_key_id: faker.word.sample(),
apple_team_id: faker.word.sample(),
auth_url: faker.word.sample(),
client_id: faker.word.sample(),
client_secret: faker.word.sample(),
issuer_url: faker.word.sample(),
mapper_url: faker.word.sample(),
microsoft_tenant: faker.word.sample(),
provider: faker.word.sample(),
requested_claims: faker.word.sample(),
subject_source: "userinfo",
token_url: faker.word.sample(),
scope: ["email"],
});
mock.onGet(`/idps/${provider.id}`).reply(200, { data: [provider] });
mock.onPatch(`/idps/${provider.id}`).reply(200);
});

test("can cancel", async () => {
let location: Location | null = null;
renderComponent(<ProviderEdit />, {
url: `/?id=${provider.id}`,
setLocation: (newLocation) => {
location = newLocation;
},
});
await userEvent.click(screen.getByRole("button", { name: Label.CANCEL }));
expect((location as Location | null)?.pathname).toBe("/");
expect((location as Location | null)?.search).toBe("");
});

test("calls the API on submit", async () => {
const values = {
scope: faker.word.sample(),
};
renderComponent(<ProviderEdit />, {
url: `/?id=${provider.id}`,
});
const input = screen.getByRole("textbox", { name: ProviderFormLabel.SCOPES });
await userEvent.click(input);
await userEvent.clear(input);
await userEvent.type(input, values.scope);
await userEvent.click(screen.getByRole("button", { name: Label.SUBMIT }));
expect(mock.history.patch[0].url).toBe(`/idps/${provider.id}`);
expect(JSON.parse(mock.history.patch[0].data as string)).toMatchObject({
apple_private_key: provider.apple_private_key,
apple_private_key_id: provider.apple_private_key_id,
apple_team_id: provider.apple_team_id,
auth_url: provider.auth_url,
client_id: provider.client_id,
client_secret: provider.client_secret,
id: provider.id,
issuer_url: provider.issuer_url,
mapper_url: provider.mapper_url,
microsoft_tenant: provider.microsoft_tenant,
provider: provider.provider,
requested_claims: provider.requested_claims,
subject_source: "userinfo",
token_url: provider.token_url,
scope: [values.scope],
});
});

test("handles API success", async () => {
let location: Location | null = null;
const invalidateQueries = vi.fn();
vi.spyOn(reactQuery, "useQueryClient").mockReturnValue({
invalidateQueries,
} as unknown as reactQuery.QueryClient);
mock.onPatch(`/idps/${provider.id}`).reply(200);
const values = {
scope: faker.word.sample(),
};
renderComponent(
<NotificationProvider>
<ProviderEdit />
<NotificationConsumer />
</NotificationProvider>,
{
url: `/?id=${provider.id}`,
setLocation: (newLocation) => {
location = newLocation;
},
},
);
await userEvent.type(
screen.getByRole("textbox", { name: ProviderFormLabel.SCOPES }),
values.scope,
);
await userEvent.click(screen.getByRole("button", { name: Label.SUBMIT }));
await waitFor(() =>
expect(invalidateQueries).toHaveBeenCalledWith({
queryKey: [queryKeys.providers],
}),
);
expect(document.querySelector(".p-notification--positive")).toHaveTextContent(
Label.SUCCESS,
);
expect((location as Location | null)?.pathname).toBe("/");
expect((location as Location | null)?.search).toBe("");
});

test("handles API failure", async () => {
mock.onPatch(`/idps/${provider.id}`).reply(400, {
message: "oops",
});
const values = {
scope: faker.word.sample(),
};
renderComponent(
<NotificationProvider>
<ProviderEdit />
<NotificationConsumer />
</NotificationProvider>,
{ url: `/?id=${provider.id}` },
);
await userEvent.type(
screen.getByRole("textbox", { name: ProviderFormLabel.SCOPES }),
values.scope,
);
await userEvent.click(screen.getByRole("button", { name: Label.SUBMIT }));
expect(document.querySelector(".p-notification--negative")).toHaveTextContent(
`${Label.ERROR}oops`,
);
});
Loading

0 comments on commit 37598e4

Please sign in to comment.