243 lines
7.9 KiB
React
243 lines
7.9 KiB
React
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import {
|
|
cleanup,
|
|
fireEvent,
|
|
render,
|
|
screen,
|
|
waitFor,
|
|
within,
|
|
} from "@testing-library/react";
|
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
import { TestDataProvider } from "../../test/providerCompat.jsx";
|
|
import { MemoryRouter } from "../../test/routerCompat.jsx";
|
|
import { GatewayList } from "./GatewaysPage";
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
const gateways = [
|
|
{
|
|
id: "gw-1",
|
|
name: "gw-1",
|
|
endpoint: "http://127.0.0.1:8081",
|
|
token: "unit-test-gateway-token",
|
|
},
|
|
{
|
|
id: "gw-2",
|
|
name: "gw-2",
|
|
endpoint: "http://gw2:8081",
|
|
token: "offline-token-123456",
|
|
},
|
|
];
|
|
|
|
function provider(overrides = {}) {
|
|
return {
|
|
getList: vi.fn(() =>
|
|
Promise.resolve({ data: gateways, total: gateways.length }),
|
|
),
|
|
create: vi.fn().mockResolvedValue({
|
|
data: {
|
|
name: "gw-2",
|
|
endpoint: "http://gw2:8081",
|
|
token: "generated-token-abcdef",
|
|
},
|
|
}),
|
|
deleteOne: vi.fn().mockResolvedValue({ data: { id: "gw-1" } }),
|
|
getOne: vi.fn(),
|
|
getMany: vi.fn(),
|
|
getManyReference: vi.fn(),
|
|
update: vi.fn(),
|
|
updateMany: vi.fn(),
|
|
deleteMany: vi.fn(),
|
|
browserAction: vi.fn(),
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
const renderGateways = (dataProvider) =>
|
|
render(
|
|
<QueryClientProvider
|
|
client={
|
|
new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
|
}
|
|
>
|
|
<TestDataProvider dataProvider={dataProvider}>
|
|
<MemoryRouter>
|
|
<GatewayList />
|
|
</MemoryRouter>
|
|
</TestDataProvider>
|
|
</QueryClientProvider>,
|
|
);
|
|
|
|
describe("GatewayList", () => {
|
|
it("registers a gateway and reveals the generated token", async () => {
|
|
const dataProvider = provider();
|
|
renderGateways(dataProvider);
|
|
await screen.findAllByText("gw-1");
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "注册网关" }));
|
|
const dialog = screen.getByRole("dialog");
|
|
fireEvent.change(within(dialog).getByRole("textbox", { name: "名称" }), {
|
|
target: { value: "gw-2" },
|
|
});
|
|
fireEvent.change(
|
|
within(dialog).getByRole("textbox", { name: "Endpoint" }),
|
|
{ target: { value: "http://gw2:8081" } },
|
|
);
|
|
fireEvent.click(within(dialog).getByRole("button", { name: "注册网关" }));
|
|
|
|
await waitFor(() =>
|
|
expect(dataProvider.create).toHaveBeenCalledWith({
|
|
resource: "gateways",
|
|
variables: { name: "gw-2", endpoint: "http://gw2:8081", token: "" },
|
|
}),
|
|
);
|
|
expect((await screen.findByRole("alert")).textContent).toContain(
|
|
"generated-token-abcdef",
|
|
);
|
|
await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull());
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "注册网关" }));
|
|
const reopened = screen.getByRole("dialog");
|
|
expect(within(reopened).getByRole("textbox", { name: "名称" }).value).toBe(
|
|
"",
|
|
);
|
|
expect(
|
|
within(reopened).getByRole("textbox", { name: "Endpoint" }).value,
|
|
).toBe("");
|
|
});
|
|
|
|
it("keeps the registration modal open after a failure", async () => {
|
|
const dataProvider = provider({
|
|
create: vi.fn().mockRejectedValue(new Error("网关名称已存在")),
|
|
});
|
|
renderGateways(dataProvider);
|
|
await screen.findAllByText("gw-1");
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "注册网关" }));
|
|
const dialog = screen.getByRole("dialog");
|
|
fireEvent.change(within(dialog).getByRole("textbox", { name: "名称" }), {
|
|
target: { value: "gw-2" },
|
|
});
|
|
fireEvent.change(
|
|
within(dialog).getByRole("textbox", { name: "Endpoint" }),
|
|
{ target: { value: "http://gw2:8081" } },
|
|
);
|
|
fireEvent.click(within(dialog).getByRole("button", { name: "注册网关" }));
|
|
|
|
expect((await within(dialog).findByRole("alert")).textContent).toContain(
|
|
"网关名称已存在",
|
|
);
|
|
expect(screen.getByRole("dialog")).toBeTruthy();
|
|
});
|
|
|
|
it("rejects an invalid endpoint before submit", async () => {
|
|
const dataProvider = provider();
|
|
renderGateways(dataProvider);
|
|
await screen.findAllByText("gw-1");
|
|
|
|
fireEvent.click(screen.getByRole("button", { name: "注册网关" }));
|
|
const dialog = screen.getByRole("dialog");
|
|
fireEvent.change(within(dialog).getByRole("textbox", { name: "名称" }), {
|
|
target: { value: "gw-2" },
|
|
});
|
|
fireEvent.change(
|
|
within(dialog).getByRole("textbox", { name: "Endpoint" }),
|
|
{ target: { value: "ftp://gw2:8081" } },
|
|
);
|
|
expect(
|
|
within(dialog).getByRole("button", { name: "注册网关" }).disabled,
|
|
).toBe(true);
|
|
expect(dataProvider.create).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("edits and renames a gateway", async () => {
|
|
const update = vi.fn().mockResolvedValue({ data: { id: "gw-main" } });
|
|
const dataProvider = provider({ update });
|
|
renderGateways(dataProvider);
|
|
|
|
fireEvent.click(await screen.findByLabelText("编辑 gw-1"));
|
|
const dialog = screen.getByRole("dialog");
|
|
const name = within(dialog).getByRole("textbox", { name: "名称" });
|
|
const endpoint = within(dialog).getByRole("textbox", { name: "Endpoint" });
|
|
expect(name.value).toBe("gw-1");
|
|
expect(endpoint.value).toBe("http://127.0.0.1:8081");
|
|
fireEvent.change(name, { target: { value: "gw-main" } });
|
|
fireEvent.change(endpoint, { target: { value: "http://gw-main:8081" } });
|
|
fireEvent.change(
|
|
within(dialog).getByRole("textbox", { name: "令牌(留空保持不变)" }),
|
|
{
|
|
target: { value: "rotated-gateway-token" },
|
|
},
|
|
);
|
|
fireEvent.click(within(dialog).getByRole("button", { name: "保存" }));
|
|
|
|
await waitFor(() =>
|
|
expect(update).toHaveBeenCalledWith({
|
|
resource: "gateways",
|
|
id: "gw-1",
|
|
variables: {
|
|
name: "gw-main",
|
|
endpoint: "http://gw-main:8081",
|
|
token: "rotated-gateway-token",
|
|
},
|
|
}),
|
|
);
|
|
expect(dataProvider.getList).toHaveBeenCalledTimes(2);
|
|
});
|
|
|
|
it("shows the edit failure without closing the form", async () => {
|
|
const update = vi.fn().mockRejectedValue(new Error("网关名称已存在"));
|
|
renderGateways(provider({ update }));
|
|
|
|
fireEvent.click(await screen.findByLabelText("编辑 gw-1"));
|
|
const dialog = screen.getByRole("dialog");
|
|
fireEvent.click(within(dialog).getByRole("button", { name: "保存" }));
|
|
|
|
expect((await within(dialog).findByRole("alert")).textContent).toContain(
|
|
"网关名称已存在",
|
|
);
|
|
expect(screen.getByRole("dialog")).toBeTruthy();
|
|
});
|
|
|
|
it("deletes a gateway after confirm", async () => {
|
|
const dataProvider = provider();
|
|
renderGateways(dataProvider);
|
|
|
|
fireEvent.click(await screen.findByLabelText("删除 gw-1"));
|
|
fireEvent.click(await screen.findByRole("button", { name: "确认删除" }));
|
|
|
|
await waitFor(() =>
|
|
expect(dataProvider.deleteOne).toHaveBeenCalledWith({
|
|
resource: "gateways",
|
|
id: "gw-1",
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("renders labeled actions in a right-aligned operation cell", async () => {
|
|
renderGateways(provider())
|
|
|
|
const table = await screen.findByRole("table")
|
|
expect(within(table).getByRole("columnheader", { name: "操作" }).className).toContain("text-right")
|
|
const edit = within(table).getByRole("button", { name: "编辑 gw-1" })
|
|
const remove = within(table).getByRole("button", { name: "删除 gw-1" })
|
|
expect(edit.textContent).toContain("编辑")
|
|
expect(remove.textContent).toContain("删除")
|
|
expect(edit.closest("td").className).toContain("text-right")
|
|
})
|
|
|
|
it("does not render live gateway status in the list", async () => {
|
|
renderGateways(provider());
|
|
|
|
const table = await screen.findByRole("table");
|
|
expect(within(table).queryByText("连通性")).toBeNull();
|
|
expect(within(table).queryByText("健康")).toBeNull();
|
|
expect(within(table).queryByText("在线")).toBeNull();
|
|
expect(within(table).queryByText("离线")).toBeNull();
|
|
expect(within(table).queryByText("异常")).toBeNull();
|
|
});
|
|
});
|