From 0b1076e0d6dfbc1ef67c4a7e955dd4a0c3de2526 Mon Sep 17 00:00:00 2001 From: mapleleafu Date: Thu, 13 Aug 2026 23:58:37 +0300 Subject: [PATCH] fix(scan): grade reports from alert.action Scan report no longer fetches GET /settings/security-policy. That map stays frozen after Rules & Policies migration, so CI kept grading against stale issueRules. Grade from each alert's resolved action on the full-scan payload instead. Alerts without an action are skipped. Assisted-by: Cursor Grok 4.6 --- CHANGELOG.md | 1 + .../cli/data/command-api-requirements.json | 4 +- .../src/commands/scan/fetch-report-data.mts | 145 ++------ .../cli/src/commands/scan/generate-report.mts | 324 +++++++++--------- .../src/commands/scan/output-scan-report.mts | 22 +- .../helpers/generate-report-test-helpers.mts | 15 +- .../integration/cli/cmd-scan-report.test.mts | 4 +- .../commands/scan/fetch-report-data.test.mts | 168 +-------- .../scan/generate-report-basic.test.mts | 73 ++-- .../scan/generate-report-fold.test.mts | 41 +-- .../scan/generate-report-shape.test.mts | 91 +---- .../generate-report-test-helpers.test.mts | 10 + .../commands/scan/generate-report.test.mts | 237 +++++++------ .../output-scan-report-short-mode.test.mts | 4 - .../commands/scan/output-scan-report.test.mts | 10 - 15 files changed, 423 insertions(+), 726 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b5a925ce..3fe6214875 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -240,6 +240,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ### Changed +- **`scan`** — grade `scan report` / `scan create --report` from each alert's resolved `action` on the scan instead of re-fetching the org security-policy map - **`cli`** — use direct env reads for HOME in 5 commands - **`publish`** — optimize CLI build and consolidate platform definitions - **`sea`** — parallelize binary injection for 8x faster builds diff --git a/packages/cli/data/command-api-requirements.json b/packages/cli/data/command-api-requirements.json index cbdf47dfb3..f5ca9c243e 100644 --- a/packages/cli/data/command-api-requirements.json +++ b/packages/cli/data/command-api-requirements.json @@ -101,8 +101,8 @@ "permissions": ["full-scans:create"] }, "scan:report": { - "quota": 2, - "permissions": ["full-scans:list", "security-policy:read"] + "quota": 1, + "permissions": ["full-scans:list"] }, "scan:view": { "quota": 1, diff --git a/packages/cli/src/commands/scan/fetch-report-data.mts b/packages/cli/src/commands/scan/fetch-report-data.mts index 9b107c2b2d..5c7867ab43 100644 --- a/packages/cli/src/commands/scan/fetch-report-data.mts +++ b/packages/cli/src/commands/scan/fetch-report-data.mts @@ -3,22 +3,15 @@ import { getDefaultLogger } from '@socketsecurity/lib-stable/logger/default' import { getDefaultSpinner } from '@socketsecurity/lib-stable/spinner/default' import { formatErrorWithDetail } from '../../util/error/errors.mjs' -import { - handleApiCallNoSpinner, - queryApiSafeText, -} from '../../util/socket/api.mjs' -import { setupSdk } from '../../util/socket/sdk.mjs' +import { queryApiSafeText } from '../../util/socket/api.mjs' import type { CResult } from '../../types.mts' import type { SocketArtifact } from '../../util/alert/artifact.mts' -import type { SetupSdkOptions } from '../../util/socket/sdk.mjs' -import type { SocketSdkSuccessResult } from '@socketsecurity/sdk-stable' const logger = getDefaultLogger() export type FetchScanData = { includeLicensePolicy?: boolean | undefined - sdkOpts?: SetupSdkOptions | undefined } /** @@ -29,24 +22,10 @@ export async function fetchScanData( orgSlug: string, scanId: string, options?: FetchScanData | undefined, -): Promise< - CResult<{ - scan: SocketArtifact[] - securityPolicy: SocketSdkSuccessResult<'getOrgSecurityPolicy'>['data'] - }> -> { - const { includeLicensePolicy, sdkOpts } = { - __proto__: null, - ...options, - } as FetchScanData +): Promise> { + const includeLicensePolicy = options?.includeLicensePolicy const spinner = getDefaultSpinner() - const sockSdkCResult = await setupSdk(sdkOpts) - if (!sockSdkCResult.ok) { - return sockSdkCResult - } - const sockSdk = sockSdkCResult.data - let policyStatus = 'requested…' let scanStatus = 'requested…' let finishedFetching = false @@ -55,25 +34,18 @@ export async function fetchScanData( updateProgress() } - function updatePolicy(status: string) { - policyStatus = status - updateProgress() - } - function updateProgress() { if (finishedFetching) { spinner.stop() - logger.info( - `Scan result: ${scanStatus}. Security policy: ${policyStatus}.`, - ) + logger.info(`Scan result: ${scanStatus}.`) } else { - spinner.start( - `Scan result: ${scanStatus}. Security policy: ${policyStatus}.`, - ) + spinner.start(`Scan result: ${scanStatus}.`) } } - async function fetchScanResult(): Promise> { + updateProgress() + + try { const result = await queryApiSafeText( `orgs/${orgSlug}/full-scans/${encodeURIComponent(scanId)}${includeLicensePolicy ? '?include_license_details=true' : ''}`, ) @@ -81,6 +53,8 @@ export async function fetchScanData( updateScan('response received') if (!result.ok) { + finishedFetching = true + updateProgress() return result } @@ -94,9 +68,11 @@ export async function fetchScanData( try { data.push(JSON.parse(line)) } catch (e) { - debug('Failed to parse report data line as JSON') + debug('Failed to parse report data line (invalid JSON)') debugDir({ error: e, line }) updateScan('received invalid JSON response') + finishedFetching = true + updateProgress() return { ok: false, message: 'Invalid Socket API response', @@ -107,92 +83,25 @@ export async function fetchScanData( } updateScan('success') - return { ok: true, data } - } - - async function fetchSecurityPolicy(): Promise< - CResult['data']> - > { - const result = (await handleApiCallNoSpinner( - sockSdk.getOrgSecurityPolicy(orgSlug), - 'GetOrgSecurityPolicy', - )) as CResult['data']> - - updatePolicy('received policy') - - return result - } - - updateProgress() - - const results = await Promise.allSettled([ - fetchScanResult().catch(e => { - updateScan('failure; unknown blocking error occurred') - return { - ok: false as const, - message: 'Socket API error', - cause: - formatErrorWithDetail('Error requesting scan', e) || - 'Error requesting scan: (no error message found)', - } - }), - fetchSecurityPolicy().catch(e => { - updatePolicy('failure; unknown blocking error occurred') - return { - ok: false as const, - message: 'Socket API error', - cause: - formatErrorWithDetail('Error requesting policy', e) || - 'Error requesting policy: (no error message found)', - } - }), - ]).finally(() => { finishedFetching = true updateProgress() - }) - - const scan: CResult = - results[0].status === 'fulfilled' - ? results[0].value - : { - ok: false as const, - message: 'Unexpected error', - cause: 'Promise rejected unexpectedly', - } - const securityPolicy: CResult< - SocketSdkSuccessResult<'getOrgSecurityPolicy'>['data'] - > = - results[1].status === 'fulfilled' - ? results[1].value - : { - ok: false as const, - message: 'Unexpected error', - cause: 'Promise rejected unexpectedly', - } - - if (!scan.ok) { - return scan - } - if (!securityPolicy.ok) { - return securityPolicy - } - - /* c8 ignore start - defensive: scan.data is always SocketArtifact[] from the loop above */ - if (!Array.isArray(scan.data)) { + return { + ok: true, + data: { + scan: data, + }, + } + } catch (e) { + updateScan('failure; unknown blocking error occurred') + finishedFetching = true + updateProgress() return { ok: false, - message: 'Failed to fetch', - cause: 'Was unable to fetch scan result, bailing', + message: 'Socket API error', + cause: + formatErrorWithDetail('Error requesting scan', e) || + 'Error requesting scan: (no error message found)', } } - /* c8 ignore stop */ - - return { - ok: true, - data: { - scan: scan.data satisfies SocketArtifact[], - securityPolicy: securityPolicy.data, - }, - } } diff --git a/packages/cli/src/commands/scan/generate-report.mts b/packages/cli/src/commands/scan/generate-report.mts index 985d22fb31..3e8ee36123 100644 --- a/packages/cli/src/commands/scan/generate-report.mts +++ b/packages/cli/src/commands/scan/generate-report.mts @@ -16,9 +16,11 @@ import { getSocketDevPackageOverviewUrlFromPurl } from '../../util/socket/url.mt import type { FOLD_SETTING, REPORT_LEVEL } from './types.mts' import type { CResult } from '../../types.mts' -import type { SocketArtifact } from '../../util/alert/artifact.mts' +import type { + ALERT_ACTION, + SocketArtifact, +} from '../../util/alert/artifact.mts' import type { SpinnerInstance } from '@socketsecurity/lib-stable/spinner/types' -import type { SocketSdkSuccessResult } from '@socketsecurity/sdk-stable' export type AlertKey = string export type EcoMap = Map @@ -49,6 +51,15 @@ export type ReportLeafNode = { manifest: string[] } +function isReportLeaf(value: unknown): value is ReportLeafNode { + return ( + typeof value === 'object' && + value !== null && + !(value instanceof Map) && + 'policy' in value + ) +} + export function addAlert( art: SocketArtifact, violations: ViolationsMap, @@ -59,49 +70,64 @@ export function addAlert( alert: NonNullable[number], policyAction: REPORT_LEVEL, ): void { - if (!violations.has(ecosystem)) { - violations.set(ecosystem, new Map()) + let ecoMap = violations.get(ecosystem) + if (!ecoMap) { + ecoMap = new Map() + violations.set(ecosystem, ecoMap) } - const ecoMap: EcoMap = violations.get(ecosystem)! if (fold === FOLD_SETTING_PKG) { - const existing = ecoMap.get(pkgName) as ReportLeafNode | undefined - if (!existing || isStricterPolicy(existing.policy, policyAction)) { + const existing = ecoMap.get(pkgName) + if ( + !isReportLeaf(existing) || + isStricterPolicy(existing.policy, policyAction) + ) { ecoMap.set(pkgName, createLeaf(art, alert, policyAction)) } - } else { - if (!ecoMap.has(pkgName)) { - ecoMap.set(pkgName, new Map()) + return + } + let pkgMap = ecoMap.get(pkgName) + if (!pkgMap || isReportLeaf(pkgMap)) { + pkgMap = new Map() + ecoMap.set(pkgName, pkgMap) + } + if (fold === FOLD_SETTING_VERSION) { + const existing = pkgMap.get(version) + if ( + !isReportLeaf(existing) || + isStricterPolicy(existing.policy, policyAction) + ) { + pkgMap.set(version, createLeaf(art, alert, policyAction)) } - const pkgMap = ecoMap.get(pkgName) as PackageMap - if (fold === FOLD_SETTING_VERSION) { - const existing = pkgMap.get(version) as ReportLeafNode | undefined - if (!existing || isStricterPolicy(existing.policy, policyAction)) { - pkgMap.set(version, createLeaf(art, alert, policyAction)) - } - } else { - if (!pkgMap.has(version)) { - pkgMap.set(version, new Map()) - } - const file = alert.file || UNKNOWN_VALUE - const verMap = pkgMap.get(version) as VersionMap - - if (fold === FOLD_SETTING_FILE) { - const existing = verMap.get(file) as ReportLeafNode | undefined - if (!existing || isStricterPolicy(existing.policy, policyAction)) { - verMap.set(file, createLeaf(art, alert, policyAction)) - } - } else { - if (!verMap.has(file)) { - verMap.set(file, new Map()) - } - const key = `${alert.type} at ${alert.start}:${alert.end}` - const fileMap: FileMap = verMap.get(file) as FileMap - const existing = fileMap.get(key) as ReportLeafNode | undefined - if (!existing || isStricterPolicy(existing.policy, policyAction)) { - fileMap.set(key, createLeaf(art, alert, policyAction)) - } - } + return + } + let verMap = pkgMap.get(version) + if (!verMap || isReportLeaf(verMap)) { + verMap = new Map() + pkgMap.set(version, verMap) + } + const file = alert.file || UNKNOWN_VALUE + if (fold === FOLD_SETTING_FILE) { + const existing = verMap.get(file) + if ( + !isReportLeaf(existing) || + isStricterPolicy(existing.policy, policyAction) + ) { + verMap.set(file, createLeaf(art, alert, policyAction)) } + return + } + let fileMap = verMap.get(file) + if (!fileMap || isReportLeaf(fileMap)) { + fileMap = new Map() + verMap.set(file, fileMap) + } + const key = `${alert.type} at ${alert.start}:${alert.end}` + const existing = fileMap.get(key) + if ( + !isReportLeaf(existing) || + isStricterPolicy(existing.policy, policyAction) + ) { + fileMap.set(key, createLeaf(art, alert, policyAction)) } } @@ -119,11 +145,19 @@ export function createLeaf( return leaf } +function isAlertAction(value: string | undefined): value is ALERT_ACTION { + return ( + value === REPORT_LEVEL_ERROR || + value === REPORT_LEVEL_WARN || + value === REPORT_LEVEL_MONITOR || + value === REPORT_LEVEL_IGNORE + ) +} + // Note: The returned cResult will only be ok:false when the generation // failed. It won't reflect the healthy state. export function generateReport( scan: SocketArtifact[], - securityPolicy: SocketSdkSuccessResult<'getOrgSecurityPolicy'>['data'], { fold, orgSlug, @@ -155,13 +189,12 @@ export function generateReport( // In the context of a report; // - the alert.severity is irrelevant - // - the securityPolicyDefault is irrelevant // - the report defaults to healthy:true with no alerts - // - the appearance of an alert will trigger the policy action; + // - the appearance of an alert will trigger its resolved action; // - error: healthy will end up as false, add alerts to report // - warn: healthy unchanged, add alerts to report - // - monitor/ignore: no action - // - defer: unknown, no action + // - monitor/ignore: no action unless reportLevel asks for them + // - missing action: skip the alert (do not fail the report) // Note: the server will emit alerts for license policy violations but // those are only included if you set the flag when requesting the scan @@ -175,121 +208,100 @@ export function generateReport( let healthy = true - const securityRules = securityPolicy.securityPolicyRules - if (securityRules) { - // Note: reportLevel: error > warn > monitor > ignore > defer - for (let i = 0, { length } = scan; i < length; i += 1) { - const artifact = scan[i]! - const { - alerts, - name: pkgName = UNKNOWN_VALUE, - type: ecosystem, - version = UNKNOWN_VALUE, - } = artifact - - // oxlint-disable-next-line socket/prefer-cached-for-loop -- call result is consumed, not a standalone statement - alerts?.forEach( - (alert: NonNullable[number]) => { - const alertName = alert.type // => policy[type] - const action = (securityRules[alertName]?.action || - '') as REPORT_LEVEL - switch (action) { - case REPORT_LEVEL_ERROR: { - healthy = false - if (!short) { - addAlert( - artifact, - violations, - fold, - ecosystem, - pkgName, - version, - alert, - action, - ) - } - break - } - case REPORT_LEVEL_WARN: { - if (!short && reportLevel !== REPORT_LEVEL_ERROR) { - addAlert( - artifact, - violations, - fold, - ecosystem, - pkgName, - version, - alert, - action, - ) - } - break - } - case REPORT_LEVEL_MONITOR: { - if ( - !short && - reportLevel !== REPORT_LEVEL_WARN && - reportLevel !== REPORT_LEVEL_ERROR - ) { - addAlert( - artifact, - violations, - fold, - ecosystem, - pkgName, - version, - alert, - action, - ) - } - break - } + // Note: reportLevel: error > warn > monitor > ignore > defer + for (let i = 0, { length } = scan; i < length; i += 1) { + const artifact = scan[i]! + const { + alerts, + name: pkgName = UNKNOWN_VALUE, + type: ecosystem, + version = UNKNOWN_VALUE, + } = artifact - case REPORT_LEVEL_IGNORE: { - if ( - !short && - reportLevel !== REPORT_LEVEL_MONITOR && - reportLevel !== REPORT_LEVEL_WARN && - reportLevel !== REPORT_LEVEL_ERROR - ) { - addAlert( - artifact, - violations, - fold, - ecosystem, - pkgName, - version, - alert, - action, - ) - } - break - } - - case REPORT_LEVEL_DEFER: { - // Not sure but ignore for now. Defer to later ;) - if (!short && reportLevel === REPORT_LEVEL_DEFER) { - addAlert( - artifact, - violations, - fold, - ecosystem, - pkgName, - version, - alert, - action, - ) - } - break - } + // oxlint-disable-next-line socket/prefer-cached-for-loop -- call result is consumed, not a standalone statement + alerts?.forEach((alert: NonNullable[number]) => { + const action = alert.action + if (!isAlertAction(action)) { + return + } + switch (action) { + case REPORT_LEVEL_ERROR: { + healthy = false + if (!short) { + addAlert( + artifact, + violations, + fold, + ecosystem, + pkgName, + version, + alert, + action, + ) + } + break + } + case REPORT_LEVEL_WARN: { + if (!short && reportLevel !== REPORT_LEVEL_ERROR) { + addAlert( + artifact, + violations, + fold, + ecosystem, + pkgName, + version, + alert, + action, + ) + } + break + } + case REPORT_LEVEL_MONITOR: { + if ( + !short && + reportLevel !== REPORT_LEVEL_WARN && + reportLevel !== REPORT_LEVEL_ERROR + ) { + addAlert( + artifact, + violations, + fold, + ecosystem, + pkgName, + version, + alert, + action, + ) + } + break + } - default: { - // This value was not emitted from the Socket API at the time of writing. - } + case REPORT_LEVEL_IGNORE: { + if ( + !short && + reportLevel !== REPORT_LEVEL_MONITOR && + reportLevel !== REPORT_LEVEL_WARN && + reportLevel !== REPORT_LEVEL_ERROR + ) { + addAlert( + artifact, + violations, + fold, + ecosystem, + pkgName, + version, + alert, + action, + ) } - }, - ) - } + break + } + + default: { + // This value was not emitted from the Socket API at the time of writing. + } + } + }) } spinner?.successAndStop(`Generated reported in ${Date.now() - now} ms`) diff --git a/packages/cli/src/commands/scan/output-scan-report.mts b/packages/cli/src/commands/scan/output-scan-report.mts index 137f555e56..86b54555e4 100644 --- a/packages/cli/src/commands/scan/output-scan-report.mts +++ b/packages/cli/src/commands/scan/output-scan-report.mts @@ -22,7 +22,6 @@ import type { ReportLeafNode, ScanReport } from './generate-report.mts' import type { FOLD_SETTING, REPORT_LEVEL } from './types.mts' import type { CResult, OutputKind } from '../../types.mts' import type { SocketArtifact } from '../../util/alert/artifact.mts' -import type { SocketSdkSuccessResult } from '@socketsecurity/sdk-stable' const logger = getDefaultLogger() export type OutputScanReportConfig = { @@ -118,7 +117,6 @@ export function formatLabelledPairs(pairs: Array<[string, string]>): string[] { export async function outputScanReport( result: CResult<{ scan: SocketArtifact[] - securityPolicy: SocketSdkSuccessResult<'getOrgSecurityPolicy'>['data'] }>, { filepath, @@ -145,18 +143,14 @@ export async function outputScanReport( } const spinner = getDefaultSpinner() - const scanReport = generateReport( - result.data.scan, - result.data.securityPolicy, - { - orgSlug, - scanId, - fold, - reportLevel, - short, - spinner, - }, - ) + const scanReport = generateReport(result.data.scan, { + orgSlug, + scanId, + fold, + reportLevel, + short, + spinner, + }) if (!scanReport.ok) { // Note: This means generation failed, it does not reflect the healthy state. diff --git a/packages/cli/test/helpers/generate-report-test-helpers.mts b/packages/cli/test/helpers/generate-report-test-helpers.mts index f04b5de893..4a09a26bad 100644 --- a/packages/cli/test/helpers/generate-report-test-helpers.mts +++ b/packages/cli/test/helpers/generate-report-test-helpers.mts @@ -1,4 +1,17 @@ -import type { SocketArtifact } from '../../src/util/alert/artifact.mts' +import type { ALERT_ACTION, SocketArtifact } from '../../src/util/alert/artifact.mts' + +/** + * Stamp a resolved policy action onto every alert in a scan fixture. + */ +export function withAlertAction( + scan: SocketArtifact[], + action: ALERT_ACTION, +): SocketArtifact[] { + return scan.map(artifact => ({ + ...artifact, + alerts: artifact.alerts?.map(alert => ({ ...alert, action })), + })) +} /** * Helper function to create a scan with environment variable alerts. diff --git a/packages/cli/test/integration/cli/cmd-scan-report.test.mts b/packages/cli/test/integration/cli/cmd-scan-report.test.mts index beaa4827e0..8238c69b1b 100644 --- a/packages/cli/test/integration/cli/cmd-scan-report.test.mts +++ b/packages/cli/test/integration/cli/cmd-scan-report.test.mts @@ -39,8 +39,8 @@ describe('socket scan report', async () => { $ socket scan report [options] [OUTPUT_PATH] API Token Requirements - - Quota: 2 units - - Permissions: full-scans:list and security-policy:read + - Quota: 1 unit + - Permissions: full-scans:list Options --fold Fold reported alerts to some degree (default 'none') diff --git a/packages/cli/test/unit/commands/scan/fetch-report-data.test.mts b/packages/cli/test/unit/commands/scan/fetch-report-data.test.mts index 4e59f5a9c2..1a9bb2dfe3 100644 --- a/packages/cli/test/unit/commands/scan/fetch-report-data.test.mts +++ b/packages/cli/test/unit/commands/scan/fetch-report-data.test.mts @@ -4,10 +4,8 @@ * Purpose: Tests fetching detailed scan report data via the Socket API. * Retrieves comprehensive scan results including alerts and scores. * - * Test Coverage: - Successful API operation - SDK setup failure handling - API - * call error scenarios - Custom SDK options (API tokens, base URLs) - Detailed - * report retrieval - Alert data - Score information - Null prototype usage for - * security. + * Test Coverage: - Successful API operation - API call error scenarios - + * Detailed report retrieval - Alert data - Score information. * * Testing Approach: Uses SDK test helpers to mock Socket API interactions. * Validates comprehensive error handling and API integration. @@ -17,12 +15,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { createErrorResult } from '../../../../test/helpers/index.mts' - describe('fetchScanData', () => { - let mockSetupSdk: ReturnType let mockQueryApiSafeText: ReturnType - let mockHandleApiCallNoSpinner: ReturnType let mockFormatErrorWithDetail: ReturnType let mockLogger: Record> let mockSpinner: Record> @@ -30,9 +24,7 @@ describe('fetchScanData', () => { beforeEach(async () => { vi.resetModules() - mockSetupSdk = vi.fn() mockQueryApiSafeText = vi.fn() - mockHandleApiCallNoSpinner = vi.fn() mockFormatErrorWithDetail = vi.fn((msg, _e) => msg) mockLogger = { @@ -64,41 +56,15 @@ describe('fetchScanData', () => { })) vi.doMock(import('../../../../src/util/socket/api.mjs'), () => ({ - handleApiCallNoSpinner: mockHandleApiCallNoSpinner, queryApiSafeText: mockQueryApiSafeText, })) - vi.doMock(import('../../../../src/util/socket/sdk.mjs'), () => ({ - setupSdk: mockSetupSdk, - })) - vi.doMock(import('../../../../src/util/error/errors.mjs'), () => ({ formatErrorWithDetail: mockFormatErrorWithDetail, })) }) - it('handles SDK setup failure', async () => { - const error = createErrorResult('Failed to setup SDK', { - code: 1, - cause: 'Invalid configuration', - }) - - mockSetupSdk.mockResolvedValue(error) - - const { fetchScanData } = - await import('../../../../src/commands/scan/fetch-report-data.mts') - - const result = await fetchScanData('test-org', 'scan-123') - - expect(result.ok).toBe(false) - expect(result.message).toBe('Failed to setup SDK') - expect(mockSetupSdk).toHaveBeenCalled() - }) - it('fetches scan data successfully', async () => { - const mockSdk = { getOrgSecurityPolicy: vi.fn() } - mockSetupSdk.mockResolvedValue({ ok: true, data: mockSdk }) - const scanData = [ { id: '1', type: 'alert', severity: 'high' }, { id: '2', type: 'alert', severity: 'medium' }, @@ -110,11 +76,6 @@ describe('fetchScanData', () => { data: ndJsonResponse, }) - mockHandleApiCallNoSpinner.mockResolvedValue({ - ok: true, - data: { rules: [] }, - }) - const { fetchScanData } = await import('../../../../src/commands/scan/fetch-report-data.mts') @@ -123,27 +84,17 @@ describe('fetchScanData', () => { expect(result.ok).toBe(true) if (result.ok) { expect(result.data.scan).toHaveLength(2) - expect(result.data.securityPolicy).toEqual({ rules: [] }) } expect(mockSpinner.start).toHaveBeenCalled() expect(mockSpinner.stop).toHaveBeenCalled() }) it('handles invalid JSON in scan response', async () => { - const mockSdk = { getOrgSecurityPolicy: vi.fn() } - mockSetupSdk.mockResolvedValue({ ok: true, data: mockSdk }) - - // Return invalid JSON. mockQueryApiSafeText.mockResolvedValue({ ok: true, data: 'not valid json\n{"valid": true}', }) - mockHandleApiCallNoSpinner.mockResolvedValue({ - ok: true, - data: { rules: [] }, - }) - const { fetchScanData } = await import('../../../../src/commands/scan/fetch-report-data.mts') @@ -156,43 +107,12 @@ describe('fetchScanData', () => { }) it('handles scan result API error', async () => { - const mockSdk = { getOrgSecurityPolicy: vi.fn() } - mockSetupSdk.mockResolvedValue({ ok: true, data: mockSdk }) - mockQueryApiSafeText.mockResolvedValue({ ok: false, message: 'API error', cause: 'Network failure', }) - mockHandleApiCallNoSpinner.mockResolvedValue({ - ok: true, - data: { rules: [] }, - }) - - const { fetchScanData } = - await import('../../../../src/commands/scan/fetch-report-data.mts') - - const result = await fetchScanData('test-org', 'scan-123') - - expect(result.ok).toBe(false) - }) - - it('handles security policy API error', async () => { - const mockSdk = { getOrgSecurityPolicy: vi.fn() } - mockSetupSdk.mockResolvedValue({ ok: true, data: mockSdk }) - - mockQueryApiSafeText.mockResolvedValue({ - ok: true, - data: '{"id": "1"}', - }) - - mockHandleApiCallNoSpinner.mockResolvedValue({ - ok: false, - message: 'Policy fetch failed', - cause: 'Forbidden', - }) - const { fetchScanData } = await import('../../../../src/commands/scan/fetch-report-data.mts') @@ -202,19 +122,11 @@ describe('fetchScanData', () => { }) it('includes license policy when specified', async () => { - const mockSdk = { getOrgSecurityPolicy: vi.fn() } - mockSetupSdk.mockResolvedValue({ ok: true, data: mockSdk }) - mockQueryApiSafeText.mockResolvedValue({ ok: true, data: '{"id": "1"}', }) - mockHandleApiCallNoSpinner.mockResolvedValue({ - ok: true, - data: { rules: [] }, - }) - const { fetchScanData } = await import('../../../../src/commands/scan/fetch-report-data.mts') @@ -226,16 +138,8 @@ describe('fetchScanData', () => { }) it('handles thrown errors during scan fetch', async () => { - const mockSdk = { getOrgSecurityPolicy: vi.fn() } - mockSetupSdk.mockResolvedValue({ ok: true, data: mockSdk }) - mockQueryApiSafeText.mockRejectedValue(new Error('Network timeout')) - mockHandleApiCallNoSpinner.mockResolvedValue({ - ok: true, - data: { rules: [] }, - }) - const { fetchScanData } = await import('../../../../src/commands/scan/fetch-report-data.mts') @@ -244,56 +148,7 @@ describe('fetchScanData', () => { expect(result.ok).toBe(false) }) - it('handles thrown errors during policy fetch', async () => { - const mockSdk = { getOrgSecurityPolicy: vi.fn() } - mockSetupSdk.mockResolvedValue({ ok: true, data: mockSdk }) - - mockQueryApiSafeText.mockResolvedValue({ - ok: true, - data: '{"id": "1"}', - }) - - mockHandleApiCallNoSpinner.mockRejectedValue(new Error('Auth failed')) - - const { fetchScanData } = - await import('../../../../src/commands/scan/fetch-report-data.mts') - - const result = await fetchScanData('test-org', 'scan-123') - - expect(result.ok).toBe(false) - }) - - it('passes SDK options when provided', async () => { - const mockSdk = { getOrgSecurityPolicy: vi.fn() } - mockSetupSdk.mockResolvedValue({ ok: true, data: mockSdk }) - - mockQueryApiSafeText.mockResolvedValue({ - ok: true, - data: '{"id": "1"}', - }) - - mockHandleApiCallNoSpinner.mockResolvedValue({ - ok: true, - data: { rules: [] }, - }) - - const { fetchScanData } = - await import('../../../../src/commands/scan/fetch-report-data.mts') - - await fetchScanData('test-org', 'scan-123', { - sdkOpts: { apiToken: 'custom-token' }, - }) - - expect(mockSetupSdk).toHaveBeenCalledWith( - expect.objectContaining({ apiToken: 'custom-token' }), - ) - }) - it('filters empty lines from ndjson response', async () => { - const mockSdk = { getOrgSecurityPolicy: vi.fn() } - mockSetupSdk.mockResolvedValue({ ok: true, data: mockSdk }) - - // Include empty lines. const ndJsonResponse = '{"id": "1"}\n\n{"id": "2"}\n\n' mockQueryApiSafeText.mockResolvedValue({ @@ -301,11 +156,6 @@ describe('fetchScanData', () => { data: ndJsonResponse, }) - mockHandleApiCallNoSpinner.mockResolvedValue({ - ok: true, - data: { rules: [] }, - }) - const { fetchScanData } = await import('../../../../src/commands/scan/fetch-report-data.mts') @@ -317,29 +167,17 @@ describe('fetchScanData', () => { } }) - it('returns "Failed to fetch" error when scan data is empty (parses to empty)', async () => { - const mockSdk = { getOrgSecurityPolicy: vi.fn() } - mockSetupSdk.mockResolvedValue({ ok: true, data: mockSdk }) - - // Empty/whitespace input produces a scan.data that is undefined / not an array. + it('returns an empty scan array for whitespace-only NDJSON', async () => { mockQueryApiSafeText.mockResolvedValue({ ok: true, - // Single completely empty line — yields empty array which IS an Array. data: ' ', }) - mockHandleApiCallNoSpinner.mockResolvedValue({ - ok: true, - data: { rules: [] }, - }) - const { fetchScanData } = await import('../../../../src/commands/scan/fetch-report-data.mts') const result = await fetchScanData('test-org', 'scan-123') - // Whitespace-only NDJSON should still parse to an array, possibly empty - // — not the "not an array" branch. Fine, just confirm no crash. if (result.ok) { expect(Array.isArray(result.data.scan)).toBe(true) } diff --git a/packages/cli/test/unit/commands/scan/generate-report-basic.test.mts b/packages/cli/test/unit/commands/scan/generate-report-basic.test.mts index f618cf0758..dfd4e8e0c4 100644 --- a/packages/cli/test/unit/commands/scan/generate-report-basic.test.mts +++ b/packages/cli/test/unit/commands/scan/generate-report-basic.test.mts @@ -17,22 +17,14 @@ import { describe, expect, it } from 'vitest' import { generateReport } from '../../../../src/commands/scan/generate-report.mts' -import type { SocketSdkSuccessResult } from '@socketsecurity/sdk-stable' - -type SecurityPolicyData = SocketSdkSuccessResult<'getOrgSecurityPolicy'>['data'] - describe('generate-report - basic functionality', () => { it('should accept empty args', () => { - const result = generateReport( - [], - { securityPolicyRules: [] } as SecurityPolicyData, - { - orgSlug: 'fakeOrg', - scanId: 'scan-ai-dee', - fold: 'none', - reportLevel: 'warn', - }, - ) + const result = generateReport([], { + orgSlug: 'fakeOrg', + scanId: 'scan-ai-dee', + fold: 'none', + reportLevel: 'warn', + }) expect(result).toMatchInlineSnapshot(` { @@ -51,20 +43,13 @@ describe('generate-report - basic functionality', () => { `) }) - it('should handle empty security policy rules', () => { - const result = generateReport( - [], - { - securityPolicyRules: {}, - securityPolicyDefault: 'medium', - }, - { - orgSlug: 'testOrg', - scanId: 'test-scan-id', - fold: 'none', - reportLevel: 'error', - }, - ) + it('should handle an empty scan', () => { + const result = generateReport([], { + orgSlug: 'testOrg', + scanId: 'test-scan-id', + fold: 'none', + reportLevel: 'error', + }) expect(result.ok).toBe(true) expect(result.data.healthy).toBe(true) @@ -73,16 +58,12 @@ describe('generate-report - basic functionality', () => { }) it('should set correct options in result', () => { - const result = generateReport( - [], - { securityPolicyRules: [] } as SecurityPolicyData, - { - orgSlug: 'myOrg', - scanId: 'my-scan-123', - fold: 'pkg', - reportLevel: 'error', - }, - ) + const result = generateReport([], { + orgSlug: 'myOrg', + scanId: 'my-scan-123', + fold: 'pkg', + reportLevel: 'error', + }) expect(result.data.options).toEqual({ fold: 'pkg', @@ -93,16 +74,12 @@ describe('generate-report - basic functionality', () => { }) it('should return ok:true for successful report generation', () => { - const result = generateReport( - [], - { securityPolicyRules: [] } as SecurityPolicyData, - { - orgSlug: 'testOrg', - scanId: 'test-id', - fold: 'type', - reportLevel: 'warn', - }, - ) + const result = generateReport([], { + orgSlug: 'testOrg', + scanId: 'test-id', + fold: 'type', + reportLevel: 'warn', + }) expect(result.ok).toBe(true) expect(result).toHaveProperty('data') diff --git a/packages/cli/test/unit/commands/scan/generate-report-fold.test.mts b/packages/cli/test/unit/commands/scan/generate-report-fold.test.mts index 5003d62b1d..60d53cbe91 100644 --- a/packages/cli/test/unit/commands/scan/generate-report-fold.test.mts +++ b/packages/cli/test/unit/commands/scan/generate-report-fold.test.mts @@ -18,6 +18,7 @@ import { describe, expect, it } from 'vitest' import { getScanWithEnvVars, getScanWithMultiplePackages, + withAlertAction, } from '../../../helpers/generate-report-test-helpers.mts' import { generateReport } from '../../../../src/commands/scan/generate-report.mts' @@ -27,15 +28,7 @@ describe('generate-report - fold functionality', () => { describe('fold=none', () => { it('should not fold anything when fold=none', () => { const result = generateReport( - getScanWithEnvVars(), - { - securityPolicyRules: { - envVars: { - action: 'error', - }, - }, - securityPolicyDefault: 'medium', - }, + withAlertAction(getScanWithEnvVars(), 'error'), { orgSlug: 'fakeOrg', scanId: 'scan-ai-dee', @@ -56,15 +49,7 @@ describe('generate-report - fold functionality', () => { describe('fold=pkg', () => { it('should fold alerts by package when fold=pkg', () => { const result = generateReport( - getScanWithMultiplePackages(), - { - securityPolicyRules: { - envVars: { - action: 'error', - }, - }, - securityPolicyDefault: 'medium', - }, + withAlertAction(getScanWithMultiplePackages(), 'error'), { orgSlug: 'fakeOrg', scanId: 'scan-ai-dee', @@ -93,15 +78,7 @@ describe('generate-report - fold functionality', () => { describe('fold=type', () => { it('should fold alerts by type when fold=type', () => { const result = generateReport( - getScanWithMultiplePackages(), - { - securityPolicyRules: { - envVars: { - action: 'error', - }, - }, - securityPolicyDefault: 'medium', - }, + withAlertAction(getScanWithMultiplePackages(), 'error'), { orgSlug: 'fakeOrg', scanId: 'scan-ai-dee', @@ -119,15 +96,7 @@ describe('generate-report - fold functionality', () => { describe('fold=all', () => { it('should fold all alerts when fold=all', () => { const result = generateReport( - getScanWithMultiplePackages(), - { - securityPolicyRules: { - envVars: { - action: 'error', - }, - }, - securityPolicyDefault: 'medium', - }, + withAlertAction(getScanWithMultiplePackages(), 'error'), { orgSlug: 'fakeOrg', scanId: 'scan-ai-dee', diff --git a/packages/cli/test/unit/commands/scan/generate-report-shape.test.mts b/packages/cli/test/unit/commands/scan/generate-report-shape.test.mts index a1dd0a21e4..48c9118a49 100644 --- a/packages/cli/test/unit/commands/scan/generate-report-shape.test.mts +++ b/packages/cli/test/unit/commands/scan/generate-report-shape.test.mts @@ -18,35 +18,21 @@ import { describe, expect, it } from 'vitest' import { getScanWithEnvVars, getSimpleCleanScan, + withAlertAction, } from '../../../helpers/generate-report-test-helpers.mts' import { generateReport } from '../../../../src/commands/scan/generate-report.mts' import type { ScanReport } from '../../../../src/commands/scan/generate-report.mts' -import type { SocketSdkSuccessResult } from '@socketsecurity/sdk-stable' - -// biome-ignore lint/correctness/noUnusedVariables: Destructuring import for test setup -type SecurityPolicyData = SocketSdkSuccessResult<'getOrgSecurityPolicy'>['data'] describe('generate-report - report shape', () => { describe('report-level=warn', () => { it('should return a healthy report without alerts when there are no violations', () => { - const result = generateReport( - getSimpleCleanScan(), - { - securityPolicyRules: { - gptSecurity: { - action: 'ignore', - }, - }, - securityPolicyDefault: 'medium', - }, - { - orgSlug: 'fakeOrg', - scanId: 'scan-ai-dee', - fold: 'none', - reportLevel: 'warn', - }, - ) + const result = generateReport(getSimpleCleanScan(), { + orgSlug: 'fakeOrg', + scanId: 'scan-ai-dee', + fold: 'none', + reportLevel: 'warn', + }) expect(result).toMatchInlineSnapshot(` { @@ -70,15 +56,7 @@ describe('generate-report - report shape', () => { it('should return a sick report with alert when an alert violates at error', () => { const result = generateReport( - getScanWithEnvVars(), - { - securityPolicyRules: { - envVars: { - action: 'error', - }, - }, - securityPolicyDefault: 'medium', - }, + withAlertAction(getScanWithEnvVars(), 'error'), { orgSlug: 'fakeOrg', scanId: 'scan-ai-dee', @@ -94,15 +72,7 @@ describe('generate-report - report shape', () => { it('should return a healthy report without alerts when an alert violates at warn', () => { const result = generateReport( - getScanWithEnvVars(), - { - securityPolicyRules: { - envVars: { - action: 'warn', - }, - }, - securityPolicyDefault: 'medium', - }, + withAlertAction(getScanWithEnvVars(), 'warn'), { orgSlug: 'fakeOrg', scanId: 'scan-ai-dee', @@ -119,23 +89,12 @@ describe('generate-report - report shape', () => { describe('report-level=error', () => { it('should return a healthy report without alerts when there are no violations', () => { - const result = generateReport( - getSimpleCleanScan(), - { - securityPolicyRules: { - gptSecurity: { - action: 'ignore', - }, - }, - securityPolicyDefault: 'medium', - }, - { - orgSlug: 'fakeOrg', - scanId: 'scan-ai-dee', - fold: 'none', - reportLevel: 'error', - }, - ) + const result = generateReport(getSimpleCleanScan(), { + orgSlug: 'fakeOrg', + scanId: 'scan-ai-dee', + fold: 'none', + reportLevel: 'error', + }) expect(result.ok).toBe(true) expect(result.ok && result.data.healthy).toBe(true) @@ -144,15 +103,7 @@ describe('generate-report - report shape', () => { it('should return a sick report with alert when an alert violates at error', () => { const result = generateReport( - getScanWithEnvVars(), - { - securityPolicyRules: { - envVars: { - action: 'error', - }, - }, - securityPolicyDefault: 'medium', - }, + withAlertAction(getScanWithEnvVars(), 'error'), { orgSlug: 'fakeOrg', scanId: 'scan-ai-dee', @@ -168,15 +119,7 @@ describe('generate-report - report shape', () => { it('should return a healthy report without alerts when an alert violates at warn', () => { const result = generateReport( - getScanWithEnvVars(), - { - securityPolicyRules: { - envVars: { - action: 'warn', - }, - }, - securityPolicyDefault: 'medium', - }, + withAlertAction(getScanWithEnvVars(), 'warn'), { orgSlug: 'fakeOrg', scanId: 'scan-ai-dee', diff --git a/packages/cli/test/unit/commands/scan/generate-report-test-helpers.test.mts b/packages/cli/test/unit/commands/scan/generate-report-test-helpers.test.mts index f8c1114cba..fb65f1a08b 100644 --- a/packages/cli/test/unit/commands/scan/generate-report-test-helpers.test.mts +++ b/packages/cli/test/unit/commands/scan/generate-report-test-helpers.test.mts @@ -16,6 +16,7 @@ import { getScanWithEnvVars, getScanWithMultiplePackages, getSimpleCleanScan, + withAlertAction, } from '../../../helpers/generate-report-test-helpers.mts' describe('generate-report-test-helpers', () => { @@ -111,4 +112,13 @@ describe('generate-report-test-helpers', () => { expect(scan[1].manifestFiles.length).toBe(1) }) }) + + describe('withAlertAction', () => { + it('stamps action onto every alert', () => { + const scan = withAlertAction(getScanWithEnvVars(), 'error') + + expect(scan[0].alerts[0].action).toBe('error') + expect(scan[0].alerts[1].action).toBe('error') + }) + }) }) diff --git a/packages/cli/test/unit/commands/scan/generate-report.test.mts b/packages/cli/test/unit/commands/scan/generate-report.test.mts index 334cdbf080..93693b3b71 100644 --- a/packages/cli/test/unit/commands/scan/generate-report.test.mts +++ b/packages/cli/test/unit/commands/scan/generate-report.test.mts @@ -3,8 +3,8 @@ * * Purpose: Tests the report generation from scan artifacts. * - * Test Coverage: - generateReport function - Policy action handling (error, - * warn, monitor, ignore, defer) - Fold settings, pkg, version, file - Report + * Test Coverage: - generateReport function - Alert action handling (error, + * warn, monitor, ignore) - Fold settings, pkg, version, file - Report * level filtering - Health status determination. * * Related Files: - src/commands/scan/generate-report.mts (implementation) @@ -26,7 +26,6 @@ import { FOLD_SETTING_VERSION, } from '../../../../src/constants/cli.mts' import { - REPORT_LEVEL_DEFER, REPORT_LEVEL_ERROR, REPORT_LEVEL_IGNORE, REPORT_LEVEL_MONITOR, @@ -51,12 +50,6 @@ describe('generate-report', () => { ...overrides, }) as SocketArtifact - const createSecurityPolicy = ( - rules: Record = {}, - ) => ({ - securityPolicyRules: rules, - }) - const defaultOptions = { fold: FOLD_SETTING_NONE, orgSlug: 'my-org', @@ -66,9 +59,8 @@ describe('generate-report', () => { it('returns healthy report when no alerts', () => { const scan = [createArtifact()] - const policy = createSecurityPolicy() - const result = generateReport(scan, policy, defaultOptions) + const result = generateReport(scan, defaultOptions) expect(result.ok).toBe(true) expect(result.data).toEqual( @@ -82,9 +74,8 @@ describe('generate-report', () => { it('returns short report when short option is true', () => { const scan = [createArtifact()] - const policy = createSecurityPolicy() - const result = generateReport(scan, policy, { + const result = generateReport(scan, { ...defaultOptions, short: true, }) @@ -96,14 +87,19 @@ describe('generate-report', () => { it('marks unhealthy when error policy alerts exist', () => { const scan = [ createArtifact({ - alerts: [{ type: 'badAlert', file: 'index.js', start: 0, end: 10 }], + alerts: [ + { + type: 'badAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'error', + }, + ], }), ] - const policy = createSecurityPolicy({ - badAlert: { action: 'error' }, - }) - const result = generateReport(scan, policy, defaultOptions) + const result = generateReport(scan, defaultOptions) expect(result.ok).toBe(true) expect(result.data).toEqual( @@ -117,14 +113,19 @@ describe('generate-report', () => { it('stays healthy with warn policy alerts', () => { const scan = [ createArtifact({ - alerts: [{ type: 'warnAlert', file: 'index.js', start: 0, end: 10 }], + alerts: [ + { + type: 'warnAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'warn', + }, + ], }), ] - const policy = createSecurityPolicy({ - warnAlert: { action: 'warn' }, - }) - const result = generateReport(scan, policy, { + const result = generateReport(scan, { ...defaultOptions, reportLevel: REPORT_LEVEL_WARN, }) @@ -140,14 +141,19 @@ describe('generate-report', () => { it('includes warn alerts when reportLevel is warn', () => { const scan = [ createArtifact({ - alerts: [{ type: 'warnAlert', file: 'index.js', start: 0, end: 10 }], + alerts: [ + { + type: 'warnAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'warn', + }, + ], }), ] - const policy = createSecurityPolicy({ - warnAlert: { action: 'warn' }, - }) - const result = generateReport(scan, policy, { + const result = generateReport(scan, { ...defaultOptions, reportLevel: REPORT_LEVEL_WARN, }) @@ -160,14 +166,19 @@ describe('generate-report', () => { it('excludes warn alerts when reportLevel is error', () => { const scan = [ createArtifact({ - alerts: [{ type: 'warnAlert', file: 'index.js', start: 0, end: 10 }], + alerts: [ + { + type: 'warnAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'warn', + }, + ], }), ] - const policy = createSecurityPolicy({ - warnAlert: { action: 'warn' }, - }) - const result = generateReport(scan, policy, { + const result = generateReport(scan, { ...defaultOptions, reportLevel: REPORT_LEVEL_ERROR, }) @@ -181,15 +192,18 @@ describe('generate-report', () => { const scan = [ createArtifact({ alerts: [ - { type: 'monitorAlert', file: 'index.js', start: 0, end: 10 }, + { + type: 'monitorAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'monitor', + }, ], }), ] - const policy = createSecurityPolicy({ - monitorAlert: { action: 'monitor' }, - }) - const result = generateReport(scan, policy, { + const result = generateReport(scan, { ...defaultOptions, reportLevel: REPORT_LEVEL_MONITOR, }) @@ -203,15 +217,18 @@ describe('generate-report', () => { const scan = [ createArtifact({ alerts: [ - { type: 'ignoreAlert', file: 'index.js', start: 0, end: 10 }, + { + type: 'ignoreAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'ignore', + }, ], }), ] - const policy = createSecurityPolicy({ - ignoreAlert: { action: 'ignore' }, - }) - const result = generateReport(scan, policy, { + const result = generateReport(scan, { ...defaultOptions, reportLevel: REPORT_LEVEL_IGNORE, }) @@ -221,36 +238,43 @@ describe('generate-report', () => { expect(data.alerts.size).toBeGreaterThan(0) }) - it('includes defer alerts when reportLevel is defer', () => { + it('skips alerts that have no resolved action', () => { const scan = [ createArtifact({ alerts: [{ type: 'deferAlert', file: 'index.js', start: 0, end: 10 }], }), ] - const policy = createSecurityPolicy({ - deferAlert: { action: 'defer' }, - }) - const result = generateReport(scan, policy, { + const result = generateReport(scan, { ...defaultOptions, - reportLevel: REPORT_LEVEL_DEFER, + reportLevel: REPORT_LEVEL_WARN, }) expect(result.ok).toBe(true) + expect(result.data).toEqual( + expect.objectContaining({ + healthy: true, + }), + ) const data = result.data as { alerts: Map } - expect(data.alerts.size).toBeGreaterThan(0) + expect(data.alerts.size).toBe(0) }) describe('fold settings', () => { const alertedArtifact = createArtifact({ - alerts: [{ type: 'badAlert', file: 'index.js', start: 0, end: 10 }], - }) - const errorPolicy = createSecurityPolicy({ - badAlert: { action: 'error' }, + alerts: [ + { + type: 'badAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'error', + }, + ], }) it('folds by package when fold is pkg', () => { - const result = generateReport([alertedArtifact], errorPolicy, { + const result = generateReport([alertedArtifact], { ...defaultOptions, fold: FOLD_SETTING_PKG, }) @@ -265,7 +289,7 @@ describe('generate-report', () => { }) it('folds by version when fold is version', () => { - const result = generateReport([alertedArtifact], errorPolicy, { + const result = generateReport([alertedArtifact], { ...defaultOptions, fold: FOLD_SETTING_VERSION, }) @@ -281,7 +305,7 @@ describe('generate-report', () => { }) it('folds by file when fold is file', () => { - const result = generateReport([alertedArtifact], errorPolicy, { + const result = generateReport([alertedArtifact], { ...defaultOptions, fold: FOLD_SETTING_FILE, }) @@ -298,7 +322,7 @@ describe('generate-report', () => { }) it('does not fold when fold is none', () => { - const result = generateReport([alertedArtifact], errorPolicy, { + const result = generateReport([alertedArtifact], { ...defaultOptions, fold: FOLD_SETTING_NONE, }) @@ -322,14 +346,19 @@ describe('generate-report', () => { createArtifact({ name: undefined as unknown, version: undefined as unknown, - alerts: [{ type: 'badAlert', file: 'index.js', start: 0, end: 10 }], + alerts: [ + { + type: 'badAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'error', + }, + ], }), ] - const policy = createSecurityPolicy({ - badAlert: { action: 'error' }, - }) - const result = generateReport(scan, policy, defaultOptions) + const result = generateReport(scan, defaultOptions) expect(result.ok).toBe(true) expect(result.data).toEqual( @@ -343,14 +372,19 @@ describe('generate-report', () => { const scan = [ createArtifact({ manifestFiles: undefined as unknown, - alerts: [{ type: 'badAlert', file: 'index.js', start: 0, end: 10 }], + alerts: [ + { + type: 'badAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'error', + }, + ], }), ] - const policy = createSecurityPolicy({ - badAlert: { action: 'error' }, - }) - const result = generateReport(scan, policy, defaultOptions) + const result = generateReport(scan, defaultOptions) expect(result.ok).toBe(true) }) @@ -358,14 +392,11 @@ describe('generate-report', () => { it('handles alerts with no file', () => { const scan = [ createArtifact({ - alerts: [{ type: 'badAlert', start: 0, end: 10 }], + alerts: [{ type: 'badAlert', start: 0, end: 10, action: 'error' }], }), ] - const policy = createSecurityPolicy({ - badAlert: { action: 'error' }, - }) - const result = generateReport(scan, policy, { + const result = generateReport(scan, { ...defaultOptions, fold: FOLD_SETTING_NONE, }) @@ -373,19 +404,22 @@ describe('generate-report', () => { expect(result.ok).toBe(true) }) - it('handles unknown policy actions', () => { + it('handles unknown alert actions', () => { const scan = [ createArtifact({ alerts: [ - { type: 'unknownAlert', file: 'index.js', start: 0, end: 10 }, + { + type: 'unknownAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'unknown-action' as never, + }, ], }), ] - const policy = createSecurityPolicy({ - unknownAlert: { action: 'unknown-action' }, - }) - const result = generateReport(scan, policy, defaultOptions) + const result = generateReport(scan, defaultOptions) expect(result.ok).toBe(true) expect(result.data).toEqual( @@ -395,15 +429,14 @@ describe('generate-report', () => { ) }) - it('handles missing security policy rules', () => { + it('skips alerts when action is missing', () => { const scan = [ createArtifact({ alerts: [{ type: 'badAlert', file: 'index.js', start: 0, end: 10 }], }), ] - const policy = {} // No securityPolicyRules. - const result = generateReport(scan, policy, defaultOptions) + const result = generateReport(scan, defaultOptions) expect(result.ok).toBe(true) expect(result.data).toEqual( @@ -417,17 +450,25 @@ describe('generate-report', () => { const scan = [ createArtifact({ alerts: [ - { type: 'warnAlert', file: 'index.js', start: 0, end: 10 }, - { type: 'errorAlert', file: 'index.js', start: 0, end: 10 }, + { + type: 'warnAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'warn', + }, + { + type: 'errorAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'error', + }, ], }), ] - const policy = createSecurityPolicy({ - warnAlert: { action: 'warn' }, - errorAlert: { action: 'error' }, - }) - const result = generateReport(scan, policy, { + const result = generateReport(scan, { ...defaultOptions, fold: FOLD_SETTING_PKG, reportLevel: REPORT_LEVEL_WARN, @@ -446,9 +487,8 @@ describe('generate-report', () => { successAndStop: vi.fn(), } const scan = [createArtifact()] - const policy = createSecurityPolicy() - generateReport(scan, policy, { + generateReport(scan, { ...defaultOptions, spinner: mockSpinner as unknown, }) @@ -462,14 +502,19 @@ describe('generate-report', () => { it('returns short unhealthy report for error alerts', () => { const scan = [ createArtifact({ - alerts: [{ type: 'badAlert', file: 'index.js', start: 0, end: 10 }], + alerts: [ + { + type: 'badAlert', + file: 'index.js', + start: 0, + end: 10, + action: 'error', + }, + ], }), ] - const policy = createSecurityPolicy({ - badAlert: { action: 'error' }, - }) - const result = generateReport(scan, policy, { + const result = generateReport(scan, { ...defaultOptions, short: true, }) diff --git a/packages/cli/test/unit/commands/scan/output-scan-report-short-mode.test.mts b/packages/cli/test/unit/commands/scan/output-scan-report-short-mode.test.mts index 4de57585bc..f2de1768d8 100644 --- a/packages/cli/test/unit/commands/scan/output-scan-report-short-mode.test.mts +++ b/packages/cli/test/unit/commands/scan/output-scan-report-short-mode.test.mts @@ -88,7 +88,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } @@ -113,7 +112,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } mockGenerateReport.mockReturnValue({ @@ -142,7 +140,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } @@ -167,7 +164,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } diff --git a/packages/cli/test/unit/commands/scan/output-scan-report.test.mts b/packages/cli/test/unit/commands/scan/output-scan-report.test.mts index edb2b688f7..67be20a91d 100644 --- a/packages/cli/test/unit/commands/scan/output-scan-report.test.mts +++ b/packages/cli/test/unit/commands/scan/output-scan-report.test.mts @@ -120,7 +120,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } @@ -149,7 +148,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } @@ -174,7 +172,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } @@ -196,7 +193,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } @@ -221,7 +217,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } @@ -251,7 +246,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } @@ -283,7 +277,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } @@ -313,7 +306,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } @@ -344,7 +336,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, } @@ -376,7 +367,6 @@ describe('output-scan-report', () => { ok: true as const, data: { scan: [], - securityPolicy: { rules: [] }, }, }