mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-22 08:06:42 +00:00
fix: load the full vulnerability list in the scan detail sheet (#1483)
The scan detail sheet fetched only the first 500 vulnerabilities for its interactive table, so severity filtering, row inspection, and suppression management could not reach findings beyond the first page on a scan with more than 500. The CSV export already paged the complete list, but that is not a substitute for working with the findings in the table. The sheet now loads every vulnerability via the same paged helper the CSV uses, so the table, filter, pagination, inspection, and suppression all operate over the complete set. The "showing first N of M, export CSV for the complete list" notice is removed because the table is no longer capped. The CSV export reuses the already-complete in-memory set rather than refetching. Secrets and misconfigurations keep their existing per-request cap; they are not the suppression-managed findings this blocker concerns and rarely exceed it.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* The detail sheet must load EVERY vulnerability (paging past the per-request
|
||||
* cap), not a single capped page, so severity filtering, inspection, and
|
||||
* suppression reach findings beyond the first page.
|
||||
*/
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, waitFor } from '@testing-library/react';
|
||||
|
||||
vi.mock('@/lib/api', () => ({ apiFetch: vi.fn() }));
|
||||
vi.mock('@/components/ui/toast-store', () => ({ toast: { error: vi.fn(), success: vi.fn() } }));
|
||||
|
||||
import { apiFetch } from '@/lib/api';
|
||||
import { VulnerabilityScanSheet } from '../VulnerabilityScanSheet';
|
||||
|
||||
const mockedFetch = vi.mocked(apiFetch);
|
||||
const ok = (body: unknown) => ({ ok: true, json: async () => body }) as Response;
|
||||
|
||||
function vuln(i: number) {
|
||||
return {
|
||||
id: i, scan_id: 1, vulnerability_id: `CVE-2026-${i}`, pkg_name: 'pkg', installed_version: '1',
|
||||
fixed_version: null, severity: 'HIGH', title: null, description: null, primary_url: null,
|
||||
};
|
||||
}
|
||||
|
||||
const SCAN = {
|
||||
id: 1, node_id: 1, image_ref: 'img:1', image_digest: null, scanned_at: Date.now(),
|
||||
total_vulnerabilities: 1500, critical_count: 0, high_count: 1500, medium_count: 0, low_count: 0,
|
||||
unknown_count: 0, fixable_count: 0, secret_count: 0, misconfig_count: 0, scanners_used: 'vuln',
|
||||
highest_severity: 'HIGH', os_info: null, trivy_version: '0.50.0', scan_duration_ms: 1, triggered_by: 'manual',
|
||||
status: 'completed', error: null, stack_context: null, policy_evaluation: null,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockedFetch.mockReset();
|
||||
mockedFetch.mockImplementation(((url: string) => {
|
||||
if (/\/vulnerabilities\?limit=1000&offset=0/.test(url)) {
|
||||
return Promise.resolve(ok({ items: Array.from({ length: 1000 }, (_, i) => vuln(i)), total: 1500 }));
|
||||
}
|
||||
if (/\/vulnerabilities\?limit=1000&offset=1000/.test(url)) {
|
||||
return Promise.resolve(ok({ items: Array.from({ length: 500 }, (_, i) => vuln(1000 + i)), total: 1500 }));
|
||||
}
|
||||
if (/\/secrets/.test(url) || /\/misconfigs/.test(url) || /\/security\/scans\?imageRef/.test(url)) {
|
||||
return Promise.resolve(ok({ items: [] }));
|
||||
}
|
||||
if (/\/security\/scans\/1$/.test(url)) return Promise.resolve(ok(SCAN));
|
||||
return Promise.resolve(ok({ items: [] }));
|
||||
}) as unknown as typeof apiFetch);
|
||||
});
|
||||
|
||||
describe('VulnerabilityScanSheet load', () => {
|
||||
it('pages the full vulnerability list instead of a single capped page', async () => {
|
||||
render(<VulnerabilityScanSheet scanId={1} onClose={() => {}} />);
|
||||
await waitFor(() => {
|
||||
const urls = mockedFetch.mock.calls.map((c) => String(c[0]));
|
||||
// Both pages of the offset-based full fetch were requested.
|
||||
expect(urls.some((u) => /vulnerabilities\?limit=1000&offset=0/.test(u))).toBe(true);
|
||||
expect(urls.some((u) => /vulnerabilities\?limit=1000&offset=1000/.test(u))).toBe(true);
|
||||
});
|
||||
// The old single capped page request is gone.
|
||||
const urls = mockedFetch.mock.calls.map((c) => String(c[0]));
|
||||
expect(urls.some((u) => /vulnerabilities\?limit=500/.test(u))).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user