diff --git a/scripts/update.mjs b/scripts/update.mjs index a63e82e..dab34aa 100644 --- a/scripts/update.mjs +++ b/scripts/update.mjs @@ -49,40 +49,38 @@ async function main() { process.stdout.write('\r\x1b[K') } - // Always update Socket packages when applying (bypass taze maturity period). - if (apply) { - if (!quiet) { - logger.progress('Updating Socket packages...') - } + // Always update Socket packages (bypass taze maturity period). + if (!quiet) { + logger.progress('Updating Socket packages...') + } - const socketResult = await spawn( - 'pnpm', - [ - 'update', - '@socketsecurity/*', - '@socketregistry/*', - '@socketbin/*', - '--latest', - '-r', - ], - { - shell: WIN32, - stdio: quiet ? 'pipe' : 'inherit', - }, - ) + const socketResult = await spawn( + 'pnpm', + [ + 'update', + '@socketsecurity/*', + '@socketregistry/*', + '@socketbin/*', + '--latest', + '-r', + ], + { + shell: WIN32, + stdio: quiet ? 'pipe' : 'inherit', + }, + ) - // Clear progress line. - if (!quiet) { - process.stdout.write('\r\x1b[K') - } + // Clear progress line. + if (!quiet) { + process.stdout.write('\r\x1b[K') + } - if (socketResult.code !== 0) { - if (!quiet) { - logger.fail('Failed to update Socket packages') - } - process.exitCode = 1 - return + if (socketResult.code !== 0) { + if (!quiet) { + logger.fail('Failed to update Socket packages') } + process.exitCode = 1 + return } if (result.code !== 0) { diff --git a/src/dlx/binary.ts b/src/dlx/binary.ts index 41925cd..e9d81bb 100644 --- a/src/dlx/binary.ts +++ b/src/dlx/binary.ts @@ -1,10 +1,9 @@ /** @fileoverview DLX binary execution utilities for Socket ecosystem. */ -import { getArch, getPlatform, WIN32 } from '../constants/platform' +import { getArch, WIN32 } from '../constants/platform' import { DLX_BINARY_CACHE_TTL } from '../constants/time' import { generateCacheKey } from './cache' -import { dlxManifest } from './manifest' import { httpDownload } from '../http-request' import { isDir, readJson, safeDelete, safeMkdir } from '../fs' import { isObjectObject } from '../objects' @@ -13,7 +12,6 @@ import { getSocketDlxDir } from '../paths/socket' import { processLock } from '../process-lock' import { spawn } from '../spawn' -import type { ChecksumAlgorithm } from './manifest' import type { SpawnExtra, SpawnOptions } from '../spawn' let _crypto: typeof import('node:crypto') | undefined @@ -80,9 +78,9 @@ export interface DlxBinaryOptions { name?: string | undefined /** - * Expected checksum (sha256) for verification. + * Expected SRI integrity hash (sha512-) for verification. */ - checksum?: string | undefined + integrity?: string | undefined /** * Cache TTL in milliseconds (default: 7 days). @@ -124,65 +122,40 @@ export interface DlxBinaryResult { /** * Metadata structure for cached binaries (.dlx-metadata.json). - * Unified schema shared across TypeScript (dlxBinary) and C++ (socket_macho_decompress). + * Unified schema shared across TypeScript (dlxBinary) and C++ stub extractor. * - * Core Fields (present in all implementations): + * Fields: * - version: Schema version (currently "1.0.0") * - cache_key: First 16 chars of SHA-512 hash (matches directory name) * - timestamp: Unix timestamp in milliseconds - * - checksum: Full hash of cached binary (SHA-512 for C++, SHA-256 for TypeScript) - * - checksum_algorithm: "sha512" or "sha256" - * - platform: "darwin" | "linux" | "win32" - * - arch: "x64" | "arm64" + * - integrity: SRI hash (sha512-, aligned with npm) * - size: Size of cached binary in bytes * - source: Origin information - * - type: "download" (from URL) or "decompression" (from embedded binary) + * - type: "download" | "extract" | "package" * - url: Download URL (if type is "download") - * - path: Source binary path (if type is "decompression") + * - path: Source binary path (if type is "extract") + * - spec: Package spec (if type is "package") + * - update_check: Update checking metadata (optional) + * - last_check: Timestamp of last update check + * - last_notification: Timestamp of last user notification + * - latest_known: Latest known version string * - * Extra Fields (implementation-specific): - * - For C++ decompression: - * - compressed_size: Size of compressed data in bytes - * - compression_algorithm: Brotli level (numeric) - * - compression_ratio: original_size / compressed_size - * - * Example (TypeScript download): + * Example: * ```json * { * "version": "1.0.0", * "cache_key": "a1b2c3d4e5f67890", * "timestamp": 1730332800000, - * "checksum": "sha256-abc123...", - * "checksum_algorithm": "sha256", - * "platform": "darwin", - * "arch": "arm64", + * "integrity": "sha512-abc123base64...", * "size": 15000000, * "source": { * "type": "download", * "url": "https://example.com/binary" - * } - * } - * ``` - * - * Example (C++ decompression): - * ```json - * { - * "version": "1.0.0", - * "cache_key": "0123456789abcdef", - * "timestamp": 1730332800000, - * "checksum": "sha512-def456...", - * "checksum_algorithm": "sha512", - * "platform": "darwin", - * "arch": "arm64", - * "size": 13000000, - * "source": { - * "type": "decompression", - * "path": "/usr/local/bin/socket" * }, - * "extra": { - * "compressed_size": 1700000, - * "compression_algorithm": 3, - * "compression_ratio": 7.647 + * "update_check": { + * "last_check": 1730332800000, + * "last_notification": 1730246400000, + * "latest_known": "2.1.0" * } * } * ``` @@ -193,17 +166,19 @@ export interface DlxMetadata { version: string cache_key: string timestamp: number - checksum: string - checksum_algorithm: string - platform: string - arch: string + integrity: string size: number source?: { - type: 'download' | 'decompression' + type: 'download' | 'extract' | 'package' url?: string path?: string + spec?: string + } + update_check?: { + last_check: number + last_notification: number + latest_known: string } - extra?: Record } /** @@ -253,7 +228,7 @@ async function isCacheValid( async function downloadBinaryFile( url: string, destPath: string, - checksum?: string | undefined, + integrity?: string | undefined, ): Promise { // Use process lock to prevent concurrent downloads. // Lock is placed in the cache entry directory as 'concurrency.lock'. @@ -270,11 +245,13 @@ async function downloadBinaryFile( if (fs.existsSync(destPath)) { const stats = await fs.promises.stat(destPath) if (stats.size > 0) { - // File exists, compute and return checksum. + // File exists, compute and return SRI integrity hash. const fileBuffer = await fs.promises.readFile(destPath) - const hasher = crypto.createHash('sha256') - hasher.update(fileBuffer) - return hasher.digest('hex') + const hash = crypto + .createHash('sha512') + .update(fileBuffer) + .digest('base64') + return `sha512-${hash}` } } @@ -290,18 +267,20 @@ async function downloadBinaryFile( ) } - // Compute checksum of downloaded file. + // Compute SRI integrity hash of downloaded file. const fileBuffer = await fs.promises.readFile(destPath) - const hasher = crypto.createHash('sha256') - hasher.update(fileBuffer) - const actualChecksum = hasher.digest('hex') - - // Verify checksum if provided. - if (checksum && actualChecksum !== checksum) { + const hash = crypto + .createHash('sha512') + .update(fileBuffer) + .digest('base64') + const actualIntegrity = `sha512-${hash}` + + // Verify integrity if provided. + if (integrity && actualIntegrity !== integrity) { // Clean up invalid file. await safeDelete(destPath) throw new Error( - `Checksum mismatch: expected ${checksum}, got ${actualChecksum}`, + `Integrity mismatch: expected ${integrity}, got ${actualIntegrity}`, ) } @@ -310,7 +289,7 @@ async function downloadBinaryFile( await fs.promises.chmod(destPath, 0o755) } - return actualChecksum + return actualIntegrity }, { // Align with npm npx locking strategy. @@ -322,31 +301,22 @@ async function downloadBinaryFile( /** * Write metadata for a cached binary. - * Writes to both per-directory metadata file (for backward compatibility) - * and global manifest (~/.socket/_dlx/.dlx-manifest.json). * Uses unified schema shared with C++ decompressor and CLI dlxBinary. * Schema documentation: See DlxMetadata interface in this file (exported). - * Core fields: version, cache_key, timestamp, checksum, checksum_algorithm, platform, arch, size, source - * Note: This implementation uses SHA-256 checksums instead of SHA-512. */ async function writeMetadata( cacheEntryPath: string, cacheKey: string, url: string, - binaryName: string, - checksum: string, + integrity: string, size: number, ): Promise { - // Write per-directory metadata file for backward compatibility. const metaPath = getMetadataPath(cacheEntryPath) - const metadata = { + const metadata: DlxMetadata = { version: '1.0.0', cache_key: cacheKey, timestamp: Date.now(), - checksum, - checksum_algorithm: 'sha256' as ChecksumAlgorithm, - platform: getPlatform(), - arch: getArch(), + integrity, size, source: { type: 'download', @@ -355,25 +325,6 @@ async function writeMetadata( } const fs = getFs() await fs.promises.writeFile(metaPath, JSON.stringify(metadata, null, 2)) - - // Write to global manifest. - try { - const spec = `${url}:${binaryName}` - await dlxManifest.setBinaryEntry(spec, cacheKey, { - checksum, - checksum_algorithm: metadata.checksum_algorithm, - platform: metadata.platform, - arch: metadata.arch, - size, - source: { - type: 'download', - url, - }, - }) - } catch { - // Silently ignore manifest write errors - not critical. - // The per-directory metadata is the source of truth for now. - } } /** @@ -454,8 +405,8 @@ export async function dlxBinary( ): Promise { const { cacheTtl = DLX_BINARY_CACHE_TTL, - checksum, force: userForce = false, + integrity, name, spawnOptions, url, @@ -475,7 +426,7 @@ export async function dlxBinary( const binaryPath = normalizePath(path.join(cacheEntryDir, binaryName)) let downloaded = false - let computedChecksum = checksum + let computedIntegrity = integrity // Check if we need to download. if ( @@ -483,7 +434,7 @@ export async function dlxBinary( fs.existsSync(cacheEntryDir) && (await isCacheValid(cacheEntryDir, cacheTtl)) ) { - // Binary is cached and valid, read the checksum from metadata. + // Binary is cached and valid, read the integrity from metadata. try { const metaPath = getMetadataPath(cacheEntryDir) const metadata = await readJson(metaPath, { throws: false }) @@ -491,10 +442,10 @@ export async function dlxBinary( metadata && typeof metadata === 'object' && !Array.isArray(metadata) && - typeof (metadata as Record)['checksum'] === 'string' + typeof (metadata as Record)['integrity'] === 'string' ) { - computedChecksum = (metadata as Record)[ - 'checksum' + computedIntegrity = (metadata as Record)[ + 'integrity' ] as string } else { // If metadata is invalid, re-download. @@ -535,7 +486,7 @@ export async function dlxBinary( } // Download the binary. - computedChecksum = await downloadBinaryFile(url, binaryPath, checksum) + computedIntegrity = await downloadBinaryFile(url, binaryPath, integrity) // Get file size for metadata. const stats = await fs.promises.stat(binaryPath) @@ -543,8 +494,7 @@ export async function dlxBinary( cacheEntryDir, cacheKey, url, - binaryName, - computedChecksum || '', + computedIntegrity || '', stats.size, ) } @@ -599,8 +549,8 @@ export async function downloadBinary( ): Promise<{ binaryPath: string; downloaded: boolean }> { const { cacheTtl = DLX_BINARY_CACHE_TTL, - checksum, force = false, + integrity, name, url, } = { __proto__: null, ...options } as DlxBinaryOptions @@ -652,7 +602,11 @@ export async function downloadBinary( } // Download the binary. - const computedChecksum = await downloadBinaryFile(url, binaryPath, checksum) + const computedIntegrity = await downloadBinaryFile( + url, + binaryPath, + integrity, + ) // Get file size for metadata. const stats = await fs.promises.stat(binaryPath) @@ -660,8 +614,7 @@ export async function downloadBinary( cacheEntryDir, cacheKey, url, - binaryName, - computedChecksum || '', + computedIntegrity || '', stats.size, ) downloaded = true @@ -733,10 +686,8 @@ export function getDlxCachePath(): string { export async function listDlxCache(): Promise< Array<{ age: number - arch: string - checksum: string + integrity: string name: string - platform: string size: number url: string }> @@ -792,10 +743,8 @@ export async function listDlxCache(): Promise< results.push({ age: now - ((metaObj['timestamp'] as number) || 0), - arch: (metaObj['arch'] as string) || 'unknown', - checksum: (metaObj['checksum'] as string) || '', + integrity: (metaObj['integrity'] as string) || '', name: binaryFile, - platform: (metaObj['platform'] as string) || 'unknown', size: binaryStats.size, url, }) diff --git a/src/dlx/manifest.ts b/src/dlx/manifest.ts index e76c0e1..baaf093 100644 --- a/src/dlx/manifest.ts +++ b/src/dlx/manifest.ts @@ -77,17 +77,23 @@ export interface PackageDetails { * Details for binary download entries. */ export interface BinaryDetails { - checksum: string - checksum_algorithm: ChecksumAlgorithm + /** SRI integrity hash (sha512-, aligned with npm). */ + integrity: string platform: string arch: string size: number source: { - type: 'download' - url: string + type: 'download' | 'extract' + url?: string + path?: string + } + /** Update check metadata (same structure as packages). */ + update_check?: { + last_check: number + last_notification: number + latest_known: string } } -export type ChecksumAlgorithm = 'sha256' | 'sha512' /** * Unified manifest entry for all cached items (packages and binaries). diff --git a/test/unit/dlx/binary.test.mts b/test/unit/dlx/binary.test.mts index 13d5b2d..e6aaf93 100644 --- a/test/unit/dlx/binary.test.mts +++ b/test/unit/dlx/binary.test.mts @@ -40,12 +40,12 @@ beforeAll(async () => { if (url === '/binary') { res.writeHead(200, { 'Content-Type': 'application/octet-stream' }) res.end('#!/bin/bash\necho "test binary"') - } else if (url === '/binary-with-checksum') { + } else if (url === '/binary-with-integrity') { const content = '#!/bin/bash\necho "verified binary"' - const hash = createHash('sha256').update(content).digest('hex') + const hash = createHash('sha512').update(content).digest('base64') res.writeHead(200, { 'Content-Type': 'application/octet-stream', - 'X-Checksum': hash, + 'X-Integrity': `sha512-${hash}`, }) res.end(content) } else if (url === '/binary-invalid-checksum') { @@ -245,19 +245,18 @@ describe.sequential('dlx-binary', () => { }, 'dlxBinary-quiet-') }) - it('should verify checksum when provided', async () => { + it('should verify integrity when provided', async () => { await runWithTempDir(async tmpDir => { const restoreHome = mockHomeDir(tmpDir) try { const content = '#!/bin/bash\necho "verified binary"' - const expectedChecksum = createHash('sha256') - .update(content) - .digest('hex') - const url = `${httpBaseUrl}/binary-with-checksum` + const hash = createHash('sha512').update(content).digest('base64') + const expectedIntegrity = `sha512-${hash}` + const url = `${httpBaseUrl}/binary-with-integrity` const result = await dlxBinary(['--version'], { - checksum: expectedChecksum, + integrity: expectedIntegrity, name: 'verified-binary', url, }) @@ -267,28 +266,29 @@ describe.sequential('dlx-binary', () => { } finally { restoreHome() } - }, 'dlxBinary-checksum-') + }, 'dlxBinary-integrity-') }) - it('should throw on checksum mismatch', async () => { + it('should throw on integrity mismatch', async () => { await runWithTempDir(async tmpDir => { const restoreHome = mockHomeDir(tmpDir) try { const url = `${httpBaseUrl}/binary-invalid-checksum` - const wrongChecksum = 'a'.repeat(64) + // SHA-512 base64 is 88 characters. + const wrongIntegrity = `sha512-${'a'.repeat(86)}==` await expect( dlxBinary(['--version'], { - checksum: wrongChecksum, - name: 'invalid-checksum-binary', + integrity: wrongIntegrity, + name: 'invalid-integrity-binary', url, }), - ).rejects.toThrow(/Checksum mismatch/) + ).rejects.toThrow(/Integrity mismatch/) } finally { restoreHome() } - }, 'dlxBinary-bad-checksum-') + }, 'dlxBinary-bad-integrity-') }) it('should throw on download failure', async () => { @@ -527,7 +527,7 @@ describe.sequential('dlx-binary', () => { }, 'dlxBinary-array-meta-') }) - it('should handle metadata with missing checksum', async () => { + it('should handle metadata with missing integrity', async () => { await runWithTempDir(async tmpDir => { const restoreHome = mockHomeDir(tmpDir) @@ -536,13 +536,13 @@ describe.sequential('dlx-binary', () => { // First download const result1 = await dlxBinary(['--version'], { - name: 'no-checksum-meta-binary', + name: 'no-integrity-meta-binary', url, }) await result1.spawnPromise.catch(() => {}) - // Write metadata without checksum - const name = 'no-checksum-meta-binary' + // Write metadata without integrity + const name = 'no-integrity-meta-binary' const spec = `${url}:${name}` const cacheKey = createHash('sha512') .update(spec) @@ -558,7 +558,7 @@ describe.sequential('dlx-binary', () => { // Second call should re-download const result = await dlxBinary(['--version'], { - name: 'no-checksum-meta-binary', + name: 'no-integrity-meta-binary', url, }) @@ -567,7 +567,7 @@ describe.sequential('dlx-binary', () => { } finally { restoreHome() } - }, 'dlxBinary-no-checksum-meta-') + }, 'dlxBinary-no-integrity-meta-') }) it('should pass args to spawn', async () => { @@ -842,9 +842,8 @@ describe.sequential('dlx-binary', () => { const entry = list[0] expect(entry.name).toBe('list-binary') expect(entry.url).toBe(url) - expect(entry.platform).toBe(os.platform()) - expect(entry.arch).toBe(os.arch()) - expect(entry.checksum).toBeDefined() + expect(entry.integrity).toBeDefined() + expect(entry.integrity).toMatch(/^sha512-/) expect(entry.size).toBeGreaterThan(0) expect(entry.age).toBeGreaterThanOrEqual(0) } finally { @@ -929,13 +928,11 @@ describe.sequential('dlx-binary', () => { const entryPath = path.join(cachePath, 'no-binary-entry') await fs.mkdir(entryPath, { recursive: true }) - // Write metadata but no binary + // Write metadata but no binary. await fs.writeFile( path.join(entryPath, '.dlx-metadata.json'), JSON.stringify({ - arch: os.arch(), - checksum: 'test', - platform: os.platform(), + integrity: 'sha512-test', timestamp: Date.now(), url: 'test', }), @@ -974,9 +971,7 @@ describe.sequential('dlx-binary', () => { const entry = list[0] expect(entry.url).toBe('') - expect(entry.platform).toBe('unknown') - expect(entry.arch).toBe('unknown') - expect(entry.checksum).toBe('') + expect(entry.integrity).toBe('') } finally { restoreHome() } @@ -1047,24 +1042,22 @@ describe.sequential('dlx-binary', () => { const entryPath = path.join(cachePath, 'stat-fail-entry') await fs.mkdir(entryPath, { recursive: true }) - // Write metadata + // Write metadata. await fs.writeFile( path.join(entryPath, '.dlx-metadata.json'), JSON.stringify({ - arch: os.arch(), - checksum: 'test', - platform: os.platform(), + integrity: 'sha512-test', timestamp: Date.now(), url: 'test', }), 'utf8', ) - // Create binary + // Create binary. const binaryPath = path.join(entryPath, 'binary') await fs.writeFile(binaryPath, '', 'utf8') - // Delete binary to cause stat failure + // Delete binary to cause stat failure. await fs.unlink(binaryPath) const list = await listDlxCache() diff --git a/test/unit/dlx/manifest.test.mts b/test/unit/dlx/manifest.test.mts index cc25b1b..4e3b4f5 100644 --- a/test/unit/dlx/manifest.test.mts +++ b/test/unit/dlx/manifest.test.mts @@ -44,8 +44,7 @@ describe('dlx-manifest', () => { cache_key: 'test-binary', timestamp: Date.now(), details: { - checksum: 'abc123', - checksum_algorithm: 'sha256', + integrity: 'sha512-abc123base64', platform: 'linux', arch: 'x64', size: 1024, @@ -63,8 +62,7 @@ describe('dlx-manifest', () => { cache_key: 'test-binary', timestamp: Date.now(), details: { - checksum: 'abc123', - checksum_algorithm: 'sha256', + integrity: 'sha512-abc123base64', platform: 'linux', arch: 'x64', size: 1024, @@ -116,8 +114,7 @@ describe('dlx-manifest', () => { cache_key: 'binary-key', timestamp: Date.now(), details: { - checksum: 'sha256hash', - checksum_algorithm: 'sha256', + integrity: 'sha512-abc123base64hash', platform: 'darwin', arch: 'arm64', size: 2048, @@ -129,7 +126,7 @@ describe('dlx-manifest', () => { } expect(entry.type).toBe('binary') if (isBinaryEntry(entry)) { - expect(entry.details.checksum).toBe('sha256hash') + expect(entry.details.integrity).toBe('sha512-abc123base64hash') expect(entry.details.platform).toBe('darwin') expect(entry.details.arch).toBe('arm64') } @@ -157,8 +154,7 @@ describe('dlx-manifest', () => { cache_key: 'test', timestamp: Date.now(), details: { - checksum: 'abc', - checksum_algorithm: 'sha512', + integrity: 'sha512-abc123base64', platform: 'win32', arch: 'x64', size: 100, @@ -167,36 +163,23 @@ describe('dlx-manifest', () => { } if (isBinaryEntry(entry)) { - // TypeScript should know entry.details is BinaryDetails - expect(entry.details.checksum).toBeDefined() - expect(entry.details.checksum_algorithm).toBe('sha512') + // TypeScript should know entry.details is BinaryDetails. + expect(entry.details.integrity).toBeDefined() + expect(entry.details.integrity).toMatch(/^sha512-/) } }) }) - describe('checksum algorithms', () => { - it('should support sha256', () => { + describe('integrity format', () => { + it('should support SRI integrity hash format', () => { const details: BinaryDetails = { - checksum: 'abc123', - checksum_algorithm: 'sha256', + integrity: 'sha512-abc123base64hash==', platform: 'linux', arch: 'x64', size: 1024, source: { type: 'download', url: 'https://example.com' }, } - expect(details.checksum_algorithm).toBe('sha256') - }) - - it('should support sha512', () => { - const details: BinaryDetails = { - checksum: 'def456', - checksum_algorithm: 'sha512', - platform: 'darwin', - arch: 'arm64', - size: 2048, - source: { type: 'download', url: 'https://example.com' }, - } - expect(details.checksum_algorithm).toBe('sha512') + expect(details.integrity).toMatch(/^sha512-/) }) }) @@ -267,8 +250,7 @@ describe('dlx-manifest', () => { it('should return binary entry', async () => { const details: BinaryDetails = { - checksum: 'abc123', - checksum_algorithm: 'sha256', + integrity: 'sha512-abc123base64', platform: 'linux', arch: 'x64', size: 2048, @@ -358,8 +340,7 @@ describe('dlx-manifest', () => { describe('setBinaryEntry', () => { it('should store binary entry', async () => { const details: BinaryDetails = { - checksum: 'xyz789', - checksum_algorithm: 'sha512', + integrity: 'sha512-xyz789base64', platform: 'darwin', arch: 'arm64', size: 10_000, @@ -371,7 +352,7 @@ describe('dlx-manifest', () => { expect(entry).toBeDefined() expect(isBinaryEntry(entry!)).toBe(true) if (isBinaryEntry(entry!)) { - expect(entry.details.checksum).toBe('xyz789') + expect(entry.details.integrity).toBe('sha512-xyz789base64') expect(entry.details.platform).toBe('darwin') expect(entry.details.arch).toBe('arm64') } @@ -379,8 +360,7 @@ describe('dlx-manifest', () => { it('should update existing binary entry', async () => { const details1: BinaryDetails = { - checksum: 'old', - checksum_algorithm: 'sha256', + integrity: 'sha512-oldbase64', platform: 'linux', arch: 'x64', size: 1000, @@ -389,8 +369,7 @@ describe('dlx-manifest', () => { await manifest.setBinaryEntry('bin', 'key1', details1) const details2: BinaryDetails = { - checksum: 'new', - checksum_algorithm: 'sha512', + integrity: 'sha512-newbase64', platform: 'win32', arch: 'x64', size: 2000, @@ -400,7 +379,7 @@ describe('dlx-manifest', () => { const entry = manifest.getManifestEntry('bin') if (isBinaryEntry(entry!)) { - expect(entry.details.checksum).toBe('new') + expect(entry.details.integrity).toBe('sha512-newbase64') expect(entry.cache_key).toBe('key2') } })