Skip to content
Open
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4064018
Add structured vulnerability findings workflow
ross0x01 Jul 16, 2026
419d7cf
Improve findings empty state
ross0x01 Jul 17, 2026
b08d162
Address findings review feedback
ross0x01 Jul 17, 2026
49ad875
Improve findings detail navigation
ross0x01 Jul 17, 2026
1e67681
Align source message at its start
ross0x01 Jul 17, 2026
85aca38
Keep source message pagination moving
ross0x01 Jul 17, 2026
e39bec4
Keep source message scroll position
ross0x01 Jul 17, 2026
23cbcc4
Avoid duplicate sticky-scroll escape
ross0x01 Jul 17, 2026
fa96493
Polish finding detail workspace
ross0x01 Jul 17, 2026
8af6869
Fix findings URL state sync
ross0x01 Jul 17, 2026
9383ef0
Keep findings search URL in sync
ross0x01 Jul 17, 2026
1719b26
Avoid stale findings history state
ross0x01 Jul 17, 2026
42da50b
Stabilize finding discovery time
ross0x01 Jul 17, 2026
38bcea3
Merge remote-tracking branch 'origin/main' into codex/hac-30-findings
ross0x01 Jul 17, 2026
4c702ee
Harden findings workflow for production
ross0x01 Jul 18, 2026
94d97a5
Hide redundant finding source action in chat
ross0x01 Jul 18, 2026
958e809
Open findings in focused modal
ross0x01 Jul 18, 2026
a559e49
Keep chat finding details read-only
ross0x01 Jul 18, 2026
bda7e3e
Hide redundant source chat filter
ross0x01 Jul 18, 2026
69c2ca2
Hide findings navigation icon on desktop
ross0x01 Jul 18, 2026
29b6d19
Redesign vulnerability findings workspace
ross0x01 Jul 18, 2026
ae83658
Merge remote-tracking branch 'origin/main' into codex/hac-30-findings
ross0x01 Jul 18, 2026
c2d7439
Accept blank optional finding identifiers
ross0x01 Jul 18, 2026
ee33a87
Normalize optional finding report fields
ross0x01 Jul 18, 2026
b6b23f7
Improve tool failure details
ross0x01 Jul 18, 2026
9024c82
Refine source message highlight
ross0x01 Jul 20, 2026
c57c484
Keep source message scroll on target
ross0x01 Jul 20, 2026
b5c2694
Scroll after source message layout
ross0x01 Jul 20, 2026
117cb5f
Fade source message highlight
ross0x01 Jul 20, 2026
05927de
Remove persistent source message outline
ross0x01 Jul 20, 2026
8e735d2
Refine findings workspace UI
ross0x01 Jul 21, 2026
8268e20
Tighten mobile findings layout
ross0x01 Jul 21, 2026
5ff7ec4
Forward tool error keyboard handling
ross0x01 Jul 21, 2026
d322796
Refine findings workspace and lifecycle
ross0x01 Jul 21, 2026
36f62f5
Harden findings CSV export
ross0x01 Jul 21, 2026
bc7243a
Improve finding severity explanations
ross0x01 Jul 21, 2026
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
358 changes: 358 additions & 0 deletions app/(chat)/findings/__tests__/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,358 @@
import "@testing-library/jest-dom";
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";

const mockUsePaginatedQuery = jest.fn();
const mockUseQuery = jest.fn((_ref: unknown, args: any) =>
args?.findingId ? mockFinding : undefined,
);
const mockConvexQuery = jest.fn();
const mockDownloadFile = jest.fn();
const mockCapture = jest.fn();
const mockPush = jest.fn();
const mockSearchParams = new URLSearchParams();
const mockCloseSidebar = jest.fn();
const mockInitializeNewChat = jest.fn();
const mockSetChatMode = jest.fn();
const mockSetTemporaryChatsEnabled = jest.fn();

