Use project-scoped endpoints and run IDs for current project, SCADA metadata, historical time series, migrated DMA results, and sensor placement runs. The regressions recurred because tests mocked pre-interceptor and new-only payload shapes, while history queries relied on implicit global scheme state and unbounded request fan-out. Cover post-interceptor pages, migrated result_rows, explicit run_id routing, multi-element requests, request limits, and cancellation.
107 lines
2.9 KiB
TypeScript
107 lines
2.9 KiB
TypeScript
import { render, waitFor } from "@testing-library/react";
|
|
|
|
import { ProjectProvider } from "./ProjectContext";
|
|
|
|
const mockApiFetch = jest.fn();
|
|
const mockUseSession = jest.fn();
|
|
const mockSetCurrentProjectId = jest.fn();
|
|
|
|
jest.mock("next-auth/react", () => ({
|
|
useSession: () => mockUseSession(),
|
|
}));
|
|
|
|
jest.mock("next/navigation", () => ({
|
|
usePathname: () => "/login",
|
|
useRouter: () => ({ push: jest.fn() }),
|
|
}));
|
|
|
|
jest.mock("@/components/project/ProjectSelector", () => ({
|
|
ProjectSelector: () => <div>project selector</div>,
|
|
}));
|
|
|
|
jest.mock("@/config/config", () => ({
|
|
config: {
|
|
BACKEND_URL: "http://backend.test",
|
|
MAP_EXTENT: [1, 2, 3, 4],
|
|
MAP_WORKSPACE: "default-workspace",
|
|
},
|
|
NETWORK_NAME: "default-network",
|
|
setMapExtent: jest.fn(),
|
|
setMapWorkspace: jest.fn(),
|
|
setNetworkName: jest.fn(),
|
|
}));
|
|
|
|
jest.mock("@/lib/apiFetch", () => ({
|
|
apiFetch: (...args: unknown[]) => mockApiFetch(...args),
|
|
}));
|
|
|
|
jest.mock("@/lib/permissions", () => ({
|
|
permissionCodes: { environmentManage: "environment:manage" },
|
|
}));
|
|
|
|
jest.mock("@/store/accessStore", () => ({
|
|
useAccessStore: (selector: (state: { permissions: string[] }) => unknown) =>
|
|
selector({ permissions: [] }),
|
|
}));
|
|
|
|
jest.mock("@/store/projectStore", () => ({
|
|
useProjectStore: (
|
|
selector: (state: { setCurrentProjectId: typeof mockSetCurrentProjectId }) => unknown,
|
|
) => selector({ setCurrentProjectId: mockSetCurrentProjectId }),
|
|
}));
|
|
|
|
const seedSavedProject = () => {
|
|
localStorage.setItem("MAP_WORKSPACE", "fengyang");
|
|
localStorage.setItem("NETWORK_NAME", "fengyang");
|
|
localStorage.setItem("MAP_EXTENT", "1,2,3,4");
|
|
localStorage.setItem(
|
|
"active_project",
|
|
"a2d67c84-fd9d-4feb-a500-c357244b2760",
|
|
);
|
|
};
|
|
|
|
describe("ProjectProvider authentication boundary", () => {
|
|
beforeEach(() => {
|
|
localStorage.clear();
|
|
mockApiFetch.mockReset();
|
|
mockSetCurrentProjectId.mockReset();
|
|
mockApiFetch.mockResolvedValue({
|
|
ok: true,
|
|
json: async () => ({}),
|
|
});
|
|
});
|
|
|
|
it("does not restore or open a saved project while unauthenticated", () => {
|
|
seedSavedProject();
|
|
mockUseSession.mockReturnValue({ status: "unauthenticated" });
|
|
|
|
render(
|
|
<ProjectProvider>
|
|
<div>login page</div>
|
|
</ProjectProvider>,
|
|
);
|
|
|
|
expect(mockSetCurrentProjectId).not.toHaveBeenCalled();
|
|
expect(mockApiFetch).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("restores a saved project and reads its current metadata", async () => {
|
|
seedSavedProject();
|
|
mockUseSession.mockReturnValue({ status: "authenticated" });
|
|
|
|
render(
|
|
<ProjectProvider>
|
|
<div>main page</div>
|
|
</ProjectProvider>,
|
|
);
|
|
|
|
await waitFor(() => expect(mockApiFetch).toHaveBeenCalledTimes(1));
|
|
expect(mockApiFetch).toHaveBeenCalledWith(
|
|
"http://backend.test/api/v1/projects/current",
|
|
);
|
|
expect(mockSetCurrentProjectId).toHaveBeenCalledWith(
|
|
"a2d67c84-fd9d-4feb-a500-c357244b2760",
|
|
);
|
|
});
|
|
});
|