Files
creator-hub/web/src/NetworkExitsPage.test.jsx
T

197 lines
6.0 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 { Refine } from "@refinedev/core";
import { MemoryRouter } from "react-router";
import { NetworkExitList } from "./NetworkExitsPage";
afterEach(() => {
cleanup();
vi.restoreAllMocks();
});
const httpError = (message, status, body) =>
Object.assign(new Error(message), { status, body });
const networkExit = {
id: "exit-a",
protocol: "socks5",
host: "proxy.example",
port: 1080,
username: "proxy-user",
password: "plain-password",
health_status: "healthy",
observed_public_ip: "203.0.113.1",
};
function provider(exits = [], overrides = {}) {
return {
getList: vi.fn(({ resource }) =>
Promise.resolve(
resource === "network-exits"
? { data: exits, total: exits.length }
: { data: [], total: 0 },
),
),
create: vi.fn().mockResolvedValue({ data: networkExit }),
networkExitAction: vi.fn().mockResolvedValue(networkExit),
getOne: vi.fn(),
getMany: vi.fn(),
getManyReference: vi.fn(),
update: vi.fn(),
updateMany: vi.fn(),
delete: vi.fn(),
deleteMany: vi.fn(),
...overrides,
};
}
const renderExits = (dataProvider) =>
render(
<QueryClientProvider
client={
new QueryClient({ defaultOptions: { queries: { retry: false } } })
}
>
<Refine dataProvider={dataProvider}>
<MemoryRouter>
<NetworkExitList />
</MemoryRouter>
</Refine>
</QueryClientProvider>,
);
describe("NetworkExitList", () => {
it("shows the ordered empty-state instruction", async () => {
renderExits(provider());
expect(await screen.findByText(/创建并检测健康后/)).toBeTruthy();
});
it("shows the exit ID as the primary identifier with stored credentials", async () => {
const dataProvider = provider([networkExit]);
renderExits(dataProvider);
const idLink = await screen.findByRole("link", { name: "exit-a" });
expect(idLink.getAttribute("href")).toBe("/network-exits/exit-a");
expect(screen.getByText("socks5://proxy.example:1080")).toBeTruthy();
expect((await screen.findAllByText(/proxy-user/)).length).toBeGreaterThan(
0,
);
expect(
(await screen.findAllByText(/plain-password/)).length,
).toBeGreaterThan(0);
fireEvent.click(screen.getAllByRole("button", { name: "检测" })[0]);
await waitFor(() =>
expect(dataProvider.networkExitAction).toHaveBeenCalledWith(
"exit-a",
"check",
),
);
});
it("creates with plain credentials and closes on success", async () => {
const dataProvider = provider();
renderExits(dataProvider);
fireEvent.click(
await screen.findByRole("button", { name: "创建网络出口" }),
);
const dialog = screen.getByRole("dialog");
fireEvent.change(within(dialog).getByRole("textbox", { name: "主机" }), {
target: { value: "proxy.example" },
});
fireEvent.change(within(dialog).getByRole("spinbutton", { name: "端口" }), {
target: { value: "1080" },
});
fireEvent.change(
within(dialog).getByRole("textbox", { name: "用户名(可选)" }),
{ target: { value: "proxy-user" } },
);
fireEvent.change(
within(dialog).getByRole("textbox", { name: "密码(可选)" }),
{ target: { value: "plain-password" } },
);
fireEvent.click(
within(dialog).getByRole("button", { name: "创建网络出口" }),
);
await waitFor(() =>
expect(dataProvider.create).toHaveBeenCalledWith({
resource: "network-exits",
variables: {
protocol: "socks5",
host: "proxy.example",
port: 1080,
username: "proxy-user",
password: "plain-password",
expected_public_ip: "",
expected_region: "",
},
}),
);
await waitFor(() => expect(screen.queryByRole("dialog")).toBeNull());
});
it("keeps the create modal open after a failure", async () => {
const dataProvider = provider([], {
create: vi.fn().mockRejectedValue(new Error("创建失败")),
});
renderExits(dataProvider);
fireEvent.click(
await screen.findByRole("button", { name: "创建网络出口" }),
);
const dialog = screen.getByRole("dialog");
fireEvent.change(within(dialog).getByRole("textbox", { name: "主机" }), {
target: { value: "proxy.example" },
});
fireEvent.change(within(dialog).getByRole("spinbutton", { name: "端口" }), {
target: { value: "1080" },
});
fireEvent.click(
within(dialog).getByRole("button", { name: "创建网络出口" }),
);
expect((await within(dialog).findByRole("alert")).textContent).toContain(
"创建失败",
);
expect(screen.getByRole("dialog")).toBeTruthy();
});
it("shows unknown bindings and retries when browsers return 502", async () => {
const dataProvider = provider([networkExit], {
getList: vi.fn(({ resource }) =>
resource === "browsers"
? Promise.reject(httpError("bad gateway", 502))
: Promise.resolve({ data: [networkExit], total: 1 }),
),
});
renderExits(dataProvider);
expect(await screen.findByText(/环境不可用/)).toBeTruthy();
expect(
(await screen.findAllByText(/绑定账号:状态未知|状态未知/)).length,
).toBeGreaterThan(0);
expect(screen.queryByText(/绑定账号:未绑定/)).toBeNull();
expect(screen.queryByText(/^未绑定$/)).toBeNull();
expect(screen.getAllByRole("button", { name: "检测" })[0].disabled).toBe(
false,
);
fireEvent.click(screen.getByRole("button", { name: "重试环境状态" }));
await waitFor(() =>
expect(
dataProvider.getList.mock.calls.filter(
([call]) => call.resource === "browsers",
).length,
).toBeGreaterThan(1),
);
});
});