jest.mock("convex/react", () => ({
useConvex: () => ({ query: mockConvexQuery }),
useConvexAuth: () => ({ isLoading: false, isAuthenticated: true }),
usePaginatedQuery: (...args: unknown[]) => mockUsePaginatedQuery(...args),
useQuery: (...args: unknown[]) => mockUseQuery(...args),
useMutation: () => jest.fn(),
}));

jest.mock("@/app/contexts/GlobalState", () => ({
useGlobalState: () => ({
setChatSidebarOpen: jest.fn(),
closeSidebar: mockCloseSidebar,
initializeNewChat: mockInitializeNewChat,
setChatMode: mockSetChatMode,
setTemporaryChatsEnabled: mockSetTemporaryChatsEnabled,
}),
}));

jest.mock("next/navigation", () => ({
useRouter: () => ({ push: mockPush }),
useSearchParams: () => mockSearchParams,
}));

jest.mock("@/app/hooks/useTauri", () => ({ navigateToAuth: jest.fn() }));
jest.mock("@/lib/analytics/client", () => ({
captureAuthenticatedEvent: mockCapture,
}));
jest.mock("@/lib/utils/file-download", () => ({
downloadFile: (...args: unknown[]) => mockDownloadFile(...args),
}));

const mockFinding = {
finding_id: "finding-1",
title: "Confirmed IDOR",
target: "https://app.example.test",
endpoint: "/api/invoices/other",
method: "GET",
severity: "high",
cvss_score: 7.1,
category: "access_control",
status: "active",
chat_id: "chat-1",
chat_title: "Invoice test",
created_at: Date.now(),
updated_at: Date.now(),
message_id: "message-1",
description: "Another account's invoice is readable.",
impact: "Billing data disclosure.",
technical_analysis: "Missing owner predicate.",
poc_description: "Request another account's invoice.",
poc_script_code: "curl /api/invoices/other",
remediation_steps: "Add an owner predicate.",
evidence: "HTTP 200 returned another account's data.",
assumptions: "Ordinary account.",
fix_effort: "low",
cvss_vector: "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N",
cvss_breakdown: {
attack_vector: "N",
attack_complexity: "L",
privileges_required: "L",
user_interaction: "N",
scope: "U",
confidentiality: "H",
integrity: "N",
availability: "N",
},
};

const Page = require("../page").default as typeof import("../page").default;

