mirror of
https://github.com/elicpeter/nyx.git
synced 2026-07-12 21:02:11 +02:00
[pitboss/grind] deferred session-0003 (20260521T201327Z-3848)
This commit is contained in:
parent
d99361cff6
commit
368f628054
14 changed files with 672 additions and 86 deletions
|
|
@ -2504,6 +2504,139 @@ tr.selected td {
|
|||
color: var(--text);
|
||||
}
|
||||
|
||||
/* ── Finding Detail: dynamic verification ─────────────────────────── */
|
||||
.badge-dyn-confirmed {
|
||||
background: var(--success-bg);
|
||||
color: var(--success);
|
||||
}
|
||||
.badge-dyn-notconfirmed {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.badge-dyn-inconclusive {
|
||||
background: var(--sev-medium-bg);
|
||||
color: var(--sev-medium);
|
||||
}
|
||||
.badge-dyn-unsupported {
|
||||
background: var(--conf-low-bg);
|
||||
color: var(--conf-low);
|
||||
}
|
||||
.dynamic-verdict-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-3);
|
||||
font-size: var(--text-sm);
|
||||
line-height: 1.45;
|
||||
}
|
||||
.dynamic-verdict-badge-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
.dynamic-toolchain-match {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 22px;
|
||||
padding: 2px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--text-xs);
|
||||
}
|
||||
.repro-panel,
|
||||
.dynamic-verdict-detail,
|
||||
.dynamic-attempts {
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 6px;
|
||||
background: var(--bg);
|
||||
padding: var(--space-3);
|
||||
}
|
||||
.repro-cmd-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
min-width: 0;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.repro-label,
|
||||
.dynamic-attempts > strong,
|
||||
.dynamic-verdict-detail strong {
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--weight-semibold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.repro-cmd {
|
||||
flex: 1 1 220px;
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
padding: 4px 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--terminal-bg);
|
||||
color: var(--terminal-text);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
.repro-copy-btn {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.dynamic-verdict-detail {
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
.dynamic-verdict-detail-text {
|
||||
color: var(--text-secondary);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.dynamic-attempt-list {
|
||||
list-style: none;
|
||||
display: grid;
|
||||
gap: var(--space-2);
|
||||
margin: var(--space-2) 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
.attempt-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto auto;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--border-light);
|
||||
border-radius: 6px;
|
||||
background: var(--surface);
|
||||
}
|
||||
.attempt-row.triggered {
|
||||
border-color: color-mix(in srgb, var(--success) 35%, var(--border));
|
||||
background: var(--success-bg);
|
||||
}
|
||||
.attempt-row code {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.attempt-outcome,
|
||||
.attempt-exit-code {
|
||||
color: var(--text-secondary);
|
||||
font-size: var(--text-xs);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.attempt-row.triggered .attempt-outcome {
|
||||
color: var(--success);
|
||||
font-weight: var(--weight-semibold);
|
||||
}
|
||||
@media (max-width: 640px) {
|
||||
.attempt-row {
|
||||
grid-template-columns: 1fr;
|
||||
align-items: start;
|
||||
}
|
||||
.attempt-outcome,
|
||||
.attempt-exit-code {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Code Viewer Modal ────────────────────────────────────────────── */
|
||||
.code-modal-overlay {
|
||||
position: fixed;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@ describe('DynamicVerdictSection', () => {
|
|||
it('renders Confirmed badge', () => {
|
||||
render(
|
||||
<DynamicVerdictSection
|
||||
verdict={makeVerdict('Confirmed', { triggered_payload: 'sqli-tautology' })}
|
||||
verdict={makeVerdict('Confirmed', {
|
||||
triggered_payload: 'sqli-tautology',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId('verdict-badge-confirmed')).toBeInTheDocument();
|
||||
|
|
@ -36,7 +38,18 @@ describe('DynamicVerdictSection', () => {
|
|||
|
||||
it('renders NotConfirmed badge', () => {
|
||||
render(<DynamicVerdictSection verdict={makeVerdict('NotConfirmed')} />);
|
||||
expect(screen.getByTestId('verdict-badge-notconfirmed')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('verdict-badge-notconfirmed'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not crash when the API omits an empty attempts array', () => {
|
||||
render(
|
||||
<DynamicVerdictSection
|
||||
verdict={{ finding_id: 'no-attempts', status: 'Confirmed' }}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId('verdict-badge-confirmed')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Unsupported badge', () => {
|
||||
|
|
@ -51,10 +64,14 @@ describe('DynamicVerdictSection', () => {
|
|||
it('renders Inconclusive badge', () => {
|
||||
render(
|
||||
<DynamicVerdictSection
|
||||
verdict={makeVerdict('Inconclusive', { inconclusive_reason: 'BuildFailed' })}
|
||||
verdict={makeVerdict('Inconclusive', {
|
||||
inconclusive_reason: 'BuildFailed',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByTestId('verdict-badge-inconclusive')).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByTestId('verdict-badge-inconclusive'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows repro panel only for Confirmed status', () => {
|
||||
|
|
@ -64,7 +81,11 @@ describe('DynamicVerdictSection', () => {
|
|||
expect(screen.getByTestId('repro-panel')).toBeInTheDocument();
|
||||
unmount();
|
||||
|
||||
for (const status of ['NotConfirmed', 'Unsupported', 'Inconclusive'] as const) {
|
||||
for (const status of [
|
||||
'NotConfirmed',
|
||||
'Unsupported',
|
||||
'Inconclusive',
|
||||
] as const) {
|
||||
const { unmount: u } = render(
|
||||
<DynamicVerdictSection verdict={makeVerdict(status)} />,
|
||||
);
|
||||
|
|
@ -92,8 +113,9 @@ describe('DynamicVerdictSection', () => {
|
|||
fireEvent.click(copyBtn);
|
||||
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledOnce();
|
||||
const calledWith = (navigator.clipboard.writeText as ReturnType<typeof vi.fn>).mock
|
||||
.calls[0][0] as string;
|
||||
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');
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@ describe('VerdictBadge', () => {
|
|||
it('renders Confirmed badge with flame and correct class', () => {
|
||||
render(
|
||||
<VerdictBadge
|
||||
verdict={makeVerdict('Confirmed', { triggered_payload: 'sqli-tautology' })}
|
||||
verdict={makeVerdict('Confirmed', {
|
||||
triggered_payload: 'sqli-tautology',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
const badge = screen.getByTestId('verdict-badge-confirmed');
|
||||
|
|
@ -41,6 +43,17 @@ describe('VerdictBadge', () => {
|
|||
expect(badge.textContent).not.toContain('🔥');
|
||||
});
|
||||
|
||||
it('renders when attempts are omitted by the API', () => {
|
||||
render(
|
||||
<VerdictBadge
|
||||
verdict={{ finding_id: 'test-finding-id', status: 'NotConfirmed' }}
|
||||
/>,
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId('verdict-badge-notconfirmed'),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders Unsupported badge with correct class', () => {
|
||||
render(
|
||||
<VerdictBadge
|
||||
|
|
@ -68,7 +81,9 @@ describe('VerdictBadge', () => {
|
|||
it('tooltip contains payload for Confirmed', () => {
|
||||
render(
|
||||
<VerdictBadge
|
||||
verdict={makeVerdict('Confirmed', { triggered_payload: 'sqli-payload' })}
|
||||
verdict={makeVerdict('Confirmed', {
|
||||
triggered_payload: 'sqli-payload',
|
||||
})}
|
||||
/>,
|
||||
);
|
||||
const badge = screen.getByTestId('verdict-badge-confirmed');
|
||||
|
|
@ -100,7 +115,9 @@ describe('VerdictBadge', () => {
|
|||
'Inconclusive',
|
||||
];
|
||||
for (const status of statuses) {
|
||||
const { unmount } = render(<VerdictBadge verdict={makeVerdict(status)} />);
|
||||
const { unmount } = render(
|
||||
<VerdictBadge verdict={makeVerdict(status)} />,
|
||||
);
|
||||
expect(
|
||||
screen.getByTestId(`verdict-badge-${status.toLowerCase()}`),
|
||||
).toBeInTheDocument();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue