[pitboss] sweep after phase 07: 6 deferred items resolved

This commit is contained in:
pitboss 2026-05-12 13:47:11 -04:00
parent bfdfcb9d1a
commit 25e8b0eb0e
6 changed files with 730 additions and 8 deletions

View file

@ -705,10 +705,12 @@ function HowToFix({ finding }: { finding: FindingView }) {
// ── Dynamic Verification Panel ──────────────────────────────────────────────
function DynamicVerdictSection({ verdict }: { verdict: VerifyResult }) {
export function DynamicVerdictSection({ verdict }: { verdict: VerifyResult }) {
const [copied, setCopied] = useState(false);
const reproPath = `~/.cache/nyx/dynamic/repro/${verdict.finding_id}/`;
const reproCmd = './reproduce.sh';
// The repro bundle is keyed by spec_hash (not finding_id) inside the Nyx
// cache. Rather than showing a path that may not match, surface the CLI
// command that locates and opens the bundle regardless of the hash.
const reproCmd = `nyx repro --finding ${verdict.finding_id}`;
const copyCmd = () => {
navigator.clipboard.writeText(reproCmd).then(() => {
@ -733,11 +735,8 @@ function DynamicVerdictSection({ verdict }: { verdict: VerifyResult }) {
{verdict.status === 'Confirmed' && (
<div className="repro-panel" data-testid="repro-panel">
<div className="repro-path-row">
<span className="repro-label">Repro artifact:</span>
<code className="repro-path">{reproPath}</code>
</div>
<div className="repro-cmd-row">
<span className="repro-label">Reproduce:</span>
<code className="repro-cmd">{reproCmd}</code>
<button
type="button"

View file

@ -0,0 +1,118 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { DynamicVerdictSection } from '@/pages/FindingDetailPage';
import type { VerifyResult } from '@/api/types';
function makeVerdict(
status: VerifyResult['status'],
extras: Partial<VerifyResult> = {},
): VerifyResult {
return {
finding_id: 'test-finding-id-abc',
status,
attempts: [],
...extras,
};
}
// Mock navigator.clipboard before each test.
beforeEach(() => {
Object.defineProperty(navigator, 'clipboard', {
value: { writeText: vi.fn().mockResolvedValue(undefined) },
configurable: true,
writable: true,
});
});
describe('DynamicVerdictSection', () => {
it('renders Confirmed badge', () => {
render(
<DynamicVerdictSection
verdict={makeVerdict('Confirmed', { triggered_payload: 'sqli-tautology' })}
/>,
);
expect(screen.getByTestId('verdict-badge-confirmed')).toBeInTheDocument();
});
it('renders NotConfirmed badge', () => {
render(<DynamicVerdictSection verdict={makeVerdict('NotConfirmed')} />);
expect(screen.getByTestId('verdict-badge-notconfirmed')).toBeInTheDocument();
});
it('renders Unsupported badge', () => {
render(
<DynamicVerdictSection
verdict={makeVerdict('Unsupported', { reason: 'NoPayloadsForCap' })}
/>,
);
expect(screen.getByTestId('verdict-badge-unsupported')).toBeInTheDocument();
});
it('renders Inconclusive badge', () => {
render(
<DynamicVerdictSection
verdict={makeVerdict('Inconclusive', { inconclusive_reason: 'BuildFailed' })}
/>,
);
expect(screen.getByTestId('verdict-badge-inconclusive')).toBeInTheDocument();
});
it('shows repro panel only for Confirmed status', () => {
const { unmount } = render(
<DynamicVerdictSection verdict={makeVerdict('Confirmed')} />,
);
expect(screen.getByTestId('repro-panel')).toBeInTheDocument();
unmount();
for (const status of ['NotConfirmed', 'Unsupported', 'Inconclusive'] as const) {
const { unmount: u } = render(
<DynamicVerdictSection verdict={makeVerdict(status)} />,
);
expect(screen.queryByTestId('repro-panel')).toBeNull();
u();
}
});
it('repro-panel contains the finding_id in the CLI command', () => {
render(
<DynamicVerdictSection
verdict={makeVerdict('Confirmed', { finding_id: 'cafecafe12345678' })}
/>,
);
const panel = screen.getByTestId('repro-panel');
expect(panel.textContent).toContain('cafecafe12345678');
expect(panel.textContent).toContain('nyx repro');
});
it('Copy button triggers clipboard writeText with the repro command', async () => {
const findingId = 'test-finding-id-abc';
render(<DynamicVerdictSection verdict={makeVerdict('Confirmed')} />);
const copyBtn = screen.getByRole('button', { name: /copy/i });
fireEvent.click(copyBtn);
expect(navigator.clipboard.writeText).toHaveBeenCalledOnce();
const calledWith = (navigator.clipboard.writeText as ReturnType<typeof vi.fn>).mock
.calls[0][0] as string;
expect(calledWith).toContain(findingId);
expect(calledWith).toContain('nyx repro');
});
it('shows exact toolchain match label when toolchain_match is exact', () => {
render(
<DynamicVerdictSection
verdict={makeVerdict('Confirmed', { toolchain_match: 'exact' })}
/>,
);
expect(screen.getByText('exact toolchain')).toBeInTheDocument();
});
it('shows approximate toolchain match label when toolchain_match is drift', () => {
render(
<DynamicVerdictSection
verdict={makeVerdict('Confirmed', { toolchain_match: 'drift' })}
/>,
);
expect(screen.getByText('approximate toolchain')).toBeInTheDocument();
});
});