describe("FindingsPage", () => {
beforeEach(() => {
jest.clearAllMocks();
for (const key of [...mockSearchParams.keys()]) {
mockSearchParams.delete(key);
}
window.history.replaceState({}, "", "/findings");
mockUseQuery.mockImplementation((_ref: unknown, args: any) =>
args?.findingId ? mockFinding : undefined,
);
mockUsePaginatedQuery.mockReturnValue({
results: [mockFinding],
status: "Exhausted",
loadMore: jest.fn(),
});
mockConvexQuery.mockResolvedValue({
page: [mockFinding],
isDone: true,
continueCursor: "",
});
mockDownloadFile.mockResolvedValue(undefined);
});

it("lists metadata and searches without a source-chat filter", async () => {
render(<Page />);
expect(screen.getByRole("heading", { name: "Findings" })).toBeVisible();
expect(screen.getByRole("button", { name: "Open navigation" })).toHaveClass(
"md:hidden",
);
expect(screen.getByText("Confirmed IDOR")).toBeVisible();
expect(screen.queryByText("/api/invoices/other")).toBeNull();
expect(screen.getByText("7.1")).toBeVisible();
expect(screen.getByTestId("finding-severity-dot-finding-1")).toHaveClass(
"bg-orange-500",
);
expect(screen.getByText("Current Results")).toBeVisible();
expect(screen.queryByText("Validation Standard")).toBeNull();
expect(screen.queryByText("Evidence + working PoC")).toBeNull();
expect(screen.queryByText("Endpoint")).toBeNull();
expect(screen.queryByText("Source Chat")).toBeNull();
expect(screen.getAllByText("Category").length).toBeGreaterThan(0);
expect(screen.getAllByText("Status").length).toBeGreaterThan(0);
expect(screen.getByText("Access Control / IDOR")).toBeVisible();
expect(screen.getAllByText("Active").length).toBeGreaterThan(0);
expect(screen.getByRole("list", { name: "Findings" })).toBeVisible();
expect(screen.getByLabelText("Filter by category")).toBeVisible();
expect(screen.getByLabelText("Filter by status")).toBeVisible();
expect(screen.getByLabelText("Filter by severity")).toBeVisible();
expect(screen.queryByLabelText("Filter by source chat")).toBeNull();
expect(
screen.getByRole("button", { name: "Export findings as CSV" }),
).toBeVisible();
expect(
screen.getByRole("button", { name: "Start new scan" }),
).toBeVisible();

fireEvent.change(screen.getByLabelText("Search findings"), {
target: { value: "CWE-639" },
});
await waitFor(() => {
expect(mockUsePaginatedQuery).toHaveBeenLastCalledWith(
expect.anything(),
expect.objectContaining({ search: "CWE-639" }),
{ initialNumItems: 25 },
);
});
expect(screen.getByText("Best matches for “CWE-639”")).toBeVisible();
expect(window.location.search).toBe("?q=CWE-639");

fireEvent.change(screen.getByLabelText("Search findings"), {
target: { value: "" },
});
await waitFor(() => {
expect(window.location.search).toBe("");
});
expect(mockCapture).toHaveBeenCalledWith("findings_page_viewed");
});

it("filters by category and lifecycle status", async () => {
render(<Page />);

fireEvent.click(screen.getByLabelText("Filter by category"));
fireEvent.click(screen.getByRole("option", { name: "Injection" }));
await waitFor(() => {
expect(mockUsePaginatedQuery).toHaveBeenLastCalledWith(
expect.anything(),
expect.objectContaining({ category: "injection" }),
{ initialNumItems: 25 },
);
});

fireEvent.click(screen.getByLabelText("Filter by status"));
fireEvent.click(screen.getByRole("option", { name: "Closed" }));
await waitFor(() => {
expect(mockUsePaginatedQuery).toHaveBeenLastCalledWith(
expect.anything(),
expect.objectContaining({
category: "injection",
status: "closed",
}),
{ initialNumItems: 25 },
);
});
expect(window.location.search).toContain("category=injection");
expect(window.location.search).toContain("status=closed");
});

it("exports the complete filtered summary as CSV", async () => {
render(<Page />);

fireEvent.click(
screen.getByRole("button", { name: "Export findings as CSV" }),
);

await waitFor(() => {
expect(mockConvexQuery).toHaveBeenCalledWith(expect.anything(), {
paginationOpts: { cursor: null, numItems: 25 },
});
expect(mockDownloadFile).toHaveBeenCalledWith(
expect.objectContaining({
filename: expect.stringMatching(/^findings-\d{4}-\d{2}-\d{2}\.csv$/),
mimeType: "text/csv;charset=utf-8",
content: expect.stringContaining(
'"Confirmed IDOR","https://app.example.test","Access Control / IDOR","high","7.1","active"',
),
}),
);
});
});

it("drops legacy source-chat filter parameters", () => {
mockSearchParams.set("chat", "chat-1");
window.history.replaceState({}, "", "/findings?chat=chat-1");

render(<Page />);

expect(screen.queryByLabelText("Filter by source chat")).toBeNull();
expect(window.location.search).toBe("");
expect(mockUsePaginatedQuery).toHaveBeenLastCalledWith(
expect.anything(),
expect.not.objectContaining({ chatId: expect.anything() }),
{ initialNumItems: 25 },
);
});

it("guides first-time users into a persistent Agent security test", () => {
mockUsePaginatedQuery.mockReturnValue({
results: [],
status: "Exhausted",
loadMore: jest.fn(),
});

render(<Page />);

expect(screen.getByText("No findings yet")).toBeVisible();
expect(
screen.getByText(/Once it confirms a vulnerability with solid evidence/i),
).toBeVisible();

fireEvent.click(
screen.getByRole("button", { name: "Start your first scan" }),
);

expect(mockCloseSidebar).toHaveBeenCalled();
expect(mockInitializeNewChat).toHaveBeenCalled();
expect(mockSetTemporaryChatsEnabled).toHaveBeenCalledWith(false);
expect(mockSetChatMode).toHaveBeenCalledWith("agent");
expect(mockPush).toHaveBeenCalledWith("/");
});

it("shows a reset action when search results are empty", async () => {
mockUsePaginatedQuery.mockReturnValue({
results: [],
status: "Exhausted",
loadMore: jest.fn(),
});

render(<Page />);
fireEvent.change(screen.getByLabelText("Search findings"), {
target: { value: "missing target" },
});

expect(await screen.findByText("No matching findings")).toBeVisible();
fireEvent.click(screen.getByRole("button", { name: "Clear filters" }));

expect(screen.getByLabelText("Search findings")).toHaveValue("");
await waitFor(() => {
expect(screen.getByText("No findings yet")).toBeVisible();
expect(window.location.search).toBe("");
});
});

it("opens and closes the reusable detail in a focused modal", async () => {
render(<Page />);
const findingRow = screen.getByRole("link", {
name: /Confirmed IDOR/i,
});
expect(findingRow).toHaveAttribute("href", "/findings?finding=finding-1");
fireEvent.click(findingRow);
const dialog = screen.getByRole("dialog", {
name: "Vulnerability Report",
});
expect(dialog).toBeVisible();
expect(dialog).toHaveClass("sm:max-w-6xl", "sm:rounded-2xl");
expect(document.querySelector('[data-slot="dialog-overlay"]')).toHaveClass(
"bg-black/60",
"backdrop-blur-sm",
);
expect(screen.getByText(mockFinding.description)).toBeVisible();
expect(
screen.getByRole("link", {
name: "Open source message in Invoice test",
}),
).toHaveAttribute("href", "/c/chat-1#message=message-1");
expect(mockCapture).toHaveBeenCalledWith("finding_viewed", {
surface: "findings_page",
});

fireEvent.click(
screen.getByRole("button", { name: "Close vulnerability report" }),
);
await waitFor(() => {
expect(screen.queryByText(mockFinding.description)).toBeNull();
expect(findingRow).toHaveFocus();
});
expect(window.location.pathname).toBe("/findings");
expect(window.location.search).toBe("");
});

it("opens a finding directly from the URL", () => {
mockSearchParams.set("finding", "finding-1");
window.history.replaceState({}, "", "/findings?finding=finding-1");

render(<Page />);

expect(screen.getByText(mockFinding.description)).toBeVisible();
expect(
screen.getByRole("button", { name: "Close vulnerability report" }),
).toBeVisible();
expect(screen.getByRole("button", { name: "Close Finding" })).toBeVisible();
expect(mockCapture).toHaveBeenCalledWith("finding_viewed", {
surface: "findings_page",
});
});

it("keeps a full-screen mobile close path and restores list focus", async () => {
render(<Page />);
const findingRow = screen.getByRole("link", {
name: /Confirmed IDOR/i,
});
findingRow.focus();
fireEvent.click(findingRow);

expect(
screen.getByRole("dialog", { name: "Vulnerability Report" }),
).toHaveClass("h-dvh", "w-screen");
expect(
screen.getByRole("button", { name: "Back to Findings" }),
).toBeVisible();
expect(screen.getByText(mockFinding.description)).toBeVisible();

fireEvent.click(screen.getByRole("button", { name: "Back to Findings" }));
await waitFor(() => {
expect(
screen.queryByRole("dialog", { name: "Vulnerability Report" }),
).toBeNull();
expect(findingRow).toHaveFocus();
});
});
});
Loading