diff --git a/scripts/test/snapshot-dependencies.mts b/scripts/test/snapshot-dependencies.mts index d8463cd72..55d992767 100644 --- a/scripts/test/snapshot-dependencies.mts +++ b/scripts/test/snapshot-dependencies.mts @@ -8,6 +8,7 @@ export interface SnapshotDependency { readonly source: string readonly versionPattern: RegExp readonly snapshots: readonly string[] + readonly versionSnapshots?: readonly string[] readonly testNamePattern: string } @@ -23,9 +24,29 @@ export const SNAPSHOT_DEPENDENCIES = { ], testNamePattern: 'captures native help for (fix|scan create|scan reach)', }, + patch: { + packageName: '@socketsecurity/socket-patch', + source: 'package.json', + versionPattern: /"@socketsecurity\/socket-patch"\s*:\s*"([^"]+)"/, + snapshots: [ + 'test/native-snapshots/socket-patch-apply-help.help.svg', + 'test/native-snapshots/socket-patch-rollback-help.help.svg', + 'test/native-snapshots/socket-patch-get-help.help.svg', + 'test/native-snapshots/socket-patch-scan-help.help.svg', + 'test/native-snapshots/socket-patch-list-help.help.svg', + 'test/native-snapshots/socket-patch-remove-help.help.svg', + 'test/native-snapshots/socket-patch-setup-help.help.svg', + 'test/native-snapshots/socket-patch-repair-help.help.svg', + 'test/native-snapshots/socket-patch-version.version.svg', + ], + versionSnapshots: ['test/native-snapshots/socket-patch-version.version.svg'], + testNamePattern: + 'captures bundled patch help for (apply|rollback|get|scan|list|remove|setup|repair)|captures bundled socket-patch version', + }, } as const satisfies Record -export type SnapshotDependencyName = keyof typeof SNAPSHOT_DEPENDENCIES +export type SnapshotDependencyName = 'coana' | 'patch' | 'socket-patch' +type SnapshotDependencyMapName = keyof typeof SNAPSHOT_DEPENDENCIES export interface StaleSnapshotDependency { readonly dependencyName: SnapshotDependencyName @@ -38,7 +59,10 @@ export async function findStaleSnapshotDependencies(): Promise< StaleSnapshotDependency[] > { const findings = await Promise.all( - Object.entries(SNAPSHOT_DEPENDENCIES).map(async ([name, dependency]) => { + (Object.entries(SNAPSHOT_DEPENDENCIES) as Array<[ + string, + SnapshotDependency, + ]>).map(async ([name, dependency]) => { const source = await fs.readFile( path.join(ROOT, dependency.source), 'utf8', @@ -49,8 +73,9 @@ export async function findStaleSnapshotDependencies(): Promise< `Could not read ${dependency.packageName} version from ${dependency.source}`, ) } + const versionSnapshots = dependency.versionSnapshots ?? dependency.snapshots const snapshots = await Promise.all( - dependency.snapshots.map(async snapshot => ({ + versionSnapshots.map(async snapshot => ({ path: snapshot, contents: await fs.readFile(path.join(ROOT, snapshot), 'utf8'), })), @@ -60,10 +85,13 @@ export async function findStaleSnapshotDependencies(): Promise< .map(snapshot => snapshot.path) if (staleSnapshots.length) { return { - dependencyName: name as SnapshotDependencyName, + dependencyName: + name === 'patch' ? 'socket-patch' : (name as SnapshotDependencyName), packageName: dependency.packageName, version, - snapshots: staleSnapshots, + snapshots: dependency.versionSnapshots + ? dependency.snapshots + : staleSnapshots, } } return undefined @@ -78,6 +106,14 @@ export async function findStaleSnapshotDependencies(): Promise< return stale } +export function normalizeSnapshotDependencyName( + name: string, +): SnapshotDependencyMapName | undefined { + if (name === 'socket-patch') {return 'patch'} + if (name === 'coana' || name === 'patch') {return name} + return undefined +} + export function snapshotDependencyUpdateHint( stale: readonly StaleSnapshotDependency[], ): string { diff --git a/scripts/test/snapshots.mts b/scripts/test/snapshots.mts index a141568eb..0df903ca8 100644 --- a/scripts/test/snapshots.mts +++ b/scripts/test/snapshots.mts @@ -6,6 +6,7 @@ import process from 'node:process' import { SNAPSHOT_DEPENDENCIES, assertSnapshotDependenciesCurrent, + normalizeSnapshotDependencyName, } from './snapshot-dependencies.mts' import { isMainModule } from '../lib/is-main-module.mts' import { runMain } from '../lib/run-main.mts' @@ -31,9 +32,9 @@ export interface SnapshotOptions { function missingSnapshotDependencyError(): Error { return new Error( 'Provide a dependency name. Usage: pnpm snapshots:update:dep . ' + - 'Current example: pnpm snapshots:update:dep coana. ' + - 'Partner names to map: socket-patch, socket-python-cli. ' + - `Currently mapped: ${Object.keys(SNAPSHOT_DEPENDENCIES).join(', ')}.`, + 'Examples: pnpm snapshots:update:dep coana, pnpm snapshots:update:dep socket-patch, or pnpm snapshots:update:dep patch. ' + + 'Socket Python CLI still needs a snapshot mapping. ' + + `Currently mapped: ${Object.keys(SNAPSHOT_DEPENDENCIES).join(', ')}, socket-patch.`, ) } @@ -49,9 +50,10 @@ export function parseSnapshotArgs(args: readonly string[]): SnapshotOptions { if (!value?.trim()) { throw missingSnapshotDependencyError() } - if (!(value in SNAPSHOT_DEPENDENCIES)) { + const normalizedName = normalizeSnapshotDependencyName(value) + if (!normalizedName) { throw new Error( - `Unknown snapshot dependency: ${value}; available: ${Object.keys(SNAPSHOT_DEPENDENCIES).join(', ')}`, + `Unknown snapshot dependency: ${value}; available: coana, socket-patch, patch`, ) } if (dependencyName) { @@ -75,7 +77,8 @@ export function nativeSnapshotArgs(update: boolean): string[] { export function dependencySnapshotArgs( dependencyName: SnapshotDependencyName, ): string[] { - const config = SNAPSHOT_DEPENDENCIES[dependencyName] + const config = + SNAPSHOT_DEPENDENCIES[normalizeSnapshotDependencyName(dependencyName)!] return [ '--build', 'test/native/commands/cli.native.test.mts', @@ -165,6 +168,7 @@ export interface SnapshotWorkflowDependencies { acquireLock(): Promise releaseLock(): Promise createArtifactsDir(): Promise + assertDependenciesCurrent(): Promise runNative(update: boolean, artifactsDir: string): void runDependencyNative(dependencyName: SnapshotDependencyName, artifactsDir: string): void runTerminal(update: boolean, artifactsDir: string): void @@ -198,6 +202,7 @@ const DEFAULT_DEPENDENCIES: SnapshotWorkflowDependencies = { ), ) }, + assertDependenciesCurrent: assertSnapshotDependenciesCurrent, runNative(update, artifactsDir) { run(['scripts/test/native.mts', ...nativeSnapshotArgs(update)], { env: { ...process.env, SOCKET_CLI_NATIVE_TEST_ARTIFACTS: artifactsDir }, @@ -246,7 +251,7 @@ export async function runSnapshotWorkflow( dependencies.runDependencyNative(selectedDependencyName, artifactsDir) } else { if (!update) { - await assertSnapshotDependenciesCurrent() + await dependencies.assertDependenciesCurrent() } dependencies.runNative(update, artifactsDir) dependencies.runTerminal(update, artifactsDir) @@ -269,13 +274,13 @@ const SCRIPT_META: ScriptMeta = { describe: 'builds, runs, and reports native terminal snapshot tests', help: `Usage: pnpm snapshots [--update|-u] pnpm snapshots:update - pnpm snapshots:update:dep + pnpm snapshots:update:dep (coana, socket-patch, patch) Builds the native CLI, runs the focused terminal screenshot tests, and writes a self-contained HTML report into a fresh build/native-test-artifacts folder. Pass --update or -u to update the native screenshot baselines. Use the dep command to rebuild and update only snapshots affected by a pinned tool, for - example: pnpm snapshots:update:dep coana.`, + example: pnpm snapshots:update:dep socket-patch.`, } if (isMainModule(import.meta.url)) { diff --git a/test/native-snapshots/socket-patch-version.version.svg b/test/native-snapshots/socket-patch-version.version.svg new file mode 100644 index 000000000..9539e5f38 --- /dev/null +++ b/test/native-snapshots/socket-patch-version.version.svg @@ -0,0 +1,9 @@ + +socket-patch 2.0.0 + + + + + + + \ No newline at end of file diff --git a/test/native/commands/cli.native.test.mts b/test/native/commands/cli.native.test.mts index a26382c4a..dfd8bce2c 100644 --- a/test/native/commands/cli.native.test.mts +++ b/test/native/commands/cli.native.test.mts @@ -1,3 +1,4 @@ +import { promises as fs } from 'node:fs' import path from 'node:path' import { describe, expect, it } from 'vitest' @@ -151,6 +152,20 @@ for (const patchCommand of [ }, 60_000) } +it('captures bundled socket-patch version', async () => { + const packageMetadata = JSON.parse( + await fs.readFile(path.join(process.cwd(), 'package.json'), 'utf8'), + ) as { dependencies: { '@socketsecurity/socket-patch': string } } + await captureFlow({ + flow: 'socket-patch-version', + args: ['patch', '--version'], + expectedText: `socket-patch ${packageMetadata.dependencies['@socketsecurity/socket-patch']}`, + exitCode: 0, + rows: 12, + checkpointName: 'version', + }) +}, 30_000) + describe('native Socket CLI', () => { it('reports the version embedded in the SEA manifest', async () => { await captureFlow({ diff --git a/test/unit/scripts/test/snapshot-dependencies.test.mts b/test/unit/scripts/test/snapshot-dependencies.test.mts index 2af6c0951..5cf429da1 100644 --- a/test/unit/scripts/test/snapshot-dependencies.test.mts +++ b/test/unit/scripts/test/snapshot-dependencies.test.mts @@ -1,8 +1,15 @@ import { describe, expect, it } from 'vitest' -import { snapshotDependencyUpdateHint } from '../../../../scripts/test/snapshot-dependencies.mts' +import { + findStaleSnapshotDependencies, + snapshotDependencyUpdateHint, +} from '../../../../scripts/test/snapshot-dependencies.mts' describe('snapshot dependency hints', () => { + it('matches pinned Coana and socket-patch versions in their snapshots', async () => { + await expect(findStaleSnapshotDependencies()).resolves.toEqual([]) + }) + it('lists stale files and the focused refresh command', () => { expect( snapshotDependencyUpdateHint([ diff --git a/test/unit/scripts/test/snapshots.test.mts b/test/unit/scripts/test/snapshots.test.mts index aa49d7104..9d0922b39 100644 --- a/test/unit/scripts/test/snapshots.test.mts +++ b/test/unit/scripts/test/snapshots.test.mts @@ -25,11 +25,19 @@ describe('native snapshot runner', () => { update: true, dependencyName: 'coana', }) + expect(parseSnapshotArgs(['--update', '--dep', 'patch'])).toEqual({ + update: true, + dependencyName: 'patch', + }) + expect(parseSnapshotArgs(['--update', '--dep', 'socket-patch'])).toEqual({ + update: true, + dependencyName: 'socket-patch', + }) expect(() => parseSnapshotArgs(['--dep', 'unknown', '--update'])).toThrow( 'Unknown snapshot dependency', ) expect(() => parseSnapshotArgs(['--update', '--dep'])).toThrow( - 'Partner names to map: socket-patch, socket-python-cli', + 'pnpm snapshots:update:dep socket-patch', ) expect(() => parseSnapshotArgs(['--update', '--dep='])).toThrow( 'Provide a dependency name', @@ -53,6 +61,18 @@ describe('native snapshot runner', () => { ]) }) + it('selects patch help and version snapshots for a socket-patch refresh', () => { + const patchArgs = [ + '--build', + 'test/native/commands/cli.native.test.mts', + '--testNamePattern', + 'captures bundled patch help for (apply|rollback|get|scan|list|remove|setup|repair)|captures bundled socket-patch version', + '--update', + ] + expect(dependencySnapshotArgs('socket-patch')).toEqual(patchArgs) + expect(dependencySnapshotArgs('patch')).toEqual(patchArgs) + }) + it('builds and runs only the native screenshot suites', () => { expect(nativeSnapshotArgs(false)).toEqual([ '--build', @@ -108,6 +128,7 @@ describe('native snapshot runner', () => { expect(events).toEqual([ 'acquire', 'create-artifacts', + 'assert-dependencies-current', 'native:false:/artifacts/run-a', 'release', ]) @@ -145,14 +166,17 @@ function workflowDependencies( events.push('create-artifacts') return '/artifacts/run-a' }, + async assertDependenciesCurrent() { + events.push('assert-dependencies-current') + }, runNative(update, artifactsDir) { events.push(`native:${update}:${artifactsDir}`) if (nativeFails) { throw new Error('native failed') } }, - runDependencyNative(dependency, artifactsDir) { - events.push(`dependency-native:${dependency}:${artifactsDir}`) + runDependencyNative(dependencyName, artifactsDir) { + events.push(`dependency-native:${dependencyName}:${artifactsDir}`) }, runTerminal(update, artifactsDir) { events.push(`terminal:${update}:${artifactsDir}`)