|
| 1 | +/** |
| 2 | + * When was this answer measured? |
| 3 | + * |
| 4 | + * THE DEFECT. The systems table listed platform, tier, criticality, exposure, |
| 5 | + * mode and owner — and nothing about when any of it was last looked at. On the |
| 6 | + * live sample estate seven systems rendered identically, and two of them |
| 7 | + * (DEV/300 and QAS/200) had never been scanned at all: no complete run, ever. |
| 8 | + * Above that table sat the sentence |
| 9 | + * |
| 10 | + * Open findings across 7 systems. |
| 11 | + * |
| 12 | + * which counted every REGISTERED system whether or not anything had measured |
| 13 | + * it. The figure was drawn from five and read as a claim about seven. |
| 14 | + * |
| 15 | + * The distinction these tests hold is the one the whole feature turns on: a |
| 16 | + * system nobody has scanned has no age. It must never render as an em dash |
| 17 | + * beside six populated cells, and never as "0 days". |
| 18 | + */ |
| 19 | +import { render, screen, waitFor } from '@testing-library/react' |
| 20 | +import { MemoryRouter } from 'react-router' |
| 21 | +import { beforeEach, describe, expect, it, vi } from 'vitest' |
| 22 | + |
| 23 | +import { Dashboard } from './Dashboard' |
| 24 | + |
| 25 | +vi.mock('../api/client', () => ({ |
| 26 | + dashboard: vi.fn(), |
| 27 | + csf: vi.fn(), |
| 28 | + domains: vi.fn(), |
| 29 | + ApiError: class ApiError extends Error { |
| 30 | + status: number |
| 31 | + constructor(status: number, message: string) { super(message); this.status = status } |
| 32 | + }, |
| 33 | +})) |
| 34 | +vi.mock('../lib/title', () => ({ useTitle: () => {} })) |
| 35 | + |
| 36 | +import { csf, dashboard, domains } from '../api/client' |
| 37 | + |
| 38 | +function system(over: Record<string, unknown>) { |
| 39 | + return { |
| 40 | + id: 1, landscape_id: 1, platform: 'abap', external_key: null, |
| 41 | + label: 'PRD/100', sid: 'PRD', client: '100', tier: 'prod', |
| 42 | + product: null, release: null, kernel_patch: null, btp_subaccount: null, |
| 43 | + criticality: 'critical', exposure_zone: 'internal', owner: null, |
| 44 | + tags: [], created_at: '2026-01-01T00:00:00Z', |
| 45 | + landscape_name: 'L', deployment_mode: 'rise_pce', |
| 46 | + last_assessed: '2026-09-01T00:00:00Z', days_since_assessed: 0, |
| 47 | + assessed_runs: 1, |
| 48 | + ...over, |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +function view(systems: unknown[], freshness: Record<string, unknown>) { |
| 53 | + return { |
| 54 | + summary: { |
| 55 | + by_severity: { CRITICAL: 1, HIGH: 2, MEDIUM: 3, LOW: 4 }, |
| 56 | + by_remediation_owner: { customer_fixable: 10 }, |
| 57 | + by_state: { open: 10 }, open_total: 10, |
| 58 | + expired_acceptances: 0, weak_identity: 0, regressed: 0, sod_trust: null, |
| 59 | + }, |
| 60 | + systems, |
| 61 | + freshness: { |
| 62 | + systems: systems.length, current: systems.length, stale: 0, |
| 63 | + never_assessed: 0, stale_after_days: 35, oldest_days: 0, |
| 64 | + never_assessed_labels: [], stale_labels: [], |
| 65 | + ...freshness, |
| 66 | + }, |
| 67 | + recent_runs: [], crq: null, crq_scenarios: [], |
| 68 | + } |
| 69 | +} |
| 70 | + |
| 71 | +function draw() { |
| 72 | + return render(<MemoryRouter><Dashboard /></MemoryRouter>) |
| 73 | +} |
| 74 | + |
| 75 | +beforeEach(() => { |
| 76 | + vi.clearAllMocks() |
| 77 | + vi.mocked(csf).mockRejectedValue(new Error('not under test')) |
| 78 | + vi.mocked(domains).mockRejectedValue(new Error('not under test')) |
| 79 | +}) |
| 80 | + |
| 81 | +describe('the estate says when it was last measured', () => { |
| 82 | + it('does not count a never-scanned system in a claim drawn from findings', async () => { |
| 83 | + vi.mocked(dashboard).mockResolvedValue(view( |
| 84 | + [system({ id: 1 }), system({ id: 2, label: 'DEV/300', last_assessed: null, |
| 85 | + days_since_assessed: null, assessed_runs: 0 })], |
| 86 | + { systems: 2, current: 1, never_assessed: 1, |
| 87 | + never_assessed_labels: ['DEV/300'] }, |
| 88 | + ) as never) |
| 89 | + draw() |
| 90 | + expect(await screen.findByText(/across 1 of 2 registered systems/)) |
| 91 | + .toBeInTheDocument() |
| 92 | + expect(screen.getByText(/never been assessed, so nothing here is a statement/)) |
| 93 | + .toBeInTheDocument() |
| 94 | + }) |
| 95 | + |
| 96 | + it('says "never", not a dash, for a system nothing has scanned', async () => { |
| 97 | + // An em dash is what the Owner column shows for "not recorded". Reusing it |
| 98 | + // here would put "nobody has ever looked at this system" in the same |
| 99 | + // visual class as "no owner set". |
| 100 | + vi.mocked(dashboard).mockResolvedValue(view( |
| 101 | + [system({ label: 'DEV/300', last_assessed: null, |
| 102 | + days_since_assessed: null, assessed_runs: 0 })], |
| 103 | + { systems: 1, current: 0, never_assessed: 1, oldest_days: null, |
| 104 | + never_assessed_labels: ['DEV/300'] }, |
| 105 | + ) as never) |
| 106 | + draw() |
| 107 | + expect(await screen.findByText('never')).toBeInTheDocument() |
| 108 | + }) |
| 109 | + |
| 110 | + it('names the stale systems and how old the oldest answer is', async () => { |
| 111 | + vi.mocked(dashboard).mockResolvedValue(view( |
| 112 | + [system({ label: 'D01/300', days_since_assessed: 90, |
| 113 | + last_assessed: '2026-06-03T00:00:00Z' })], |
| 114 | + { systems: 1, current: 0, stale: 1, oldest_days: 90, |
| 115 | + stale_labels: ['D01/300'] }, |
| 116 | + ) as never) |
| 117 | + draw() |
| 118 | + expect(await screen.findByText(/Last assessed over 35 days ago: D01\/300/)) |
| 119 | + .toBeInTheDocument() |
| 120 | + expect(screen.getByText(/oldest answer here is 90 days old/)) |
| 121 | + .toBeInTheDocument() |
| 122 | + expect(screen.getByText('90d ago')).toBeInTheDocument() |
| 123 | + }) |
| 124 | + |
| 125 | + it('stays quiet when every system is current', async () => { |
| 126 | + // A banner that is always on is a banner nobody reads. This one has to |
| 127 | + // stay silent on a healthy estate to mean anything on an unhealthy one. |
| 128 | + vi.mocked(dashboard).mockResolvedValue(view([system({})], {}) as never) |
| 129 | + draw() |
| 130 | + await screen.findByText(/across 1 of 1 registered system/) |
| 131 | + await waitFor(() => { |
| 132 | + expect(screen.queryByText(/never been assessed/)).not.toBeInTheDocument() |
| 133 | + expect(screen.queryByText(/Last assessed over/)).not.toBeInTheDocument() |
| 134 | + }) |
| 135 | + expect(screen.getByText('today')).toBeInTheDocument() |
| 136 | + }) |
| 137 | +}) |
0 commit comments