diff --git a/packages/cli/src/utils/ecosystem/spec.mts b/packages/cli/src/utils/ecosystem/spec.mts deleted file mode 100644 index 211bfe9a8..000000000 --- a/packages/cli/src/utils/ecosystem/spec.mts +++ /dev/null @@ -1,27 +0,0 @@ -import semver from 'semver' - -import { NPM } from '@socketsecurity/lib/constants/agents' - -import { stripPnpmPeerSuffix } from '../pnpm/lockfile.mts' - -import type { PackageURL } from '@socketregistry/packageurl-js' - -export function idToNpmPurl(id: string): string { - return `pkg:${NPM}/${id}` -} - -export function idToPurl(id: string, type: string): string { - return `pkg:${type}/${id}` -} - -export function resolvePackageVersion(purlObj: PackageURL): string { - const { version } = purlObj - if (!version) { - return '' - } - const { type } = purlObj - return ( - semver.coerce(type === NPM ? stripPnpmPeerSuffix(version) : version) - ?.version ?? '' - ) -} diff --git a/packages/cli/src/utils/pnpm/lockfile.mts b/packages/cli/src/utils/pnpm/lockfile.mts deleted file mode 100644 index 9a33fc676..000000000 --- a/packages/cli/src/utils/pnpm/lockfile.mts +++ /dev/null @@ -1,125 +0,0 @@ -import fs from 'node:fs' - -import yaml from 'js-yaml' -import semver from 'semver' - -import { readFileUtf8 } from '@socketsecurity/lib/fs' -import { isObjectObject } from '@socketsecurity/lib/objects' -import { stripBom } from '@socketsecurity/lib/strings' - -import { idToNpmPurl } from '../ecosystem/spec.mjs' - -import type { LockfileObject, PackageSnapshot } from '@pnpm/lockfile.fs' -import type { SemVer } from 'semver' - -export function extractOverridesFromPnpmLockSrc(lockfileContent: any): string { - let match: any - if (typeof lockfileContent === 'string') { - // Use non-greedy match to prevent catastrophic backtracking from nested quantifiers. - match = /^overrides:(?:\r?\n {2}[^\n]+)*(?:\r?\n)*/m.exec( - lockfileContent, - )?.[0] - } - return match ?? '' -} - -export async function extractPurlsFromPnpmLockfile( - lockfile: LockfileObject, -): Promise { - const packages = lockfile?.packages ?? {} - const seen = new Set() - const visit = (pkgPath: string) => { - if (seen.has(pkgPath)) { - return - } - const pkg = (packages as any)[pkgPath] as PackageSnapshot - if (!pkg) { - return - } - seen.add(pkgPath) - const deps: { [name: string]: string } = { - __proto__: null, - ...pkg.dependencies, - ...pkg.optionalDependencies, - ...(pkg as any).devDependencies, - } - for (const depName in deps) { - const ref = deps[depName] - if (!ref) { - continue - } - const subKey = isPnpmDepPath(ref) ? ref : `/${depName}@${ref}` - visit(subKey) - } - } - for (const pkgPath of Object.keys(packages)) { - visit(pkgPath) - } - return Array.from(seen).map(p => - idToNpmPurl(stripPnpmPeerSuffix(stripLeadingPnpmDepPathSlash(p))), - ) -} - -export function isPnpmDepPath(maybeDepPath: string): boolean { - return maybeDepPath.length > 0 && maybeDepPath.charCodeAt(0) === 47 /*'/'*/ -} - -export function parsePnpmLockfile( - lockfileContent: unknown, -): LockfileObject | null { - let result: any - if (typeof lockfileContent === 'string') { - try { - result = yaml.load(stripBom(lockfileContent)) - } catch {} - } - return isObjectObject(result) - ? ({ lockfileVersion: '', importers: {}, ...result } as LockfileObject) - : null -} - -export function parsePnpmLockfileVersion(version: unknown): SemVer | undefined { - try { - return semver.coerce(version as string) ?? undefined - } catch {} - return undefined -} - -export async function readPnpmLockfile( - lockfilePath: string, -): Promise { - return fs.existsSync(lockfilePath) - ? await readFileUtf8(lockfilePath) - : undefined -} - -export function stripLeadingPnpmDepPathSlash(depPath: string): string { - return isPnpmDepPath(depPath) ? depPath.slice(1) : depPath -} - -/** - * Strip pnpm peer dependency suffix from dep path. - * - * PNPM appends peer dependency info in format: - * - `package@1.0.0(peer@2.0.0)` - parentheses for peer deps. - * - `package@1.0.0_peer@2.0.0` - underscore for peer deps. - * - * Edge cases to consider: - * - Package names with `(` or `_` in legitimate names (rare but valid). - * - Scoped packages: `@scope/package` should work correctly. - * - Empty or malformed dep paths should return unchanged. - * - * Current implementation: Find first `(` or `_` and strip everything after. - * Limitation: May incorrectly strip if package name contains these chars. - * Mitigation: Most package names don't contain `(` or `_`, pnpm format is predictable. - */ -export function stripPnpmPeerSuffix(depPath: string): string { - // Defensive: Handle empty or invalid inputs. - if (!depPath || typeof depPath !== 'string') { - return depPath - } - - const parenIndex = depPath.indexOf('(') - const index = parenIndex === -1 ? depPath.indexOf('_') : parenIndex - return index === -1 ? depPath : depPath.slice(0, index) -} diff --git a/packages/cli/src/utils/socket/alerts.mts b/packages/cli/src/utils/socket/alerts.mts deleted file mode 100644 index c1843dc19..000000000 --- a/packages/cli/src/utils/socket/alerts.mts +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Alerts map utilities for Socket CLI. - * Manages security alerts and vulnerability mappings for packages. - * - * Key Functions: - * - getAlertsMapFromPnpmLockfile: Extract alerts from pnpm lockfile - * - getAlertsMapFromPurls: Get alerts for specific package URLs - * - processAlertsApiResponse: Process API response into alerts map - * - * Alert Processing: - * - Filters alerts based on socket.yml configuration - * - Maps package URLs to security vulnerabilities - * - Supports batch processing for performance - * - * Integration: - * - Works with pnpm lockfiles for dependency scanning - * - Uses Socket API for vulnerability data - * - Respects filter configurations from socket.yml - */ - -import { arrayUnique } from '@socketsecurity/lib/arrays' -import { debugDirNs } from '@socketsecurity/lib/debug' -import { getDefaultLogger } from '@socketsecurity/lib/logger' -import { getOwn } from '@socketsecurity/lib/objects' -import { isNonEmptyString } from '@socketsecurity/lib/strings' - -import { findSocketYmlSync } from '../config.mts' -import { extractPurlsFromPnpmLockfile } from '../pnpm/lockfile.mts' -import { addArtifactToAlertsMap } from '../socket/package-alert.mts' -import { setupSdk } from '../socket/sdk.mjs' -import { toFilterConfig } from '../validation/filter-config.mts' - -import type { CompactSocketArtifact } from '../alert/artifact.mts' -import type { AlertFilter, AlertsByPurl } from '../socket/package-alert.mts' -import type { LockfileObject } from '@pnpm/lockfile.fs' -import type { Spinner } from '@socketsecurity/lib/spinner' - -export type GetAlertsMapFromPnpmLockfileOptions = { - apiToken?: string | undefined - consolidate?: boolean | undefined - filter?: AlertFilter | undefined - overrides?: { [key: string]: string } | undefined - nothrow?: boolean | undefined - spinner?: Spinner | undefined -} - -export async function getAlertsMapFromPnpmLockfile( - lockfile: LockfileObject, - options?: GetAlertsMapFromPnpmLockfileOptions | undefined, -): Promise { - const purls = await extractPurlsFromPnpmLockfile(lockfile) - return await getAlertsMapFromPurls(purls, { - overrides: lockfile.overrides, - ...options, - }) -} - -export type GetAlertsMapFromPurlsOptions = { - apiToken?: string | undefined - consolidate?: boolean | undefined - filter?: AlertFilter | undefined - onlyFixable?: boolean | undefined - overrides?: { [key: string]: string } | undefined - nothrow?: boolean | undefined - spinner?: Spinner | undefined -} - -export async function getAlertsMapFromPurls( - purls: string[] | readonly string[], - options?: GetAlertsMapFromPurlsOptions | undefined, -): Promise { - const uniqPurls = arrayUnique(purls) - debugDirNs('silly', { purls: uniqPurls }) - - let { length: remaining } = uniqPurls - const alertsByPurl: AlertsByPurl = new Map() - - if (!remaining) { - return alertsByPurl - } - - const opts = { - __proto__: null, - consolidate: false, - nothrow: false, - ...options, - filter: toFilterConfig(getOwn(options, 'filter')), - } as GetAlertsMapFromPurlsOptions & { filter: AlertFilter } - - if (opts.onlyFixable) { - opts.filter.fixable = true - } - - const { apiToken, spinner } = opts - - const getText = () => `Looking up data for ${remaining} packages` - - spinner?.start(getText()) - - const sockSdkCResult = await setupSdk({ apiToken }) - if (!sockSdkCResult.ok) { - spinner?.stop() - throw new Error('Auth error: Run `socket login` first.') - } - const sockSdk = sockSdkCResult.data - const socketYmlResult = findSocketYmlSync() - const socketYml = - socketYmlResult.ok && socketYmlResult.data - ? socketYmlResult.data.parsed - : undefined - - const alertsMapOptions = { - consolidate: opts.consolidate, - filter: opts.filter, - overrides: opts.overrides, - socketYml, - spinner, - } - - try { - for await (const batchResult of sockSdk.batchPackageStream( - { - components: uniqPurls.map(purl => ({ purl })), - }, - { - queryParams: { - alerts: 'true', - compact: 'true', - ...(opts.onlyFixable ? { fixable: 'true ' } : {}), - ...(Array.isArray(opts.filter.actions) - ? { actions: opts.filter.actions.join(',') } - : {}), - }, - }, - )) { - if (batchResult.success) { - const artifact = batchResult.data as CompactSocketArtifact - await addArtifactToAlertsMap(artifact, alertsByPurl, alertsMapOptions) - } else if (!opts.nothrow) { - spinner?.stop() - if (isNonEmptyString(batchResult.error)) { - throw new Error(batchResult.error) - } - const statusCode = batchResult.status ?? 'unknown' - throw new Error( - `Socket API server error (${statusCode}): No status message`, - ) - } else { - spinner?.stop() - const logger = getDefaultLogger() - logger.fail( - `Received a ${batchResult.status} response from Socket API which we consider a permanent failure:`, - batchResult.error, - batchResult.cause ? `( ${batchResult.cause} )` : '', - ) - debugDirNs('inspect', { batchResult }) - break - } - remaining -= 1 - if (remaining > 0) { - spinner?.start(getText()) - } - } - } catch (e) { - spinner?.stop() - throw e - } - - spinner?.stop() - - return alertsByPurl -} diff --git a/packages/cli/test/unit/utils/ecosystem/spec.test.mts b/packages/cli/test/unit/utils/ecosystem/spec.test.mts deleted file mode 100644 index 2a8175f4f..000000000 --- a/packages/cli/test/unit/utils/ecosystem/spec.test.mts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Unit tests for ecosystem package spec. - * - * Purpose: - * Tests ecosystem package specification parsing. Validates package spec formats across ecosystems. - * - * Test Coverage: - * - npm package spec parsing - * - Python requirement.txt parsing - * - Go module parsing - * - Maven POM parsing - * - Multi-ecosystem support - * - * Testing Approach: - * Tests package specification parsers for various ecosystems. - * - * Related Files: - * - utils/ecosystem/spec.mts (implementation) - */ - -import { beforeEach, describe, expect, it, vi } from 'vitest' - -import { - idToNpmPurl, - idToPurl, - resolvePackageVersion, -} from '../../../../src/utils/ecosystem/spec.mts' - -// Mock semver module. -const mockStripPnpmPeerSuffix = vi.hoisted(() => - vi.fn(v => v.replace(/_.*$/, '')), -) - -vi.mock('semver', () => ({ - default: { - coerce: vi.fn(), - }, -})) - -// Mock pnpm utilities. -vi.mock('../../../../src/utils/pnpm/lockfile.mts', () => ({ - stripPnpmPeerSuffix: mockStripPnpmPeerSuffix, -})) - -describe('spec utilities', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - - describe('idToNpmPurl', () => { - it('converts package ID to npm PURL', () => { - expect(idToNpmPurl('express@4.18.0')).toBe('pkg:npm/express@4.18.0') - expect(idToNpmPurl('lodash@4.17.21')).toBe('pkg:npm/lodash@4.17.21') - }) - - it('handles scoped packages', () => { - expect(idToNpmPurl('@babel/core@7.0.0')).toBe('pkg:npm/@babel/core@7.0.0') - expect(idToNpmPurl('@types/node@18.0.0')).toBe( - 'pkg:npm/@types/node@18.0.0', - ) - }) - - it('handles packages without versions', () => { - expect(idToNpmPurl('express')).toBe('pkg:npm/express') - expect(idToNpmPurl('@babel/core')).toBe('pkg:npm/@babel/core') - }) - }) - - describe('idToPurl', () => { - it('converts package ID to PURL with specified type', () => { - expect(idToPurl('flask==2.0.0', 'pypi')).toBe('pkg:pypi/flask==2.0.0') - expect(idToPurl('gem@1.0.0', 'gem')).toBe('pkg:gem/gem@1.0.0') - expect(idToPurl('org.apache:commons@3.0', 'maven')).toBe( - 'pkg:maven/org.apache:commons@3.0', - ) - }) - - it('handles npm type', () => { - expect(idToPurl('express@4.18.0', 'npm')).toBe('pkg:npm/express@4.18.0') - }) - - it('handles empty type', () => { - expect(idToPurl('package@1.0.0', '')).toBe('pkg:/package@1.0.0') - }) - }) - - describe('resolvePackageVersion', () => { - it('returns empty string when no version', () => { - const purlObj = { - type: 'npm', - name: 'express', - version: undefined, - } as any - - const result = resolvePackageVersion(purlObj) - expect(result).toBe('') - }) - - it('coerces npm package versions', async () => { - const semver = (await import('semver')).default - vi.mocked(semver.coerce).mockReturnValue({ version: '4.18.0' } as any) - - const purlObj = { - type: 'npm', - name: 'express', - version: '4.18.0_peer@1.0.0', - } as any - - const result = resolvePackageVersion(purlObj) - expect(result).toBe('4.18.0') - - const { stripPnpmPeerSuffix } = vi.mocked( - await import('../../../../../src/utils/pnpm/lockfile.mts'), - ) - expect(stripPnpmPeerSuffix).toHaveBeenCalledWith('4.18.0_peer@1.0.0') - expect(semver.coerce).toHaveBeenCalledWith('4.18.0') - }) - - it('coerces non-npm package versions without stripping', async () => { - const semver = (await import('semver')).default - vi.mocked(semver.coerce).mockReturnValue({ version: '2.0.0' } as any) - - const purlObj = { - type: 'pypi', - name: 'flask', - version: '2.0.0', - } as any - - const result = resolvePackageVersion(purlObj) - expect(result).toBe('2.0.0') - - const { stripPnpmPeerSuffix } = vi.mocked( - await import('../../../../../src/utils/pnpm/lockfile.mts'), - ) - expect(stripPnpmPeerSuffix).not.toHaveBeenCalled() - expect(semver.coerce).toHaveBeenCalledWith('2.0.0') - }) - - it('returns empty string when coerce returns null', async () => { - const semver = (await import('semver')).default - vi.mocked(semver.coerce).mockReturnValue(null) - - const purlObj = { - type: 'npm', - name: 'invalid', - version: 'not-a-version', - } as any - - const result = resolvePackageVersion(purlObj) - expect(result).toBe('') - }) - - it('handles complex npm versions with peer suffixes', async () => { - const semver = (await import('semver')).default - vi.mocked(semver.coerce).mockReturnValue({ version: '18.2.0' } as any) - - const purlObj = { - type: 'npm', - name: 'react', - version: '18.2.0_react-dom@18.2.0', - } as any - - const result = resolvePackageVersion(purlObj) - expect(result).toBe('18.2.0') - - const { stripPnpmPeerSuffix } = vi.mocked( - await import('../../../../../src/utils/pnpm/lockfile.mts'), - ) - expect(stripPnpmPeerSuffix).toHaveBeenCalledWith( - '18.2.0_react-dom@18.2.0', - ) - }) - }) -}) diff --git a/packages/cli/test/unit/utils/pnpm/lockfile.test.mts b/packages/cli/test/unit/utils/pnpm/lockfile.test.mts deleted file mode 100644 index 403614114..000000000 --- a/packages/cli/test/unit/utils/pnpm/lockfile.test.mts +++ /dev/null @@ -1,402 +0,0 @@ -/** - * Unit tests for pnpm lockfile parsing. - * - * Purpose: - * Tests pnpm-lock.yaml parsing. Validates pnpm lockfile format parsing and dependency extraction. - * - * Test Coverage: - * - pnpm-lock.yaml parsing - * - Dependency resolution - * - Workspace dependencies - * - Version extraction - * - Lockfile validation - * - * Testing Approach: - * Tests pnpm lockfile parser with fixture lockfiles. - * - * Related Files: - * - utils/pnpm/lockfile.mts (implementation) - */ - -import fs from 'node:fs' - -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' - -import { - extractOverridesFromPnpmLockSrc, - extractPurlsFromPnpmLockfile, - isPnpmDepPath, - parsePnpmLockfile, - parsePnpmLockfileVersion, - readPnpmLockfile, - stripLeadingPnpmDepPathSlash, - stripPnpmPeerSuffix, -} from '../../../../src/utils/pnpm/lockfile.mts' - -// Mock fs module. -let mockExistsSync: ReturnType -const mockReadFileUtf8 = vi.hoisted(() => vi.fn()) - -// Mock registry modules. -vi.mock('@socketsecurity/lib/fs', () => ({ - readFileUtf8: mockReadFileUtf8, -})) - -describe('pnpm utilities', () => { - beforeEach(() => { - vi.clearAllMocks() - mockExistsSync = vi.spyOn(fs, 'existsSync') - }) - - afterEach(() => { - vi.restoreAllMocks() - }) - - describe('extractOverridesFromPnpmLockSrc', () => { - it('extracts overrides section from lockfile content', () => { - const lockfileContent = `lockfileVersion: 5.4 -overrides: - lodash: 4.17.21 - react: 18.0.0 - -dependencies: - express: 4.18.0` - const result = extractOverridesFromPnpmLockSrc(lockfileContent) - expect(result).toBe('overrides:\n lodash: 4.17.21\n react: 18.0.0\n\n') - }) - - it('returns empty string when no overrides section', () => { - const lockfileContent = `lockfileVersion: 5.4 -dependencies: - express: 4.18.0` - const result = extractOverridesFromPnpmLockSrc(lockfileContent) - expect(result).toBe('') - }) - - it('returns empty string for non-string input', () => { - expect(extractOverridesFromPnpmLockSrc({})).toBe('') - expect(extractOverridesFromPnpmLockSrc(null)).toBe('') - expect(extractOverridesFromPnpmLockSrc(undefined)).toBe('') - }) - - it('handles Windows line endings', () => { - const lockfileContent = - 'lockfileVersion: 5.4\r\noverrides:\r\n lodash: 4.17.21\r\n\r\ndependencies:' - const result = extractOverridesFromPnpmLockSrc(lockfileContent) - expect(result).toBe('overrides:\r\n lodash: 4.17.21\r\n\r\n') - }) - }) - - describe('isPnpmDepPath', () => { - it('identifies pnpm dependency paths', () => { - expect(isPnpmDepPath('/lodash@4.17.21')).toBe(true) - expect(isPnpmDepPath('/express@4.18.0')).toBe(true) - expect(isPnpmDepPath('/@babel/core@7.0.0')).toBe(true) - }) - - it('returns false for non-dependency paths', () => { - expect(isPnpmDepPath('lodash@4.17.21')).toBe(false) - expect(isPnpmDepPath('')).toBe(false) - expect(isPnpmDepPath('4.17.21')).toBe(false) - }) - }) - - describe('parsePnpmLockfile', () => { - it('parses valid YAML lockfile content', () => { - const lockfileContent = `lockfileVersion: 5.4 -packages: - /lodash@4.17.21: - resolution: {integrity: sha512-test} - dev: false` - - const result = parsePnpmLockfile(lockfileContent) - expect(result).toBeDefined() - expect(result?.lockfileVersion).toBe(5.4) - expect(result?.packages).toBeDefined() - }) - - it('handles BOM in lockfile content', () => { - const lockfileContent = - '\ufeff' + - `lockfileVersion: 5.4 -packages: {}` - - const result = parsePnpmLockfile(lockfileContent) - expect(result).toBeDefined() - expect(result?.lockfileVersion).toBe(5.4) - }) - - it('returns null for invalid YAML', () => { - const lockfileContent = '{not: valid yaml' - const result = parsePnpmLockfile(lockfileContent) - expect(result).toBeNull() - }) - - it('returns null for non-string input', () => { - expect(parsePnpmLockfile(123)).toBeNull() - expect(parsePnpmLockfile(null)).toBeNull() - expect(parsePnpmLockfile(undefined)).toBeNull() - }) - - it('returns null for non-object result', () => { - const lockfileContent = `"just a string"` - const result = parsePnpmLockfile(lockfileContent) - expect(result).toBeNull() - }) - }) - - describe('parsePnpmLockfileVersion', () => { - it('parses valid version numbers', () => { - const result = parsePnpmLockfileVersion('5.4') - expect(result).toBeDefined() - expect(result?.major).toBe(5) - expect(result?.minor).toBe(4) - expect(result?.patch).toBe(0) - }) - - it('coerces partial versions', () => { - const result = parsePnpmLockfileVersion('5') - expect(result).toBeDefined() - expect(result?.major).toBe(5) - expect(result?.minor).toBe(0) - expect(result?.patch).toBe(0) - }) - - it('returns undefined for invalid versions', () => { - expect(parsePnpmLockfileVersion('not a version')).toBeUndefined() - expect(parsePnpmLockfileVersion(null)).toBeUndefined() - expect(parsePnpmLockfileVersion(undefined)).toBeUndefined() - expect(parsePnpmLockfileVersion({})).toBeUndefined() - }) - }) - - describe('readPnpmLockfile', () => { - it('reads existing lockfile', async () => { - mockExistsSync.mockReturnValue(true) - mockReadFileUtf8.mockResolvedValue('lockfile content') - - const result = await readPnpmLockfile('/path/to/pnpm-lock.yaml') - expect(result).toBe('lockfile content') - expect(fs.existsSync).toHaveBeenCalledWith('/path/to/pnpm-lock.yaml') - expect(mockReadFileUtf8).toHaveBeenCalledWith('/path/to/pnpm-lock.yaml') - }) - - it('returns undefined for non-existent lockfile', async () => { - mockExistsSync.mockReturnValue(false) - - const result = await readPnpmLockfile('/path/to/missing.yaml') - expect(result).toBeUndefined() - }) - }) - - describe('stripLeadingPnpmDepPathSlash', () => { - it('strips leading slash from dependency paths', () => { - expect(stripLeadingPnpmDepPathSlash('/lodash@4.17.21')).toBe( - 'lodash@4.17.21', - ) - expect(stripLeadingPnpmDepPathSlash('/@babel/core@7.0.0')).toBe( - '@babel/core@7.0.0', - ) - }) - - it('returns unchanged for non-dependency paths', () => { - expect(stripLeadingPnpmDepPathSlash('lodash@4.17.21')).toBe( - 'lodash@4.17.21', - ) - expect(stripLeadingPnpmDepPathSlash('')).toBe('') - }) - }) - - describe('stripPnpmPeerSuffix', () => { - it('strips peer dependency suffix with parentheses', () => { - expect(stripPnpmPeerSuffix('react@18.0.0(react-dom@18.0.0)')).toBe( - 'react@18.0.0', - ) - expect(stripPnpmPeerSuffix('vue@3.0.0(typescript@4.0.0)')).toBe( - 'vue@3.0.0', - ) - }) - - it('strips peer dependency suffix with underscore', () => { - expect(stripPnpmPeerSuffix('react@18.0.0_react-dom@18.0.0')).toBe( - 'react@18.0.0', - ) - expect(stripPnpmPeerSuffix('vue@3.0.0_typescript@4.0.0')).toBe( - 'vue@3.0.0', - ) - }) - - it('prefers parentheses over underscore', () => { - expect(stripPnpmPeerSuffix('pkg@1.0.0(peer)_other')).toBe('pkg@1.0.0') - }) - - it('returns unchanged for paths without suffixes', () => { - expect(stripPnpmPeerSuffix('lodash@4.17.21')).toBe('lodash@4.17.21') - expect(stripPnpmPeerSuffix('@babel/core@7.0.0')).toBe('@babel/core@7.0.0') - }) - }) - - describe('extractPurlsFromPnpmLockfile', () => { - it('extracts PURLs from lockfile with dependencies', async () => { - const lockfile = { - lockfileVersion: 5.4, - packages: { - '/lodash@4.17.21': { - resolution: { integrity: 'sha512-test' }, - dev: false, - }, - '/express@4.18.0': { - resolution: { integrity: 'sha512-test2' }, - dependencies: { - 'body-parser': '1.19.0', - }, - dev: false, - }, - '/body-parser@1.19.0': { - resolution: { integrity: 'sha512-test3' }, - dev: false, - }, - }, - } - - const purls = await extractPurlsFromPnpmLockfile(lockfile) - expect(purls).toContain('pkg:npm/lodash@4.17.21') - expect(purls).toContain('pkg:npm/express@4.18.0') - expect(purls).toContain('pkg:npm/body-parser@1.19.0') - }) - - it('handles optional and dev dependencies', async () => { - const lockfile = { - lockfileVersion: 5.4, - packages: { - '/main@1.0.0': { - resolution: { integrity: 'sha512-test' }, - dependencies: { - dep: '1.0.0', - }, - optionalDependencies: { - optional: '1.0.0', - }, - devDependencies: { - dev: '1.0.0', - }, - }, - '/dep@1.0.0': { - resolution: { integrity: 'sha512-test2' }, - }, - '/optional@1.0.0': { - resolution: { integrity: 'sha512-test3' }, - }, - '/dev@1.0.0': { - resolution: { integrity: 'sha512-test4' }, - }, - }, - } - - const purls = await extractPurlsFromPnpmLockfile(lockfile) - expect(purls).toHaveLength(4) - expect(purls).toContain('pkg:npm/main@1.0.0') - expect(purls).toContain('pkg:npm/dep@1.0.0') - expect(purls).toContain('pkg:npm/optional@1.0.0') - expect(purls).toContain('pkg:npm/dev@1.0.0') - }) - - it('handles circular dependencies', async () => { - const lockfile = { - lockfileVersion: 5.4, - packages: { - '/a@1.0.0': { - resolution: { integrity: 'sha512-test' }, - dependencies: { - b: '1.0.0', - }, - }, - '/b@1.0.0': { - resolution: { integrity: 'sha512-test2' }, - dependencies: { - a: '1.0.0', - }, - }, - }, - } - - const purls = await extractPurlsFromPnpmLockfile(lockfile) - expect(purls).toHaveLength(2) - expect(purls).toContain('pkg:npm/a@1.0.0') - expect(purls).toContain('pkg:npm/b@1.0.0') - }) - - it('handles empty lockfile', async () => { - const lockfile = { - lockfileVersion: 5.4, - } - - const purls = await extractPurlsFromPnpmLockfile(lockfile) - expect(purls).toEqual([]) - }) - - it('handles lockfile with no packages', async () => { - const lockfile = { - lockfileVersion: 5.4, - packages: {}, - } - - const purls = await extractPurlsFromPnpmLockfile(lockfile) - expect(purls).toEqual([]) - }) - - it('handles dependency pointing to non-existent package', async () => { - const lockfile = { - lockfileVersion: 5.4, - packages: { - '/main@1.0.0': { - resolution: { integrity: 'sha512-test' }, - dependencies: { - 'missing-pkg': '1.0.0', - }, - }, - // Note: /missing-pkg@1.0.0 is not in packages. - }, - } - - const purls = await extractPurlsFromPnpmLockfile(lockfile) - // Should include main and handle the missing package gracefully. - // The seen set tracks visited paths but only existing package paths are mapped to purls. - expect(purls).toContain('pkg:npm/main@1.0.0') - expect(purls).toHaveLength(1) - }) - - it('handles empty dependency reference', async () => { - const lockfile = { - lockfileVersion: 5.4, - packages: { - '/main@1.0.0': { - resolution: { integrity: 'sha512-test' }, - dependencies: { - 'some-pkg': '', - }, - }, - }, - } - - const purls = await extractPurlsFromPnpmLockfile(lockfile) - // Should only include main, empty ref should be skipped. - expect(purls).toHaveLength(1) - expect(purls).toContain('pkg:npm/main@1.0.0') - }) - }) - - describe('stripPnpmPeerSuffix edge cases', () => { - it('handles empty string input', () => { - expect(stripPnpmPeerSuffix('')).toBe('') - }) - - it('handles null input', () => { - expect(stripPnpmPeerSuffix(null as any)).toBe(null) - }) - - it('handles undefined input', () => { - expect(stripPnpmPeerSuffix(undefined as any)).toBe(undefined) - }) - }) -}) diff --git a/packages/cli/test/unit/utils/pnpm/scanning.test.mts b/packages/cli/test/unit/utils/pnpm/scanning.test.mts deleted file mode 100644 index e43cbcc95..000000000 --- a/packages/cli/test/unit/utils/pnpm/scanning.test.mts +++ /dev/null @@ -1,291 +0,0 @@ -/** - * Unit tests for pnpm project scanning. - * - * Purpose: - * Tests pnpm project scanning utilities. Validates workspace detection and manifest discovery. - * - * Test Coverage: - * - Workspace detection - * - pnpm-workspace.yaml parsing - * - Package discovery - * - Dependency graph - * - Monorepo support - * - * Testing Approach: - * Tests pnpm workspace scanning and analysis. - * - * Related Files: - * - utils/pnpm/scanning.mts (implementation) - */ - -import { beforeEach, describe, expect, it, vi } from 'vitest' - -import { - extractPurlsFromPnpmLockfile, - parsePnpmLockfile, -} from '../../../../src/utils/pnpm/lockfile.mts' -import { getAlertsMapFromPnpmLockfile } from '../../../../src/utils/socket/alerts.mts' - -// Mock all dependencies with vi.hoisted for better type safety -const mockGetPublicApiToken = vi.hoisted(() => vi.fn()) -const mockSetupSdk = vi.hoisted(() => vi.fn()) -const mockFindSocketYmlSync = vi.hoisted(() => vi.fn()) -const mockAddArtifactToAlertsMap = vi.hoisted(() => vi.fn()) -const mockBatchPackageStream = vi.hoisted(() => vi.fn()) - -const mockGetConfigValueOrUndef = vi.hoisted(() => vi.fn()) - -vi.mock('../../../../src/utils/socket/sdk.mts', () => ({ - getPublicApiToken: mockGetPublicApiToken, - setupSdk: mockSetupSdk, -})) - -vi.mock('../../../../src/utils/config.mts', () => ({ - findSocketYmlSync: mockFindSocketYmlSync, - getConfigValueOrUndef: mockGetConfigValueOrUndef, -})) - -vi.mock('../../../../src/utils/socket/package-alert.mts', () => ({ - addArtifactToAlertsMap: mockAddArtifactToAlertsMap, -})) - -vi.mock('../../../../src/utils/validation/filter-config.mts', () => ({ - toFilterConfig: vi.fn(filter => filter || {}), -})) - -describe('PNPM Lockfile PURL Scanning', () => { - beforeEach(() => { - vi.clearAllMocks() - - // Setup default mock implementations - mockGetPublicApiToken.mockReturnValue('test-token') - mockFindSocketYmlSync.mockReturnValue({ ok: false, data: undefined }) - mockAddArtifactToAlertsMap.mockResolvedValue(undefined) - - mockBatchPackageStream.mockImplementation(async function* () { - yield { - success: true, - data: { - purl: 'pkg:npm/lodash@4.17.21', - name: 'lodash', - version: '4.17.21', - alerts: [], - }, - } - }) - - mockSetupSdk.mockResolvedValue({ - ok: true, - data: { - batchPackageStream: mockBatchPackageStream, - }, - }) - }) - it('should extract PURLs from simple pnpm lockfile', async () => { - const lockfileContent = `lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -dependencies: - lodash: - specifier: ^4.17.21 - version: 4.17.21 - -packages: - - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: false` - - const lockfile = parsePnpmLockfile(lockfileContent) - expect(lockfile).toBeTruthy() - - const purls = await extractPurlsFromPnpmLockfile(lockfile!) - expect(purls).toContain('pkg:npm/lodash@4.17.21') - }) - - it('should extract PURLs from lockfile with scoped packages', async () => { - const lockfileContent = `lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -dependencies: - '@types/node': - specifier: ^20.0.0 - version: 20.11.19 - -packages: - - /@types/node@20.11.19: - resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NtpXhPbzxYrZOVgou07X5wMeFWmEK8lgP5btmu+2IjuXlRQQzk3TgEDwVlaUaIZA==} - dev: true` - - const lockfile = parsePnpmLockfile(lockfileContent) - expect(lockfile).toBeTruthy() - - const purls = await extractPurlsFromPnpmLockfile(lockfile!) - expect(purls).toContain('pkg:npm/@types/node@20.11.19') - }) - - it('should extract PURLs from lockfile with transitive dependencies', async () => { - const lockfileContent = `lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -dependencies: - express: - specifier: ^4.18.0 - version: 4.18.2 - -packages: - - /express@4.18.2: - resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} - engines: {node: '>= 0.10.0'} - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - dev: false - - /accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - dependencies: - mime-types: 2.1.35 - dev: false - - /array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - dev: false - - /mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 - dev: false - - /mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - dev: false` - - const lockfile = parsePnpmLockfile(lockfileContent) - expect(lockfile).toBeTruthy() - - const purls = await extractPurlsFromPnpmLockfile(lockfile!) - - // Should include all packages, both direct and transitive - expect(purls).toContain('pkg:npm/express@4.18.2') - expect(purls).toContain('pkg:npm/accepts@1.3.8') - expect(purls).toContain('pkg:npm/array-flatten@1.1.1') - expect(purls).toContain('pkg:npm/mime-types@2.1.35') - expect(purls).toContain('pkg:npm/mime-db@1.52.0') - }) - - it('should handle lockfile with peer dependencies', async () => { - const lockfileContent = `lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -dependencies: - react-dom: - specifier: ^18.0.0 - version: 18.2.0(react@18.2.0) - react: - specifier: ^18.0.0 - version: 18.2.0 - -packages: - - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} - engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - dev: false - - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} - peerDependencies: - react: ^18.2.0 - dependencies: - loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 - dev: false - - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko26NfpXKwULFDNYB9LKqcxUWkOiMccJDR0RAw==} - hasBin: true - dependencies: - js-tokens: 4.0.0 - dev: false - - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: false - - /scheduler@0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} - dependencies: - loose-envify: 1.4.0 - dev: false` - - const lockfile = parsePnpmLockfile(lockfileContent) - expect(lockfile).toBeTruthy() - - const purls = await extractPurlsFromPnpmLockfile(lockfile!) - - expect(purls).toContain('pkg:npm/react@18.2.0') - expect(purls).toContain('pkg:npm/react-dom@18.2.0') - expect(purls).toContain('pkg:npm/loose-envify@1.4.0') - expect(purls).toContain('pkg:npm/js-tokens@4.0.0') - expect(purls).toContain('pkg:npm/scheduler@0.23.0') - }) - - it('should successfully scan lockfile and return alerts map', async () => { - const lockfile = { - lockfileVersion: '6.0', - packages: { - '/lodash@4.17.21': { - resolution: { integrity: 'sha512-test' }, - dependencies: {}, - dev: false, - }, - }, - } - - const alertsMap = await getAlertsMapFromPnpmLockfile(lockfile, { - nothrow: true, - filter: { actions: ['error', 'monitor', 'warn'] }, - }) - - expect(alertsMap).toBeInstanceOf(Map) - }) - - it('should handle empty lockfile gracefully', async () => { - const lockfile = { - lockfileVersion: '6.0', - packages: {}, - } - - const purls = await extractPurlsFromPnpmLockfile(lockfile) - expect(purls).toEqual([]) - - const alertsMap = await getAlertsMapFromPnpmLockfile(lockfile, { - nothrow: true, - }) - - expect(alertsMap).toBeInstanceOf(Map) - expect(alertsMap.size).toBe(0) - }) -}) diff --git a/packages/cli/test/unit/utils/socket/alerts.test.mts b/packages/cli/test/unit/utils/socket/alerts.test.mts deleted file mode 100644 index 91327b191..000000000 --- a/packages/cli/test/unit/utils/socket/alerts.test.mts +++ /dev/null @@ -1,398 +0,0 @@ -/** - * Unit tests for Socket alert utilities. - * - * Purpose: - * Tests Socket alert data structures and utilities. Validates alert parsing and categorization. - * - * Test Coverage: - * - Alert parsing - * - Severity calculation - * - Alert filtering - * - Alert grouping - * - Issue rules application - * - * Testing Approach: - * Tests alert utilities for Socket API responses. - * - * Related Files: - * - utils/socket/alerts.mts (implementation) - */ - -import { beforeEach, describe, expect, it, vi } from 'vitest' - -import { - getAlertsMapFromPnpmLockfile, - getAlertsMapFromPurls, -} from '../../../../src/utils/socket/alerts.mts' - -// Mock dependencies. -const mockLogger = vi.hoisted(() => ({ - error: vi.fn(), - fail: vi.fn(), - info: vi.fn(), - log: vi.fn(), - success: vi.fn(), - warn: vi.fn(), -})) - -const mockFindSocketYmlSync = vi.hoisted(() => vi.fn()) -const mockToFilterConfig = vi.hoisted(() => vi.fn()) -const mockExtractPurlsFromPnpmLockfile = vi.hoisted(() => vi.fn()) -const mockAddArtifactToAlertsMap = vi.hoisted(() => vi.fn()) -const mockSetupSdk = vi.hoisted(() => vi.fn()) -const mockGetPublicApiToken = vi.hoisted(() => vi.fn()) -const mockGetDefaultApiToken = vi.hoisted(() => vi.fn(() => 'mock-value')) - -vi.mock('@socketsecurity/lib/logger', () => ({ - getDefaultLogger: () => mockLogger, - logger: mockLogger, -})) - -vi.mock('../../../../src/utils/socket/sdk.mjs', () => ({ - getDefaultApiToken: mockGetDefaultApiToken, - getPublicApiToken: mockGetPublicApiToken, - setupSdk: mockSetupSdk, -})) - -vi.mock('../../../../src/utils/config.mts', () => ({ - findSocketYmlSync: mockFindSocketYmlSync, -})) - -vi.mock('../../../../src/utils/validation/filter-config.mts', () => ({ - toFilterConfig: mockToFilterConfig, -})) - -vi.mock('../../../../src/utils/pnpm/lockfile.mts', () => ({ - extractPurlsFromPnpmLockfile: mockExtractPurlsFromPnpmLockfile, -})) - -vi.mock('../../../../src/utils/socket/package-alert.mts', () => ({ - addArtifactToAlertsMap: mockAddArtifactToAlertsMap, -})) - -describe('alerts-map utilities', () => { - beforeEach(() => { - vi.clearAllMocks() - }) - - describe('getAlertsMapFromPnpmLockfile', () => { - it('returns empty map for lockfile with no packages', async () => { - mockExtractPurlsFromPnpmLockfile.mockResolvedValue([]) - mockSetupSdk.mockReturnValue({ - ok: true, - data: { - org: { - dependencies: { - post: vi.fn().mockResolvedValue({ - ok: true, - data: [], - }), - }, - }, - }, - } as any) - mockFindSocketYmlSync.mockReturnValue({ - ok: false, - } as any) - - const lockfile = { - lockfileVersion: '6.0', - packages: {}, - } - - const result = await getAlertsMapFromPnpmLockfile(lockfile, { - apiToken: 'mock-value', - nothrow: true, - }) - - // Check that result is a Map. - expect(result).toBeInstanceOf(Map) - expect(result.size).toBe(0) - }) - }) - - describe('getAlertsMapFromPurls', () => { - it('returns map for empty purls', async () => { - mockSetupSdk.mockReturnValue({ - ok: true, - data: { - org: { - dependencies: { - post: vi.fn().mockResolvedValue({ - ok: true, - data: [], - }), - }, - }, - }, - } as any) - mockFindSocketYmlSync.mockReturnValue({ - ok: false, - } as any) - - const result = await getAlertsMapFromPurls([], { - apiToken: 'mock-value', - nothrow: true, - }) - - // Check that result is a Map. - expect(result).toBeInstanceOf(Map) - expect(result.size).toBe(0) - }) - - it('requires API token', async () => { - mockSetupSdk.mockReturnValue({ - ok: false, - message: 'No API token', - } as any) - - try { - await getAlertsMapFromPurls(['pkg:npm/test@1.0.0'], { - nothrow: false, - }) - expect.fail('Should have thrown') - } catch (e) { - expect(e).toBeDefined() - } - }) - - it('deduplicates purls before processing', async () => { - mockSetupSdk.mockReturnValue({ - ok: true, - data: { - batchPackageStream: async function* () { - // Empty generator. - }, - }, - } as any) - mockFindSocketYmlSync.mockReturnValue({ - ok: false, - } as any) - mockToFilterConfig.mockReturnValue({}) - - const result = await getAlertsMapFromPurls( - ['pkg:npm/test@1.0.0', 'pkg:npm/test@1.0.0', 'pkg:npm/test@1.0.0'], - { - apiToken: 'mock-value', - }, - ) - - expect(result).toBeInstanceOf(Map) - }) - - it('processes batch results with onlyFixable option', async () => { - mockSetupSdk.mockReturnValue({ - ok: true, - data: { - batchPackageStream: async function* () { - // Empty generator. - }, - }, - } as any) - mockFindSocketYmlSync.mockReturnValue({ - ok: false, - } as any) - mockToFilterConfig.mockReturnValue({}) - - const result = await getAlertsMapFromPurls(['pkg:npm/test@1.0.0'], { - apiToken: 'mock-value', - onlyFixable: true, - }) - - expect(result).toBeInstanceOf(Map) - }) - - it('uses socketYml config when found', async () => { - mockSetupSdk.mockReturnValue({ - ok: true, - data: { - batchPackageStream: async function* () { - // Empty generator. - }, - }, - } as any) - mockFindSocketYmlSync.mockReturnValue({ - ok: true, - data: { - parsed: { - issueRules: { - 'known-malware': 'error', - }, - }, - }, - } as any) - mockToFilterConfig.mockReturnValue({}) - - const result = await getAlertsMapFromPurls(['pkg:npm/test@1.0.0'], { - apiToken: 'mock-value', - }) - - expect(result).toBeInstanceOf(Map) - }) - - it('processes successful batch results', async () => { - mockSetupSdk.mockReturnValue({ - ok: true, - data: { - batchPackageStream: async function* () { - yield { - success: true, - data: { - purl: 'pkg:npm/lodash@4.0.0', - alerts: [], - }, - } - }, - }, - } as any) - mockFindSocketYmlSync.mockReturnValue({ - ok: false, - } as any) - mockToFilterConfig.mockReturnValue({}) - mockAddArtifactToAlertsMap.mockResolvedValue(undefined) - - const result = await getAlertsMapFromPurls(['pkg:npm/lodash@4.0.0'], { - apiToken: 'mock-value', - }) - - expect(result).toBeInstanceOf(Map) - expect(mockAddArtifactToAlertsMap).toHaveBeenCalled() - }) - - it('handles failed batch results with nothrow option', async () => { - mockSetupSdk.mockReturnValue({ - ok: true, - data: { - batchPackageStream: async function* () { - yield { - success: false, - status: 500, - error: 'Internal server error', - } - }, - }, - } as any) - mockFindSocketYmlSync.mockReturnValue({ - ok: false, - } as any) - mockToFilterConfig.mockReturnValue({}) - - const result = await getAlertsMapFromPurls(['pkg:npm/test@1.0.0'], { - apiToken: 'mock-value', - nothrow: true, - }) - - expect(result).toBeInstanceOf(Map) - expect(mockLogger.fail).toHaveBeenCalled() - }) - - it('throws on failed batch results without nothrow', async () => { - mockSetupSdk.mockReturnValue({ - ok: true, - data: { - batchPackageStream: async function* () { - yield { - success: false, - status: 500, - error: 'Internal server error', - } - }, - }, - } as any) - mockFindSocketYmlSync.mockReturnValue({ - ok: false, - } as any) - mockToFilterConfig.mockReturnValue({}) - - await expect( - getAlertsMapFromPurls(['pkg:npm/test@1.0.0'], { - apiToken: 'mock-value', - nothrow: false, - }), - ).rejects.toThrow('Internal server error') - }) - - it('throws generic error when batch result has no error message', async () => { - mockSetupSdk.mockReturnValue({ - ok: true, - data: { - batchPackageStream: async function* () { - yield { - success: false, - status: 500, - } - }, - }, - } as any) - mockFindSocketYmlSync.mockReturnValue({ - ok: false, - } as any) - mockToFilterConfig.mockReturnValue({}) - - await expect( - getAlertsMapFromPurls(['pkg:npm/test@1.0.0'], { - apiToken: 'mock-value', - nothrow: false, - }), - ).rejects.toThrow('Socket API server error (500)') - }) - - it('updates spinner during processing', async () => { - const mockSpinner = { - start: vi.fn(), - stop: vi.fn(), - } - - mockSetupSdk.mockReturnValue({ - ok: true, - data: { - batchPackageStream: async function* () { - yield { - success: true, - data: { - purl: 'pkg:npm/test@1.0.0', - alerts: [], - }, - } - }, - }, - } as any) - mockFindSocketYmlSync.mockReturnValue({ - ok: false, - } as any) - mockToFilterConfig.mockReturnValue({}) - mockAddArtifactToAlertsMap.mockResolvedValue(undefined) - - await getAlertsMapFromPurls(['pkg:npm/test@1.0.0'], { - apiToken: 'mock-value', - spinner: mockSpinner as any, - }) - - expect(mockSpinner.start).toHaveBeenCalled() - expect(mockSpinner.stop).toHaveBeenCalled() - }) - - it('passes filter actions to query params', async () => { - mockSetupSdk.mockReturnValue({ - ok: true, - data: { - batchPackageStream: async function* () { - // Empty generator. - }, - }, - } as any) - mockFindSocketYmlSync.mockReturnValue({ - ok: false, - } as any) - mockToFilterConfig.mockReturnValue({ - actions: ['error', 'warn'], - }) - - const result = await getAlertsMapFromPurls(['pkg:npm/test@1.0.0'], { - apiToken: 'mock-value', - }) - - expect(result).toBeInstanceOf(Map) - }) - }) -})