113 lines
3.8 KiB
TypeScript
113 lines
3.8 KiB
TypeScript
// @vitest-environment jsdom
|
||
|
||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||
import { afterEach, describe, expect, it } from 'vitest'
|
||
|
||
import type { ScanReport } from './api'
|
||
import { FindingsView } from './components/FindingsView'
|
||
|
||
const report: ScanReport = {
|
||
id: 'scan-1',
|
||
repository_path: '/scan/repopulse',
|
||
repository_name: 'RepoPulse',
|
||
source: {
|
||
origin: 'gitea',
|
||
provider: 'gitea',
|
||
repository_id: '64',
|
||
repository_full_name: 'Jens/RepoPulse',
|
||
branch: 'main',
|
||
requested_revision: 'revision-1',
|
||
actual_revision: 'revision-1',
|
||
trigger: 'scheduled',
|
||
},
|
||
policy: {
|
||
auto_scan: true,
|
||
min_health_score: 70,
|
||
max_blockers: 0,
|
||
require_tests: true,
|
||
require_ci: true,
|
||
suppressions: [],
|
||
},
|
||
scanned_at: '2026-09-12T20:00:00Z',
|
||
duration_ms: 1200,
|
||
metrics: {
|
||
files: 2,
|
||
source_files: 2,
|
||
lines_of_code: 20,
|
||
test_files: 1,
|
||
todo: 0,
|
||
fixme: 0,
|
||
hack: 0,
|
||
xxx: 0,
|
||
dependency_manifests: 1,
|
||
declared_dependencies: 1,
|
||
large_files: 0,
|
||
large_source_files: 0,
|
||
},
|
||
languages: { TypeScript: 20 },
|
||
category_scores: { 'Security basics': 20, Maintainability: 80 },
|
||
health_score: 70,
|
||
release_readiness: 'BLOCKED',
|
||
findings: [
|
||
{
|
||
code: 'security.secret-assignment',
|
||
category: 'Security basics',
|
||
severity: 'blocker',
|
||
message: 'Potential credential assignment detected; value intentionally redacted',
|
||
path: 'backend/app/main.py',
|
||
line: 245,
|
||
deduction: 20,
|
||
fingerprint: 'fingerprint-1',
|
||
suppressed: false,
|
||
},
|
||
{
|
||
code: 'maintainability.long-function',
|
||
category: 'Maintainability',
|
||
severity: 'warning',
|
||
message: 'Function is longer than the configured threshold',
|
||
path: 'backend/app/signals.py',
|
||
line: 111,
|
||
deduction: 4,
|
||
fingerprint: 'fingerprint-2',
|
||
suppressed: false,
|
||
},
|
||
],
|
||
tests: { frameworks: ['pytest'], coverage_artifacts: [], ci_workflows: [], test_source_ratio: null },
|
||
git: { available: true, commit_count: 1, contributor_count: 1, latest_commit_at: null, tracked_files: 2, renamed_files: [] },
|
||
dependencies: { manifests: ['package.json'], lockfiles: ['package-lock.json'], declared_dependencies: 1, unbounded_dependencies: 0, unpinned_dependencies: 0, duplicate_dependencies: [] },
|
||
architecture: { nodes: [], edges: [], clusters: {}, truncated: false, total_nodes: 0, total_edges: 0 },
|
||
hotspots: [],
|
||
release_gate: null,
|
||
}
|
||
|
||
afterEach(() => cleanup())
|
||
|
||
describe('FindingsView', () => {
|
||
it('keeps findings scanable while exposing expandable rule details', () => {
|
||
const { container } = render(<FindingsView report={report} />)
|
||
const firstFinding = container.querySelector('details.finding') as HTMLDetailsElement
|
||
|
||
expect(firstFinding.open).toBe(false)
|
||
expect(screen.getByText('Potential credential assignment detected; value intentionally redacted')).toBeTruthy()
|
||
|
||
fireEvent.click(screen.getByText('Potential credential assignment detected; value intentionally redacted'))
|
||
|
||
expect(firstFinding.open).toBe(true)
|
||
expect(screen.getByText('−20 points')).toBeTruthy()
|
||
expect(screen.getByText('backend/app/main.py:245')).toBeTruthy()
|
||
expect(screen.getByText('security.secret-assignment')).toBeTruthy()
|
||
})
|
||
|
||
it('filters by severity and reports an actionable empty state', () => {
|
||
render(<FindingsView report={report} />)
|
||
const severity = screen.getByRole('combobox', { name: 'Filter findings by severity' })
|
||
const query = screen.getByRole('textbox', { name: 'Filter findings' })
|
||
|
||
fireEvent.change(severity, { target: { value: 'warning' } })
|
||
expect(screen.getByText(/Showing 1 of 2/)).toBeTruthy()
|
||
|
||
fireEvent.change(query, { target: { value: 'does-not-exist' } })
|
||
expect(screen.getByText('No findings match the current filters.')).toBeTruthy()
|
||
})
|
||
})
|