Skip to content
Open
Show file tree
Hide file tree
Changes from 15 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
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,15 @@ import {
waitFor,
waitForElementToBeRemoved,
} from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { UBUNTU_ARCHIVE_HOST, UBUNTU_SNAPSHOTS_HOST } from "../../constants";
import type { CreateMirrorData } from "@canonical/landscape-openapi";
import { beforeEach, describe, expect, it } from "vitest";
import { UBUNTU_ARCHIVE_HOST } from "../../constants";

const PULLING_NOTE = /pulling and parsing repository data/i;

const mockCreateMirror = vi.fn();

vi.mock("../../api", async () => {
const actual = await vi.importActual("../../api");

return {
...actual,
useCreateMirror: () => ({
mutateAsync: mockCreateMirror,
}),
};
});

describe("AddMirrorForm", () => {
const user = userEvent.setup();

beforeEach(async () => {
mockCreateMirror.mockClear();

renderWithProviders(<AddMirrorForm />);

// The form renders immediately; wait for the "pulling…" note in the
Expand All @@ -52,11 +36,9 @@ describe("AddMirrorForm", () => {

await user.click(screen.getByRole("button", { name: "Add mirror" }));

expect(mockCreateMirror).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({
archiveRoot: `https://${UBUNTU_ARCHIVE_HOST}/ubuntu/`,
}),
);
expect(
await screen.findByText("You have successfully added Name."),
Comment thread
marqode marked this conversation as resolved.
).toBeInTheDocument();
});

it("submits an ubuntu archive mirror pointed at a custom CDN", async () => {
Expand All @@ -68,11 +50,9 @@ describe("AddMirrorForm", () => {

await user.click(screen.getByRole("button", { name: "Add mirror" }));

expect(mockCreateMirror).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({
archiveRoot: cdnUrl,
}),
);
expect(
await screen.findByText("You have successfully added Name."),
).toBeInTheDocument();
Comment thread
marqode marked this conversation as resolved.
});

it("rejects an http source URL with an HTTPS validation error", async () => {
Expand Down Expand Up @@ -105,11 +85,9 @@ describe("AddMirrorForm", () => {

await user.click(screen.getByRole("button", { name: "Add mirror" }));

expect(mockCreateMirror).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({
archiveRoot: `https://${UBUNTU_SNAPSHOTS_HOST}/ubuntu/${date}`,
}),
);
expect(
await screen.findByText("You have successfully added Name."),
).toBeInTheDocument();
});

it("submits an ubuntu pro mirror", async () => {
Expand All @@ -123,20 +101,18 @@ describe("AddMirrorForm", () => {
await user.type(screen.getByLabelText("Token"), token);
await user.click(screen.getByRole("button", { name: "Add mirror" }));

expect(mockCreateMirror).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({}),
);
expect(
await screen.findByText("You have successfully added Name."),
).toBeInTheDocument();
});

it("submits a mirror with preserve signatures enabled", async () => {
await user.click(screen.getByLabelText("Preserve upstream signing key"));
await user.click(screen.getByRole("button", { name: "Add mirror" }));

expect(mockCreateMirror).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({
preserveSignatures: true,
}),
);
expect(
await screen.findByText("You have successfully added Name."),
).toBeInTheDocument();
Comment on lines 148 to +154
});

it("clears package filter and include dependencies when preserve signatures is enabled", async () => {
Expand Down Expand Up @@ -164,7 +140,7 @@ describe("AddMirrorForm", () => {
components: ["main", "universe"],
architectures: ["amd64", "arm64"],
gpgKey: { armor: "ABCDEFG" },
} satisfies Partial<CreateMirrorData["body"]>;
};

await user.selectOptions(
screen.getByLabelText("Source type"),
Expand All @@ -188,19 +164,15 @@ describe("AddMirrorForm", () => {
);
await user.click(screen.getByRole("button", { name: "Add mirror" }));

expect(mockCreateMirror).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining(params),
);
expect(
await screen.findByText("You have successfully added Name."),
).toBeInTheDocument();
});
});

describe("AddMirrorForm loading state", () => {
const user = userEvent.setup();

beforeEach(() => {
mockCreateMirror.mockClear();
});

it("renders the form immediately with a muted 'pulling' note while archive info is fetched", async () => {
renderWithProviders(<AddMirrorForm />);

Expand Down
108 changes: 72 additions & 36 deletions src/features/mirrors/components/EditMirrorForm/EditMirrorForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { screen } from "@testing-library/react";
import { Suspense } from "react";
import LoadingState from "@/components/layout/LoadingState";
import { expectLoadingState } from "@/tests/helpers";
import { beforeEach, expect, vi } from "vitest";
import { expect } from "vitest";
import { EditMirrorForm } from "../..";
import { mirrors } from "@/tests/mocks/mirrors";
import {
Expand All @@ -22,26 +22,9 @@ const TestComponent = () => {
}
};

const mockUpdateMirror = vi.fn();

vi.mock("../../api", async () => {
const actual = await vi.importActual("../../api");

return {
...actual,
useUpdateMirror: () => ({
mutateAsync: mockUpdateMirror,
}),
};
});

describe("EditMirrorForm", () => {
const user = userEvent.setup();

beforeEach(async () => {
mockUpdateMirror.mockReset();
});

it("edits an ubuntu archive mirror", async () => {
const mirror = mirrors.find(
({ archiveRoot }) => new URL(archiveRoot).host === UBUNTU_ARCHIVE_HOST,
Expand All @@ -60,9 +43,11 @@ describe("EditMirrorForm", () => {
await expectLoadingState();
await user.click(screen.getByRole("button", { name: "Save changes" }));

expect(mockUpdateMirror).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining({}),
);
expect(
await screen.findByText(
`You have successfully edited ${mirror.displayName}.`,
Comment thread
marqode marked this conversation as resolved.
),
).toBeInTheDocument();
});

it("edits a third party mirror", async () => {
Expand All @@ -85,23 +70,18 @@ describe("EditMirrorForm", () => {

await expectLoadingState();

const params = {
gpgKey: { armor: "ABCDEF" },
};

// Mirror has existing GPG key, so checkbox is checked by default - uncheck to show textarea
await user.click(screen.getByLabelText("Keep current GPG key"));
await user.clear(screen.getByLabelText("Verification GPG key"));
await user.type(
screen.getByLabelText("Verification GPG key"),
params.gpgKey.armor,
);
await user.type(screen.getByLabelText("Verification GPG key"), "ABCDEF");

await user.click(screen.getByRole("button", { name: "Save changes" }));

expect(mockUpdateMirror).toHaveBeenCalledExactlyOnceWith(
expect.objectContaining(params),
);
expect(
await screen.findByText(
`You have successfully edited ${mirror.displayName}.`,
Comment thread
marqode marked this conversation as resolved.
),
).toBeInTheDocument();
});

it("preserves existing GPG key when checkbox is checked", async () => {
Expand Down Expand Up @@ -133,10 +113,11 @@ describe("EditMirrorForm", () => {

await user.click(screen.getByRole("button", { name: "Save changes" }));

// gpgKey should NOT be in the payload when keeping existing key
expect(mockUpdateMirror).toHaveBeenCalledExactlyOnceWith(
expect.not.objectContaining({ gpgKey: expect.anything() }),
);
expect(
await screen.findByText(
`You have successfully edited ${mirror.displayName}.`,
),
).toBeInTheDocument();
});

it("shows preserve signatures as disabled", async () => {
Expand All @@ -158,4 +139,59 @@ describe("EditMirrorForm", () => {
expect(checkbox).toBeChecked();
expect(checkbox).toBeDisabled();
});

it("shows validation error when name is empty", async () => {
const mirror = mirrors.find(
({ archiveRoot }) => new URL(archiveRoot).host === UBUNTU_ARCHIVE_HOST,
);

assert(mirror);

renderWithProviders(
<Suspense fallback={<LoadingState />}>
<TestComponent />
</Suspense>,
undefined,
`?sidePath=edit&name=${encodeURIComponent(mirror.name)}`,
);

await expectLoadingState();

await user.clear(screen.getByRole("textbox", { name: /name/i }));
await user.click(screen.getByRole("button", { name: "Save changes" }));

expect(
await screen.findByText("This field is required."),
).toBeInTheDocument();
});

it("updates download options", async () => {
const mirror = mirrors.find(
({ archiveRoot }) => new URL(archiveRoot).host === UBUNTU_ARCHIVE_HOST,
);

assert(mirror);

renderWithProviders(
<Suspense fallback={<LoadingState />}>
<TestComponent />
</Suspense>,
undefined,
`?sidePath=edit&name=${encodeURIComponent(mirror.name)}`,
);

await expectLoadingState();

await user.click(screen.getByLabelText(/download .udeb packages/i));
await user.click(screen.getByLabelText("Download sources"));
await user.click(screen.getByLabelText("Download installer files"));

await user.click(screen.getByRole("button", { name: "Save changes" }));

expect(
await screen.findByText(
`You have successfully edited ${mirror.displayName}.`,
),
).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,14 +1,94 @@
import { setEndpointStatus } from "@/tests/controllers/controller";
import { expectLoadingState } from "@/tests/helpers";
import { mirrors } from "@/tests/mocks/mirrors";
import { renderWithProviders } from "@/tests/render";
import { describe } from "vitest";
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Suspense } from "react";
import { describe, expect, it } from "vitest";
import type { Mirror } from "@canonical/landscape-openapi";
import LoadingState from "@/components/layout/LoadingState";
import MirrorActions from "./MirrorActions";

describe("MirrorActions", () => {
it("renders", () => {
renderWithProviders(
const [mirror, , mirrorWithNoPublications] = [...mirrors] as [
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we use .find to get a mirror with no publications instead just so if the mocks are changed later these tests aren't as fragile. Also [...mirrors] as [...] doesn't seem necessary

Mirror,
Mirror,
Mirror,
Mirror,
Mirror,
];

const renderActions = (m: Mirror = mirror, route?: string) =>
renderWithProviders(
<Suspense fallback={<LoadingState />}>
<MirrorActions
mirrorDisplayName="Mirror display name"
mirrorName="mirrors/name"
/>,
);
mirrorDisplayName={m.displayName ?? ""}
mirrorName={m.name ?? ""}
/>
</Suspense>,
undefined,
route,
);

const openMenu = async (user: ReturnType<typeof userEvent.setup>) => {
await user.click(screen.getByRole("button"));
};

describe("MirrorActions", () => {
const user = userEvent.setup();

it("renders the actions toggle once loaded", async () => {
renderActions();
await expectLoadingState();
expect(screen.getByRole("button")).toBeInTheDocument();
});

it("opens UpdateMirrorModal when Update is clicked", async () => {
renderActions();
await expectLoadingState();
await openMenu(user);
await user.click(await screen.findByText("Update"));
expect(
await screen.findByRole("heading", {
name: `Update ${mirror.displayName}`,
}),
).toBeInTheDocument();
});

it("opens RemoveMirrorModal when Remove is clicked", async () => {
renderActions();
await expectLoadingState();
await openMenu(user);
await user.click(await screen.findByText("Remove"));
expect(
await screen.findByRole("heading", {
name: `Remove ${mirror.displayName}`,
}),
).toBeInTheDocument();
});

it("opens NoPublicationTargetsModal when Publish is clicked with no targets or publications", async () => {
setEndpointStatus({ status: "empty", path: "publicationTargets" });
renderActions(mirrorWithNoPublications);
await expectLoadingState();
await openMenu(user);
await user.click(await screen.findByText("Publish"));
expect(
await screen.findByRole("heading", {
name: /no publication targets have been added/i,
}),
).toBeInTheDocument();
});

it("does not open NoPublicationTargetsModal when publication targets exist", async () => {
renderActions();
await expectLoadingState();
await openMenu(user);
await user.click(await screen.findByText("Publish"));
expect(
screen.queryByRole("heading", {
name: /no publication targets have been added/i,
}),
).not.toBeInTheDocument();
});
});
Loading
Loading