// @vitest-environment jsdom import { describe, test, expect, vi, beforeEach, afterEach } from "vitest"; import { render, screen, waitFor, fireEvent, cleanup } from "@testing-library/react"; import React from "react"; import { MemoryRouter, Route, Routes } from "react-router-dom"; // ── Shared mock state (vi.hoisted avoids TDZ with vi.mock hoisting) ──────────── const H = vi.hoisted(() => ({ mockNavigate: vi.fn(), mockGetNetwork: vi.fn(), mockGetNetworkMembers: vi.fn(), mockActivateMembership: vi.fn(), mockDeactivateMembership: vi.fn(), mockGetNetworkPendingRequests: vi.fn(), mockApproveRequest: vi.fn(), mockRejectRequest: vi.fn(), mockToast: vi.fn(), state: { orgId: "org-1" as string | null, networkId: "net-abc" as string | undefined, }, })); vi.mock("react-router-dom", async () => { const actual = await vi.importActual("react-router-dom"); return { ...actual, useNavigate: () => H.mockNavigate, useParams: () => ({ networkId: H.state.networkId }), }; }); vi.mock("@/hooks/useCurrentOrganization", () => ({ useCurrentOrganizationId: () => ({ orgId: H.state.orgId, isLoading: false, }), })); vi.mock("@/lib/api", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, api: { zerotier: { getNetwork: H.mockGetNetwork, getNetworkMembers: H.mockGetNetworkMembers, activateMembership: H.mockActivateMembership, deactivateMembership: H.mockDeactivateMembership, getNetworkPendingRequests: H.mockGetNetworkPendingRequests, approveRequest: H.mockApproveRequest, rejectRequest: H.mockRejectRequest, }, }, }; }); vi.mock("@/hooks/use-toast", () => ({ useToast: () => ({ toast: H.mockToast, dismiss: () => {}, toasts: [], }), })); import NetworkManagementPage from "../src/pages/org/NetworkManagementPage"; import { ApiError } from "@/lib/api"; // ── Helpers ──────────────────────────────────────────────────────────────────── const DEV_NETWORK = { id: "net-abc", organization_id: "org-1", name: "Dev Network", description: "Internal dev", owner_user_id: "user-1", zerotier_network_id: "zt-xxx", environment: "development" as const, request_mode: "open" as const, default_activation_lifetime_minutes: 1440, max_activation_lifetime_minutes: null as number | null, is_active: true, created_at: "2025-01-01T00:00:00Z", updated_at: "2025-01-01T00:00:00Z", deleted_at: null as string | null, approved_user_count: 42, active_membership_count: 7, }; function renderWithRoute(networkId = "net-abc") { return render( React.createElement( MemoryRouter, { initialEntries: [`/org/zerotier/networks/${networkId}`] }, React.createElement(Routes, null, React.createElement(Route, { path: "/org/zerotier/networks/:networkId", element: React.createElement(NetworkManagementPage), }) ) ) ); } // ── Setup / Teardown ─────────────────────────────────────────────────────────── beforeEach(() => { vi.clearAllMocks(); H.state.orgId = "org-1"; H.state.networkId = "net-abc"; // Default: getNetwork returns a pending promise (loading state) H.mockGetNetwork.mockImplementation(() => new Promise(() => {})); // Default: getNetworkPendingRequests returns a pending promise (loading state) H.mockGetNetworkPendingRequests.mockImplementation(() => new Promise(() => {})); }); afterEach(() => { cleanup(); vi.restoreAllMocks(); }); // ── Tests ────────────────────────────────────────────────────────────────────── describe("NetworkManagementPage", () => { // ── HAPPY PATH: Loading → Success ──────────────────────────────────────────── test("renders skeleton placeholders during loading state", () => { renderWithRoute(); const container = document.querySelector(".page-container"); expect(container).not.toBeNull(); const skeletons = document.querySelectorAll(".animate-pulse"); expect(skeletons.length).toBeGreaterThanOrEqual(3); }); test("renders success state with network name and tabs when API resolves", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); expect(screen.getByText("Back to Networks")).toBeDefined(); expect(screen.getByRole("tab", { name: "Overview" })).toBeDefined(); expect(screen.getByRole("tab", { name: "Members" })).toBeDefined(); expect(screen.getByRole("tab", { name: "Requests" })).toBeDefined(); expect( screen.getByText("Manage network members, devices, and access requests") ).toBeDefined(); }); test("renders fallback 'Network' when name is null", async () => { H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, name: null as unknown as string }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Network")).toBeDefined(); }); }); // ── ERROR PATH ─────────────────────────────────────────────────────────────── test("renders generic error when API throws a plain Error", async () => { H.mockGetNetwork.mockRejectedValue(new Error("Network unreachable")); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Failed to load network details.")).toBeDefined(); }); expect(screen.getByText("Back to Networks")).toBeDefined(); }); test("renders specific error message when API throws ApiError", async () => { const apiError = new ApiError("Network not found in the controller", 404, "not_found"); H.mockGetNetwork.mockRejectedValue(apiError); renderWithRoute(); await waitFor(() => { expect( screen.getByText("Network not found in the controller") ).toBeDefined(); }); }); test("renders error when orgId is missing", async () => { H.state.orgId = null; renderWithRoute(); await waitFor(() => { expect( screen.getByText("Organization or network ID is missing.") ).toBeDefined(); }); expect(H.mockGetNetwork).not.toHaveBeenCalled(); }); test("renders error when networkId is missing from URL", async () => { H.state.networkId = undefined; render( React.createElement( MemoryRouter, { initialEntries: ["/org/zerotier/networks/"] }, React.createElement(Routes, null, React.createElement(Route, { path: "/org/zerotier/networks/", element: React.createElement(NetworkManagementPage), }) ) ) ); await waitFor(() => { expect( screen.getByText("Organization or network ID is missing.") ).toBeDefined(); }); expect(H.mockGetNetwork).not.toHaveBeenCalled(); }); // ── NAVIGATION ─────────────────────────────────────────────────────────────── test("Back to Networks in success state navigates to /org/zerotier/networks", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); fireEvent.click(screen.getByText("Back to Networks")); expect(H.mockNavigate).toHaveBeenCalledTimes(1); expect(H.mockNavigate).toHaveBeenCalledWith("/org/zerotier/networks"); }); test("Back to Networks in error state navigates to /org/zerotier/networks", async () => { H.mockGetNetwork.mockRejectedValue(new Error("fail")); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Failed to load network details.")).toBeDefined(); }); fireEvent.click(screen.getByText("Back to Networks")); expect(H.mockNavigate).toHaveBeenCalledTimes(1); expect(H.mockNavigate).toHaveBeenCalledWith("/org/zerotier/networks"); }); // ── BOUNDARY / STATE ───────────────────────────────────────────────────────── test("stops showing loading skeletons after API resolves", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); renderWithRoute(); expect(document.querySelectorAll(".animate-pulse").length).toBeGreaterThan(0); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); expect(document.querySelectorAll(".animate-pulse").length).toBe(0); }); test("stops showing loading skeletons after API rejects", async () => { H.mockGetNetwork.mockRejectedValue(new Error("fail")); renderWithRoute(); expect(document.querySelectorAll(".animate-pulse").length).toBeGreaterThan(0); await waitFor(() => { expect(screen.getByText("Failed to load network details.")).toBeDefined(); }); expect(document.querySelectorAll(".animate-pulse").length).toBe(0); }); test("passes correct orgId and networkId to getNetwork", async () => { H.state.orgId = "org-2"; H.state.networkId = "net-specific"; H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, id: "net-specific", organization_id: "org-2", name: "Specific Network", }, }); renderWithRoute("net-specific"); await waitFor(() => { expect(screen.getByText("Specific Network")).toBeDefined(); }); expect(H.mockGetNetwork).toHaveBeenCalledTimes(1); expect(H.mockGetNetwork).toHaveBeenCalledWith("org-2", "net-specific"); }); test("renders exactly 3 tabs", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const tabs = screen.getAllByRole("tab"); expect(tabs.length).toBe(3); }); test("Overview tab is selected by default", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const overviewTab = screen.getByRole("tab", { name: "Overview" }); expect(overviewTab.getAttribute("data-state")).toBe("active"); }); // ── ADVERSARIAL ────────────────────────────────────────────────────────────── test("handles very long network name (500 chars)", async () => { const longName = "A".repeat(500); H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, name: longName }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText(longName)).toBeDefined(); }); expect(screen.getByText("Back to Networks")).toBeDefined(); expect(screen.getByRole("tab", { name: "Overview" })).toBeDefined(); }); test("handles network name with script tags (XSS-safe rendering)", async () => { const specialName = "Dev/Network & Co."; H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, name: specialName }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText(specialName)).toBeDefined(); }); expect(screen.getByText(specialName).textContent).toBe(specialName); }); // ── OVERVIEW TAB CONTENT ────────────────────────────────────────────────────── describe("Overview tab content", () => { beforeEach(() => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); }); test("renders environment badge with correct color classes", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("Development")).toBeDefined(); }); const badge = screen.getByText("Development"); expect(badge.className).toContain("bg-green-500/10"); expect(badge.className).toContain("text-green-600"); }); test("renders request mode badge for 'open' mode", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("Open")).toBeDefined(); }); const badge = screen.getByText("Open"); expect(badge.className).toContain("text-green-600"); }); test("renders request mode badge for 'approval_required' mode", async () => { H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, request_mode: "approval_required" }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Approval Required")).toBeDefined(); }); }); test("renders request mode badge for 'invite_only' mode", async () => { H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, request_mode: "invite_only" }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Invite Only")).toBeDefined(); }); }); test("renders inactive badge when is_active is false", async () => { H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, is_active: false }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Inactive")).toBeDefined(); }); }); test("does NOT render inactive badge when is_active is true", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("Development")).toBeDefined(); }); expect(screen.queryByText("Inactive")).toBeNull(); }); test("renders description when present", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("Internal dev")).toBeDefined(); }); }); test("does NOT render description section when null", async () => { H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, description: null }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Development")).toBeDefined(); }); expect(screen.queryByText("Internal dev")).toBeNull(); }); test("renders ZeroTier Network ID", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("zt-xxx")).toBeDefined(); }); }); test("renders default activation lifetime", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("1440 min")).toBeDefined(); }); }); test("renders 'No limit' for null max activation lifetime", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("No limit")).toBeDefined(); }); }); test("renders numeric max activation lifetime when set", async () => { H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, max_activation_lifetime_minutes: 2880 }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("2880 min")).toBeDefined(); }); }); test("renders approved user count stat card", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("Approved Users")).toBeDefined(); }); expect(screen.getByText("42")).toBeDefined(); }); test("renders active device count stat card", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("Active Devices")).toBeDefined(); }); expect(screen.getByText("7")).toBeDefined(); }); test("renders approved user count as 0 when undefined", async () => { const { approved_user_count, ...withoutApproved } = DEV_NETWORK as any; H.mockGetNetwork.mockResolvedValue({ network: withoutApproved, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("0")).toBeDefined(); }); }); test("renders active device count as 0 when undefined", async () => { const { active_membership_count, ...withoutActive } = DEV_NETWORK as any; H.mockGetNetwork.mockResolvedValue({ network: withoutActive, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Active Devices")).toBeDefined(); }); }); test("renders '—' for null created_at date", async () => { H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, created_at: null as unknown as string }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("—")).toBeDefined(); }); }); test("renders created_at as formatted date", async () => { H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, created_at: "2025-06-15T10:30:00Z" }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Development")).toBeDefined(); }); // Just verify it's not "—" (exact date format depends on locale) const createdLabel = screen.getByText("Created"); expect(createdLabel).toBeDefined(); // The value is rendered in a sibling element; verify it exists and is not em-dash const createdSection = createdLabel.closest("div"); expect(createdSection).not.toBeNull(); const valueElement = createdSection!.querySelector(".font-medium"); expect(valueElement).not.toBeNull(); expect(valueElement!.textContent).not.toBe("—"); }); test("renders request mode stat card with human-readable mode", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("Request Mode")).toBeDefined(); }); // "open" → "open" → CSS capitalize → "Open" expect(screen.getByText("open")).toBeDefined(); }); test("renders all overview metadata labels", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("Development")).toBeDefined(); }); expect(screen.getByText("ZeroTier Network ID")).toBeDefined(); expect(screen.getByText("Default Activation")).toBeDefined(); expect(screen.getByText("Max Activation")).toBeDefined(); expect(screen.getByText("Created")).toBeDefined(); }); test("renders Network Details card title", async () => { renderWithRoute(); await waitFor(() => { expect(screen.getByText("Network Details")).toBeDefined(); }); }); }); // ── OVERVIEW TAB ENVIRONMENT BADGE VARIANTS ─────────────────────────────────── describe("Environment badge variants", () => { test.each([ ["production", "Production", "bg-red-500/10", "text-red-600"], ["staging", "Staging", "bg-yellow-500/10", "text-yellow-600"], ["development", "Development", "bg-green-500/10", "text-green-600"], ["lab", "Lab", "bg-blue-500/10", "text-blue-600"], ])("renders %s environment with label '%s' and correct colors", async (env, label, expectedBg, expectedText) => { H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, environment: env as any }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText(label)).toBeDefined(); }); const badge = screen.getByText(label); expect(badge.className).toContain(expectedBg); expect(badge.className).toContain(expectedText); }); }); // ── OVERVIEW TAB NULL/UNDEFINED HANDLING ────────────────────────────────────── describe("Overview tab null edge cases", () => { test("shows '—' for null created_at", async () => { H.mockGetNetwork.mockResolvedValue({ network: { ...DEV_NETWORK, created_at: null as unknown as string }, }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("—")).toBeDefined(); }); }); test("shows '—' for undefined created_at", async () => { const { created_at, ...rest } = DEV_NETWORK; H.mockGetNetwork.mockResolvedValue({ network: rest as any }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("—")).toBeDefined(); }); }); test("shows 0 for undefined approved_user_count", async () => { const { approved_user_count, ...rest } = DEV_NETWORK; H.mockGetNetwork.mockResolvedValue({ network: rest as any }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Approved Users")).toBeDefined(); }); // The count displays as 0 via nullish coalescing: approved_user_count ?? 0 }); test("shows 0 for undefined active_membership_count", async () => { const { active_membership_count, ...rest } = DEV_NETWORK; H.mockGetNetwork.mockResolvedValue({ network: rest as any }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Active Devices")).toBeDefined(); }); }); }); // ── MEMBERS TAB ────────────────────────────────────────────────────────────── describe("Members tab", () => { // ── Fixtures ──────────────────────────────────────────────────────────────── const MEMBERSHIP_AUTHORIZED_WITH_SESSION = { id: "mem-auth-1", organization_id: "org-1", user_id: "user-a", user_name: "Alice Smith", user_email: "alice@test.com", device_id: "dev-laptop-1", device_name: "Alice Laptop", device_node_id: "abc123", portal_network_id: "net-abc", user_network_approval_id: null, active: true, status: "approved", grant_type: "assigned", granted_by_user_id: null, justification: null, join_seen: true, created_at: "2025-01-01T00:00:00Z", updated_at: "2025-01-01T00:00:00Z", deleted_at: null, active_session: { id: "session-1", organization_id: "org-1", user_id: "user-a", device_network_membership_id: "mem-auth-1", authenticated_at: "2025-01-01T00:00:00Z", expires_at: "2025-12-31T23:59:59Z", ended_at: null, end_reason: null, created_by: "user-a", created_at: "2025-01-01T00:00:00Z", updated_at: "2025-01-01T00:00:00Z", deleted_at: null, is_expired: false, is_active: true, }, }; const MEMBERSHIP_UNAUTHORIZED_NO_SESSION = { id: "mem-unauth-1", organization_id: "org-1", user_id: "user-a", user_name: "Alice Smith", user_email: "alice@test.com", device_id: "dev-desktop-1", device_name: "Alice Desktop", device_node_id: "def456", portal_network_id: "net-abc", user_network_approval_id: null, active: false, status: "approved", grant_type: "assigned", granted_by_user_id: null, justification: null, join_seen: false, created_at: "2025-02-01T00:00:00Z", updated_at: "2025-02-01T00:00:00Z", deleted_at: null, active_session: null, }; const MEMBERSHIP_SECOND_USER = { id: "mem-auth-2", organization_id: "org-1", user_id: "user-b", user_name: "Bob Jones", user_email: "bob@test.com", device_id: "dev-phone-1", device_name: "Bob Phone", device_node_id: "ghi789", portal_network_id: "net-abc", user_network_approval_id: null, active: false, status: "approved", grant_type: "assigned", granted_by_user_id: null, justification: null, join_seen: true, created_at: "2025-03-01T00:00:00Z", updated_at: "2025-03-01T00:00:00Z", deleted_at: null, active_session: null, }; const MEMBERSHIP_PENDING_REQUEST = { id: "mem-pending-1", organization_id: "org-1", user_id: "user-c", user_name: "Charlie Brown", user_email: "charlie@test.com", device_id: "dev-server-1", device_name: "Charlie Server", device_node_id: "jkl012", portal_network_id: "net-abc", user_network_approval_id: null, active: false, status: "pending", grant_type: "requested", granted_by_user_id: null, justification: null, join_seen: false, created_at: "2025-04-01T00:00:00Z", updated_at: "2025-04-01T00:00:00Z", deleted_at: null, active_session: null, }; // Helper: setup resolved network, then click Members tab async function setupMembersTab(memberships: Array> = [MEMBERSHIP_AUTHORIZED_WITH_SESSION]) { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkMembers.mockResolvedValue({ memberships, count: memberships.length }); renderWithRoute(); // Wait for the page to render (network resolves first) await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); // Activate Members tab via keyboard (Radix uses focus + Enter) const membersTab = screen.getByRole("tab", { name: "Members" }); membersTab.focus(); fireEvent.keyDown(membersTab, { key: "Enter" }); // Wait for members content await waitFor(() => { expect(screen.getByText("Network Members")).toBeDefined(); }); } // ── Loading / Error / Empty ───────────────────────────────────────────────── test("renders loading spinner in Members tab while fetching members", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); // getNetworkMembers stays pending (never resolves) H.mockGetNetworkMembers.mockImplementation(() => new Promise(() => {})); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const membersTab = screen.getByRole("tab", { name: "Members" }); membersTab.focus(); fireEvent.keyDown(membersTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("Network Members")).toBeDefined(); }); expect(screen.getByText("Loading members…")).toBeDefined(); }); test("renders error message when members API fails", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkMembers.mockRejectedValue(new Error("API unavailable")); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const membersTab = screen.getByRole("tab", { name: "Members" }); membersTab.focus(); fireEvent.keyDown(membersTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("Failed to load members.")).toBeDefined(); }); }); test("renders ApiError message when members API throws ApiError", async () => { const apiError = new ApiError("Membership lookup failed", 500, "internal_error"); H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkMembers.mockRejectedValue(apiError); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const membersTab = screen.getByRole("tab", { name: "Members" }); membersTab.focus(); fireEvent.keyDown(membersTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("Membership lookup failed")).toBeDefined(); }); }); test("renders 'No members' message when memberships array is empty", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkMembers.mockResolvedValue({ memberships: [], count: 0 }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const membersTab = screen.getByRole("tab", { name: "Members" }); membersTab.focus(); fireEvent.keyDown(membersTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("No members on this network yet.")).toBeDefined(); }); }); test("renders 'No members' when memberships field is undefined", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkMembers.mockResolvedValue({ count: 0 }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const membersTab = screen.getByRole("tab", { name: "Members" }); membersTab.focus(); fireEvent.keyDown(membersTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("No members on this network yet.")).toBeDefined(); }); }); // ── Grouping by user_id ───────────────────────────────────────────────────── test("groups memberships by user_id and renders user sections", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION, MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); // Both memberships belong to Alice Smith → one user section expect(screen.getByText("Alice Smith")).toBeDefined(); // Should show "2 devices" expect(screen.getByText(/2 devices/)).toBeDefined(); }); test("renders multiple user sections for different user_ids", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION, MEMBERSHIP_SECOND_USER]); expect(screen.getByText("Alice Smith")).toBeDefined(); expect(screen.getByText("Bob Jones")).toBeDefined(); }); test("displays user_name when available", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION]); expect(screen.getByText("Alice Smith")).toBeDefined(); }); // ── Expand / Collapse ─────────────────────────────────────────────────────── test("clicking user section expands to show devices", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION, MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); // Devices should NOT be visible before expanding expect(screen.queryByText("Alice Laptop")).toBeNull(); expect(screen.queryByText("Alice Desktop")).toBeNull(); // Click the user section to expand const userButton = screen.getByText("Alice Smith").closest("button"); expect(userButton).not.toBeNull(); fireEvent.click(userButton!); // Now devices should be visible await waitFor(() => { expect(screen.getByText("Alice Laptop")).toBeDefined(); }); expect(screen.getByText("Alice Desktop")).toBeDefined(); }); test("clicking expanded user section collapses devices", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); expect(userButton).not.toBeNull(); fireEvent.click(userButton!); // Devices should be visible await waitFor(() => { expect(screen.getByText("Alice Laptop")).toBeDefined(); }); // Click again to collapse fireEvent.click(userButton!); // Devices should disappear await waitFor(() => { expect(screen.queryByText("Alice Laptop")).toBeNull(); }); }); test("expand/collapse is independent per user section", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION, MEMBERSHIP_SECOND_USER]); const userAButton = screen.getByText("Alice Smith").closest("button")!; const userBButton = screen.getByText("Bob Jones").closest("button")!; // Expand Alice Smith only fireEvent.click(userAButton); await waitFor(() => { expect(screen.getByText("Alice Laptop")).toBeDefined(); }); // Bob Jones' device should still be hidden expect(screen.queryByText("Bob Phone")).toBeNull(); // Expand Bob Jones too fireEvent.click(userBButton); await waitFor(() => { expect(screen.getByText("Bob Phone")).toBeDefined(); }); // Collapse Alice Smith — Bob Jones should remain expanded fireEvent.click(userAButton); await waitFor(() => { expect(screen.queryByText("Alice Laptop")).toBeNull(); }); expect(screen.getByText("Bob Phone")).toBeDefined(); }); // ── Device Details ────────────────────────────────────────────────────────── test("renders Active badge for active_authorized membership", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); await waitFor(() => { expect(screen.getByText("Active")).toBeDefined(); }); }); test("renders Inactive badge for approved_inactive membership", async () => { await setupMembersTab([MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); await waitFor(() => { expect(screen.getByText("Inactive")).toBeDefined(); }); }); test("renders Inactive badge for pending_request membership", async () => { await setupMembersTab([MEMBERSHIP_PENDING_REQUEST]); const userButton = screen.getByText("Charlie Brown").closest("button"); fireEvent.click(userButton!); await waitFor(() => { expect(screen.getByText("Inactive")).toBeDefined(); }); }); test("renders Inactive badge for second user (approved_inactive)", async () => { await setupMembersTab([MEMBERSHIP_SECOND_USER]); const userButton = screen.getByText("Bob Jones").closest("button"); fireEvent.click(userButton!); await waitFor(() => { expect(screen.getByText("Inactive")).toBeDefined(); }); }); // ── Authorization Status ──────────────────────────────────────────────────── test("shows 'Active' badge when membership is active", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); await waitFor(() => { const actives = screen.getAllByText("Active"); expect(actives.length).toBeGreaterThanOrEqual(1); }); }); test("shows 'Inactive' badge when membership is not active", async () => { await setupMembersTab([MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); await waitFor(() => { expect(screen.getByText("Inactive")).toBeDefined(); }); }); // ── Active Session Info ───────────────────────────────────────────────────── test("shows session progress bar when active_session is present and is_active", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); await waitFor(() => { expect(screen.getByText(/remaining/)).toBeDefined(); }); }); test("does NOT show session progress when active_session is null", async () => { await setupMembersTab([MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); await waitFor(() => { expect(screen.getByText("Inactive")).toBeDefined(); }); expect(screen.queryByText(/remaining/)).toBeNull(); }); // ── Join Seen ─────────────────────────────────────────────────────────────── test("shows join_seen as 'Yes' when true", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); await waitFor(() => { expect(screen.getByText("Joined: Yes")).toBeDefined(); }); }); test("shows join_seen as 'No' when false", async () => { await setupMembersTab([MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); await waitFor(() => { expect(screen.getByText("Joined: No")).toBeDefined(); }); }); // ── Activate / Deactivate Buttons ─────────────────────────────────────────── test("renders Deactivate button for active memberships", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); await waitFor(() => { expect(screen.getByRole("button", { name: "Deactivate" })).toBeDefined(); }); expect(screen.queryByRole("button", { name: "Activate" })).toBeNull(); }); test("renders Activate button for approved-but-inactive memberships", async () => { await setupMembersTab([MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); await waitFor(() => { expect(screen.getByRole("button", { name: "Activate" })).toBeDefined(); }); expect(screen.queryByRole("button", { name: "Deactivate" })).toBeNull(); }); test("clicking Activate opens dialog then calls api.zerotier.activateMembership with correct orgId, membershipId, and lifetime", async () => { H.mockActivateMembership.mockResolvedValue({}); // Second call to getNetworkMembers (refresh after activate) H.mockGetNetworkMembers .mockResolvedValueOnce({ memberships: [MEMBERSHIP_UNAUTHORIZED_NO_SESSION], count: 1 }) .mockResolvedValueOnce({ memberships: [MEMBERSHIP_AUTHORIZED_WITH_SESSION], count: 1 }); await setupMembersTab([MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); const activateBtn = await screen.findByRole("button", { name: "Activate" }); fireEvent.click(activateBtn); // Dialog opens; click the Activate button in the dialog await waitFor(() => { expect(screen.getByText("Set Activation Duration")).toBeDefined(); }); const dialogBtns = screen.getAllByRole("button", { name: "Activate" }); fireEvent.click(dialogBtns[dialogBtns.length - 1]); expect(H.mockActivateMembership).toHaveBeenCalledTimes(1); expect(H.mockActivateMembership).toHaveBeenCalledWith("org-1", "mem-unauth-1", 480); }); test("clicking Deactivate calls api.zerotier.deactivateMembership with correct orgId and membershipId", async () => { H.mockDeactivateMembership.mockResolvedValue({}); // Second call to getNetworkMembers (refresh after deactivate) H.mockGetNetworkMembers .mockResolvedValueOnce({ memberships: [MEMBERSHIP_AUTHORIZED_WITH_SESSION], count: 1 }) .mockResolvedValueOnce({ memberships: [MEMBERSHIP_UNAUTHORIZED_NO_SESSION], count: 1 }); await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); const deactivateBtn = await screen.findByRole("button", { name: "Deactivate" }); fireEvent.click(deactivateBtn); expect(H.mockDeactivateMembership).toHaveBeenCalledTimes(1); expect(H.mockDeactivateMembership).toHaveBeenCalledWith("org-1", "mem-auth-1"); }); test("shows success toast after successful activation", async () => { H.mockActivateMembership.mockResolvedValue({}); H.mockGetNetworkMembers .mockResolvedValueOnce({ memberships: [MEMBERSHIP_UNAUTHORIZED_NO_SESSION], count: 1 }) .mockResolvedValueOnce({ memberships: [MEMBERSHIP_AUTHORIZED_WITH_SESSION], count: 1 }); await setupMembersTab([MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); const activateBtn = await screen.findByRole("button", { name: "Activate" }); fireEvent.click(activateBtn); // Confirm in dialog await waitFor(() => { expect(screen.getByText("Set Activation Duration")).toBeDefined(); }); const dialogBtns = screen.getAllByRole("button", { name: "Activate" }); fireEvent.click(dialogBtns[dialogBtns.length - 1]); await waitFor(() => { expect(H.mockToast).toHaveBeenCalledWith(expect.objectContaining({ title: "Membership activated" })); }); }); test("shows success toast after successful deactivation", async () => { H.mockDeactivateMembership.mockResolvedValue({}); H.mockGetNetworkMembers .mockResolvedValueOnce({ memberships: [MEMBERSHIP_AUTHORIZED_WITH_SESSION], count: 1 }) .mockResolvedValueOnce({ memberships: [MEMBERSHIP_UNAUTHORIZED_NO_SESSION], count: 1 }); await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); const deactivateBtn = await screen.findByRole("button", { name: "Deactivate" }); fireEvent.click(deactivateBtn); await waitFor(() => { expect(H.mockToast).toHaveBeenCalledWith(expect.objectContaining({ title: "Membership deactivated" })); }); }); test("shows error toast when activation fails", async () => { H.mockActivateMembership.mockRejectedValue(new Error("Activation failed")); H.mockGetNetworkMembers.mockResolvedValue({ memberships: [MEMBERSHIP_UNAUTHORIZED_NO_SESSION], count: 1 }); await setupMembersTab([MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); const activateBtn = await screen.findByRole("button", { name: "Activate" }); fireEvent.click(activateBtn); // Confirm in dialog await waitFor(() => { expect(screen.getByText("Set Activation Duration")).toBeDefined(); }); const dialogBtns = screen.getAllByRole("button", { name: "Activate" }); fireEvent.click(dialogBtns[dialogBtns.length - 1]); await waitFor(() => { expect(H.mockToast).toHaveBeenCalledWith(expect.objectContaining({ variant: "destructive", title: "Failed to activate", })); }); }); test("shows error toast when deactivation fails", async () => { H.mockDeactivateMembership.mockRejectedValue(new Error("Deactivation failed")); H.mockGetNetworkMembers.mockResolvedValue({ memberships: [MEMBERSHIP_AUTHORIZED_WITH_SESSION], count: 1 }); await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); const deactivateBtn = await screen.findByRole("button", { name: "Deactivate" }); fireEvent.click(deactivateBtn); await waitFor(() => { expect(H.mockToast).toHaveBeenCalledWith(expect.objectContaining({ variant: "destructive", title: "Failed to deactivate", })); }); }); test("shows ApiError description in error toast when activation fails with ApiError", async () => { const apiError = new ApiError("Member not found", 404, "not_found"); H.mockActivateMembership.mockRejectedValue(apiError); H.mockGetNetworkMembers.mockResolvedValue({ memberships: [MEMBERSHIP_UNAUTHORIZED_NO_SESSION], count: 1 }); await setupMembersTab([MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); const activateBtn = await screen.findByRole("button", { name: "Activate" }); fireEvent.click(activateBtn); // Confirm in dialog await waitFor(() => { expect(screen.getByText("Set Activation Duration")).toBeDefined(); }); const dialogBtns = screen.getAllByRole("button", { name: "Activate" }); fireEvent.click(dialogBtns[dialogBtns.length - 1]); await waitFor(() => { expect(H.mockToast).toHaveBeenCalledWith(expect.objectContaining({ variant: "destructive", title: "Failed to activate", description: "Member not found", })); }); }); test("refresh after activate fetches updated members", async () => { H.mockActivateMembership.mockResolvedValue({}); const updatedMembership = { ...MEMBERSHIP_UNAUTHORIZED_NO_SESSION, active: true, status: "approved", }; H.mockGetNetworkMembers .mockResolvedValueOnce({ memberships: [MEMBERSHIP_UNAUTHORIZED_NO_SESSION], count: 1 }) .mockResolvedValueOnce({ memberships: [updatedMembership], count: 1 }); await setupMembersTab([MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); const activateBtn = await screen.findByRole("button", { name: "Activate" }); fireEvent.click(activateBtn); // Confirm in dialog await waitFor(() => { expect(screen.getByText("Set Activation Duration")).toBeDefined(); }); const dialogBtns = screen.getAllByRole("button", { name: "Activate" }); fireEvent.click(dialogBtns[dialogBtns.length - 1]); // Verify getNetworkMembers was called twice: once on mount, once on refresh await waitFor(() => { expect(H.mockGetNetworkMembers).toHaveBeenCalledTimes(2); }); expect(H.mockGetNetworkMembers).toHaveBeenNthCalledWith(1, "org-1", "net-abc"); expect(H.mockGetNetworkMembers).toHaveBeenNthCalledWith(2, "org-1", "net-abc"); }); test("refresh after deactivate fetches updated members", async () => { H.mockDeactivateMembership.mockResolvedValue({}); const updatedMembership = { ...MEMBERSHIP_AUTHORIZED_WITH_SESSION, active: false, status: "approved", }; H.mockGetNetworkMembers .mockResolvedValueOnce({ memberships: [MEMBERSHIP_AUTHORIZED_WITH_SESSION], count: 1 }) .mockResolvedValueOnce({ memberships: [updatedMembership], count: 1 }); await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION]); const userButton = screen.getByText("Alice Smith").closest("button"); fireEvent.click(userButton!); const deactivateBtn = await screen.findByRole("button", { name: "Deactivate" }); fireEvent.click(deactivateBtn); await waitFor(() => { expect(H.mockGetNetworkMembers).toHaveBeenCalledTimes(2); }); }); // ── Members badge count ───────────────────────────────────────────────────── test("shows membership count in the Members tab badge", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION, MEMBERSHIP_UNAUTHORIZED_NO_SESSION, MEMBERSHIP_SECOND_USER]); expect(screen.getByText("3 memberships")).toBeDefined(); }); test("shows '0 memberships' when empty", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkMembers.mockResolvedValue({ memberships: [], count: 0 }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const membersTab = screen.getByRole("tab", { name: "Members" }); membersTab.focus(); fireEvent.keyDown(membersTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("0 memberships")).toBeDefined(); }); }); // ── Active device count in user section ───────────────────────────────────── test("shows active device count in user section header", async () => { await setupMembersTab([MEMBERSHIP_AUTHORIZED_WITH_SESSION, MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); // 1 active + 1 inactive → "1 active" expect(screen.getByText(/^1 active$/)).toBeDefined(); }); test("does not show active count when no devices are authorized", async () => { await setupMembersTab([MEMBERSHIP_UNAUTHORIZED_NO_SESSION]); // 0 active devices → no "active" count in user section expect(screen.queryByText(/^\d+ active$/)).toBeNull(); }); // ── Adversarial: Unicode user_ids ─────────────────────────────────────────── test("renders users with Unicode user_ids", async () => { const unicodeMembership = { ...MEMBERSHIP_AUTHORIZED_WITH_SESSION, id: "mem-uni-1", user_id: "user-äéîøü-中文", user_name: "Unicode User", device_id: "dev-utf8", device_name: null, }; H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkMembers.mockResolvedValue({ memberships: [unicodeMembership], count: 1 }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const membersTab = screen.getByRole("tab", { name: "Members" }); membersTab.focus(); fireEvent.keyDown(membersTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("Unicode User")).toBeDefined(); }); }); // ── Adversarial: device_id with special characters ────────────────────────── test("renders device_id with HTML-like content safely", async () => { const xssMembership = { ...MEMBERSHIP_AUTHORIZED_WITH_SESSION, id: "mem-xss-1", user_id: "user-xss", user_name: null, device_id: "", device_name: null, }; H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkMembers.mockResolvedValue({ memberships: [xssMembership], count: 1 }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const membersTab = screen.getByRole("tab", { name: "Members" }); membersTab.focus(); fireEvent.keyDown(membersTab, { key: "Enter" }); // User section should be visible const userButton = screen.getByText("user-xss").closest("button"); fireEvent.click(userButton!); await waitFor(() => { // The device_id should be rendered as text, not executed as HTML expect(screen.getByText("")).toBeDefined(); }); }); // ── Adversarial: large membership list ────────────────────────────────────── test("renders many user sections without crashing", async () => { const manyMemberships = Array.from({ length: 50 }, (_, i) => ({ ...MEMBERSHIP_AUTHORIZED_WITH_SESSION, id: `mem-many-${i}`, user_id: `user-${i}`, user_name: `User ${i}`, user_email: `user${i}@test.com`, device_id: `dev-${i}`, device_name: `Device ${i}`, active_session: null, })); H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkMembers.mockResolvedValue({ memberships: manyMemberships, count: 50 }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const membersTab = screen.getByRole("tab", { name: "Members" }); membersTab.focus(); fireEvent.keyDown(membersTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("50 memberships")).toBeDefined(); }); // Should find at least the first and last user expect(screen.getByText("User 0")).toBeDefined(); expect(screen.getByText("User 49")).toBeDefined(); }); }); // ── REQUESTS TAB ───────────────────────────────────────────────────────────── describe("Requests tab", () => { // ── Fixtures ──────────────────────────────────────────────────────────────── const PENDING_REQUEST = { id: "req-pending-1", organization_id: "org-1", user_id: "user-requestor", user_name: "Requestor User", user_email: "requestor@test.com", portal_network_id: "net-abc", granted_by_user_id: null, grant_type: "requested" as const, state: "pending" as const, justification: "Need access for development work.", created_at: "2025-06-01T10:00:00Z", updated_at: "2025-06-01T10:00:00Z", deleted_at: null, }; const APPROVED_REQUEST = { id: "req-approved-1", organization_id: "org-1", user_id: "user-approved", user_name: "Approved User", user_email: "approved@test.com", portal_network_id: "net-abc", granted_by_user_id: "user-admin", grant_type: "assigned" as const, state: "approved" as const, justification: null, created_at: "2025-05-15T08:00:00Z", updated_at: "2025-05-15T09:00:00Z", deleted_at: null, }; const REJECTED_REQUEST = { id: "req-rejected-1", organization_id: "org-1", user_id: "user-rejected", user_name: "Rejected User", user_email: "rejected@test.com", portal_network_id: "net-abc", granted_by_user_id: null, grant_type: "requested" as const, state: "rejected" as const, justification: "This user should not have access.", created_at: "2025-04-20T12:00:00Z", updated_at: "2025-04-21T12:00:00Z", deleted_at: null, }; const REVOKED_REQUEST = { id: "req-revoked-1", organization_id: "org-1", user_id: "user-revoked", portal_network_id: "net-abc", granted_by_user_id: "user-admin", grant_type: "assigned" as const, state: "revoked" as const, justification: null, created_at: "2025-03-01T00:00:00Z", updated_at: "2025-03-15T00:00:00Z", deleted_at: null, }; const SUSPENDED_REQUEST = { id: "req-suspended-1", organization_id: "org-1", user_id: "user-suspended", portal_network_id: "net-abc", granted_by_user_id: "user-admin", grant_type: "assigned" as const, state: "suspended" as const, justification: "Policy violation review.", created_at: "2025-02-10T00:00:00Z", updated_at: "2025-02-12T00:00:00Z", deleted_at: null, }; // Helper: setup resolved network, then click Requests tab async function setupRequestsTab(requestsList: Array> = [PENDING_REQUEST]) { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkPendingRequests.mockResolvedValue({ requests: requestsList, count: requestsList.length }); renderWithRoute(); // Wait for the page to render (network resolves first) await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); // Activate Requests tab via keyboard (Radix uses focus + Enter) const requestsTab = screen.getByRole("tab", { name: "Requests" }); requestsTab.focus(); fireEvent.keyDown(requestsTab, { key: "Enter" }); // Wait for requests content await waitFor(() => { expect(screen.getByText("Access Requests")).toBeDefined(); }); } // ── Loading / Error / Empty ───────────────────────────────────────────────── test("renders loading spinner in Requests tab while fetching requests", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); // getNetworkPendingRequests stays pending (never resolves) renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const requestsTab = screen.getByRole("tab", { name: "Requests" }); requestsTab.focus(); fireEvent.keyDown(requestsTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("Access Requests")).toBeDefined(); }); expect(screen.getByText("Loading requests…")).toBeDefined(); }); test("renders error message when requests API fails", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkPendingRequests.mockRejectedValue(new Error("API unavailable")); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const requestsTab = screen.getByRole("tab", { name: "Requests" }); requestsTab.focus(); fireEvent.keyDown(requestsTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("Failed to load requests.")).toBeDefined(); }); }); test("renders ApiError message when requests API throws ApiError", async () => { const apiError = new ApiError("Pending request lookup failed", 500, "internal_error"); H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkPendingRequests.mockRejectedValue(apiError); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const requestsTab = screen.getByRole("tab", { name: "Requests" }); requestsTab.focus(); fireEvent.keyDown(requestsTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("Pending request lookup failed")).toBeDefined(); }); }); test("renders 'No pending requests' message when requests array is empty", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkPendingRequests.mockResolvedValue({ requests: [], count: 0 }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const requestsTab = screen.getByRole("tab", { name: "Requests" }); requestsTab.focus(); fireEvent.keyDown(requestsTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("No pending requests for this network.")).toBeDefined(); }); }); test("renders 'No pending requests' when requests field is undefined", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkPendingRequests.mockResolvedValue({ count: 0 }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const requestsTab = screen.getByRole("tab", { name: "Requests" }); requestsTab.focus(); fireEvent.keyDown(requestsTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("No pending requests for this network.")).toBeDefined(); }); }); // ── Request list display ──────────────────────────────────────────────────── test("displays user_name when available", async () => { await setupRequestsTab([PENDING_REQUEST]); expect(screen.getByText("Requestor User")).toBeDefined(); }); test("displays grant_type badge", async () => { await setupRequestsTab([PENDING_REQUEST]); expect(screen.getByText("requested")).toBeDefined(); }); test("displays grant_type badge", async () => { await setupRequestsTab([PENDING_REQUEST]); expect(screen.getByText("requested")).toBeDefined(); }); test("displays justification in quotes when present", async () => { await setupRequestsTab([PENDING_REQUEST]); expect(screen.getByText('"Need access for development work."')).toBeDefined(); }); test("does NOT display justification when null", async () => { await setupRequestsTab([APPROVED_REQUEST]); await waitFor(() => { expect(screen.getByText("Approved User")).toBeDefined(); }); expect(screen.queryByText(/"/)).toBeNull(); }); test("displays created_at formatted date", async () => { await setupRequestsTab([PENDING_REQUEST]); // "Requested:" is part of a larger text node like "Requested: Jun 1, 2025" const requestedEl = screen.getByText(/^Requested: /); expect(requestedEl).toBeDefined(); // The element should contain text beyond just "Requested:" expect(requestedEl.textContent?.length).toBeGreaterThan("Requested:".length); // Verify it is NOT the em-dash fallback (which would mean formatDate failed) expect(requestedEl.textContent).not.toBe("Requested: —"); }); // ── ApprovalStateBadge colors ──────────────────────────────────────────────── test("renders Pending badge with yellow colors for pending state", async () => { await setupRequestsTab([PENDING_REQUEST]); const badge = screen.getByText("Pending"); expect(badge).toBeDefined(); expect(badge.className).toContain("bg-yellow-100"); expect(badge.className).toContain("text-yellow-700"); }); test("renders Approved badge with green colors for approved state", async () => { await setupRequestsTab([APPROVED_REQUEST]); const badge = screen.getByText("Approved"); expect(badge).toBeDefined(); expect(badge.className).toContain("bg-green-100"); expect(badge.className).toContain("text-green-700"); }); test("renders Rejected badge with red colors for rejected state", async () => { await setupRequestsTab([REJECTED_REQUEST]); const badge = screen.getByText("Rejected"); expect(badge).toBeDefined(); expect(badge.className).toContain("bg-red-100"); expect(badge.className).toContain("text-red-700"); }); test("renders Revoked badge with red colors for revoked state", async () => { await setupRequestsTab([REVOKED_REQUEST]); const badge = screen.getByText("Revoked"); expect(badge).toBeDefined(); expect(badge.className).toContain("bg-red-100"); expect(badge.className).toContain("text-red-700"); }); test("renders Suspended badge with orange colors for suspended state", async () => { await setupRequestsTab([SUSPENDED_REQUEST]); const badge = screen.getByText("Suspended"); expect(badge).toBeDefined(); expect(badge.className).toContain("bg-orange-100"); expect(badge.className).toContain("text-orange-700"); }); // ── Approve / Reject button visibility ─────────────────────────────────────── test("shows Approve and Reject buttons for pending state request", async () => { await setupRequestsTab([PENDING_REQUEST]); expect(screen.getByRole("button", { name: "Approve" })).toBeDefined(); expect(screen.getByRole("button", { name: "Reject" })).toBeDefined(); }); test("does NOT show action buttons for approved state request", async () => { await setupRequestsTab([APPROVED_REQUEST]); await waitFor(() => { expect(screen.getByText("Approved")).toBeDefined(); }); expect(screen.queryByRole("button", { name: "Approve" })).toBeNull(); expect(screen.queryByRole("button", { name: "Reject" })).toBeNull(); }); test("does NOT show action buttons for rejected state request", async () => { await setupRequestsTab([REJECTED_REQUEST]); await waitFor(() => { expect(screen.getByText("Rejected")).toBeDefined(); }); expect(screen.queryByRole("button", { name: "Approve" })).toBeNull(); expect(screen.queryByRole("button", { name: "Reject" })).toBeNull(); }); test("does NOT show action buttons for revoked state request", async () => { await setupRequestsTab([REVOKED_REQUEST]); await waitFor(() => { expect(screen.getByText("Revoked")).toBeDefined(); }); expect(screen.queryByRole("button", { name: "Approve" })).toBeNull(); expect(screen.queryByRole("button", { name: "Reject" })).toBeNull(); }); test("does NOT show action buttons for suspended state request", async () => { await setupRequestsTab([SUSPENDED_REQUEST]); await waitFor(() => { expect(screen.getByText("Suspended")).toBeDefined(); }); expect(screen.queryByRole("button", { name: "Approve" })).toBeNull(); expect(screen.queryByRole("button", { name: "Reject" })).toBeNull(); }); // ── Approve action ─────────────────────────────────────────────────────────── test("clicking Approve calls api.zerotier.approveRequest with correct orgId and approvalId", async () => { H.mockApproveRequest.mockResolvedValue({}); // Second call to getNetworkPendingRequests (refresh after approve) H.mockGetNetworkPendingRequests .mockResolvedValueOnce({ requests: [PENDING_REQUEST], count: 1 }) .mockResolvedValueOnce({ requests: [], count: 0 }); await setupRequestsTab([PENDING_REQUEST]); const approveBtn = screen.getByRole("button", { name: "Approve" }); fireEvent.click(approveBtn); expect(H.mockApproveRequest).toHaveBeenCalledTimes(1); expect(H.mockApproveRequest).toHaveBeenCalledWith("org-1", "req-pending-1"); }); test("shows success toast after successful approval", async () => { H.mockApproveRequest.mockResolvedValue({}); H.mockGetNetworkPendingRequests .mockResolvedValueOnce({ requests: [PENDING_REQUEST], count: 1 }) .mockResolvedValueOnce({ requests: [], count: 0 }); await setupRequestsTab([PENDING_REQUEST]); const approveBtn = screen.getByRole("button", { name: "Approve" }); fireEvent.click(approveBtn); await waitFor(() => { expect(H.mockToast).toHaveBeenCalledWith(expect.objectContaining({ title: "Request approved" })); }); }); test("shows error toast when approval fails", async () => { H.mockApproveRequest.mockRejectedValue(new Error("Approval failed")); H.mockGetNetworkPendingRequests.mockResolvedValue({ requests: [PENDING_REQUEST], count: 1 }); await setupRequestsTab([PENDING_REQUEST]); const approveBtn = screen.getByRole("button", { name: "Approve" }); fireEvent.click(approveBtn); await waitFor(() => { expect(H.mockToast).toHaveBeenCalledWith(expect.objectContaining({ variant: "destructive", title: "Failed to approve", })); }); }); test("shows ApiError description in error toast when approval fails with ApiError", async () => { const apiError = new ApiError("Request not found", 404, "not_found"); H.mockApproveRequest.mockRejectedValue(apiError); H.mockGetNetworkPendingRequests.mockResolvedValue({ requests: [PENDING_REQUEST], count: 1 }); await setupRequestsTab([PENDING_REQUEST]); const approveBtn = screen.getByRole("button", { name: "Approve" }); fireEvent.click(approveBtn); await waitFor(() => { expect(H.mockToast).toHaveBeenCalledWith(expect.objectContaining({ variant: "destructive", title: "Failed to approve", description: "Request not found", })); }); }); test("refresh after approve fetches updated requests", async () => { H.mockApproveRequest.mockResolvedValue({}); H.mockGetNetworkPendingRequests .mockResolvedValueOnce({ requests: [PENDING_REQUEST], count: 1 }) .mockResolvedValueOnce({ requests: [], count: 0 }); await setupRequestsTab([PENDING_REQUEST]); const approveBtn = screen.getByRole("button", { name: "Approve" }); fireEvent.click(approveBtn); await waitFor(() => { expect(H.mockGetNetworkPendingRequests).toHaveBeenCalledTimes(2); }); expect(H.mockGetNetworkPendingRequests).toHaveBeenNthCalledWith(1, "org-1", "net-abc"); expect(H.mockGetNetworkPendingRequests).toHaveBeenNthCalledWith(2, "org-1", "net-abc"); }); // ── Reject action ──────────────────────────────────────────────────────────── test("clicking Reject calls api.zerotier.rejectRequest with correct orgId and approvalId", async () => { H.mockRejectRequest.mockResolvedValue({}); H.mockGetNetworkPendingRequests .mockResolvedValueOnce({ requests: [PENDING_REQUEST], count: 1 }) .mockResolvedValueOnce({ requests: [], count: 0 }); await setupRequestsTab([PENDING_REQUEST]); const rejectBtn = screen.getByRole("button", { name: "Reject" }); fireEvent.click(rejectBtn); expect(H.mockRejectRequest).toHaveBeenCalledTimes(1); expect(H.mockRejectRequest).toHaveBeenCalledWith("org-1", "req-pending-1"); }); test("shows success toast after successful rejection", async () => { H.mockRejectRequest.mockResolvedValue({}); H.mockGetNetworkPendingRequests .mockResolvedValueOnce({ requests: [PENDING_REQUEST], count: 1 }) .mockResolvedValueOnce({ requests: [], count: 0 }); await setupRequestsTab([PENDING_REQUEST]); const rejectBtn = screen.getByRole("button", { name: "Reject" }); fireEvent.click(rejectBtn); await waitFor(() => { expect(H.mockToast).toHaveBeenCalledWith(expect.objectContaining({ title: "Request rejected" })); }); }); test("shows error toast when rejection fails", async () => { H.mockRejectRequest.mockRejectedValue(new Error("Rejection failed")); H.mockGetNetworkPendingRequests.mockResolvedValue({ requests: [PENDING_REQUEST], count: 1 }); await setupRequestsTab([PENDING_REQUEST]); const rejectBtn = screen.getByRole("button", { name: "Reject" }); fireEvent.click(rejectBtn); await waitFor(() => { expect(H.mockToast).toHaveBeenCalledWith(expect.objectContaining({ variant: "destructive", title: "Failed to reject", })); }); }); test("shows ApiError description in error toast when rejection fails with ApiError", async () => { const apiError = new ApiError("Request already processed", 409, "conflict"); H.mockRejectRequest.mockRejectedValue(apiError); H.mockGetNetworkPendingRequests.mockResolvedValue({ requests: [PENDING_REQUEST], count: 1 }); await setupRequestsTab([PENDING_REQUEST]); const rejectBtn = screen.getByRole("button", { name: "Reject" }); fireEvent.click(rejectBtn); await waitFor(() => { expect(H.mockToast).toHaveBeenCalledWith(expect.objectContaining({ variant: "destructive", title: "Failed to reject", description: "Request already processed", })); }); }); test("refresh after reject fetches updated requests", async () => { H.mockRejectRequest.mockResolvedValue({}); H.mockGetNetworkPendingRequests .mockResolvedValueOnce({ requests: [PENDING_REQUEST], count: 1 }) .mockResolvedValueOnce({ requests: [], count: 0 }); await setupRequestsTab([PENDING_REQUEST]); const rejectBtn = screen.getByRole("button", { name: "Reject" }); fireEvent.click(rejectBtn); await waitFor(() => { expect(H.mockGetNetworkPendingRequests).toHaveBeenCalledTimes(2); }); expect(H.mockGetNetworkPendingRequests).toHaveBeenNthCalledWith(1, "org-1", "net-abc"); expect(H.mockGetNetworkPendingRequests).toHaveBeenNthCalledWith(2, "org-1", "net-abc"); }); // ── Requests badge count ───────────────────────────────────────────────────── test("shows request count in the Requests tab badge", async () => { await setupRequestsTab([PENDING_REQUEST, APPROVED_REQUEST, REJECTED_REQUEST]); expect(screen.getByText("3 pending")).toBeDefined(); }); test("shows '0 pending' when empty", async () => { H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkPendingRequests.mockResolvedValue({ requests: [], count: 0 }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const requestsTab = screen.getByRole("tab", { name: "Requests" }); requestsTab.focus(); fireEvent.keyDown(requestsTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("0 pending")).toBeDefined(); }); }); // ── Multiple requests display ──────────────────────────────────────────────── test("renders multiple requests in the list", async () => { await setupRequestsTab([PENDING_REQUEST, APPROVED_REQUEST]); expect(screen.getByText("Requestor User")).toBeDefined(); expect(screen.getByText("Approved User")).toBeDefined(); }); test("mixed list: only pending requests have action buttons", async () => { await setupRequestsTab([PENDING_REQUEST, APPROVED_REQUEST]); // Requestor User (pending) should have action buttons // Approved User (approved) should not const approveButtons = screen.getAllByRole("button", { name: "Approve" }); expect(approveButtons.length).toBe(1); const rejectButtons = screen.getAllByRole("button", { name: "Reject" }); expect(rejectButtons.length).toBe(1); }); // ── Adversarial: Unicode user_ids in requests ─────────────────────────────── test("renders requests with Unicode user_ids", async () => { const unicodeRequest = { ...PENDING_REQUEST, id: "req-uni-1", user_id: "user-äéîøü-中文-requestor", user_name: "Unicode Requestor", }; await setupRequestsTab([unicodeRequest]); expect(screen.getByText("Unicode Requestor")).toBeDefined(); }); // ── Adversarial: XSS-safe justification ────────────────────────────────────── test("renders justification with script tags safely", async () => { const xssRequest = { ...PENDING_REQUEST, id: "req-xss-1", user_id: "user-xss-req", justification: "", }; await setupRequestsTab([xssRequest]); expect(screen.getByText('""')).toBeDefined(); }); // ── Adversarial: many requests ─────────────────────────────────────────────── test("renders many requests without crashing", async () => { const manyRequests = Array.from({ length: 50 }, (_, i) => ({ ...PENDING_REQUEST, id: `req-many-${i}`, user_id: `user-req-${i}`, user_name: `Requestor ${i}`, })); H.mockGetNetwork.mockResolvedValue({ network: DEV_NETWORK }); H.mockGetNetworkPendingRequests.mockResolvedValue({ requests: manyRequests, count: 50 }); renderWithRoute(); await waitFor(() => { expect(screen.getByText("Dev Network")).toBeDefined(); }); const requestsTab = screen.getByRole("tab", { name: "Requests" }); requestsTab.focus(); fireEvent.keyDown(requestsTab, { key: "Enter" }); await waitFor(() => { expect(screen.getByText("50 pending")).toBeDefined(); }); expect(screen.getByText("Requestor 0")).toBeDefined(); expect(screen.getByText("Requestor 49")).toBeDefined(); }); }); });