import assert from 'node:assert/strict'; import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import test from 'node:test'; import { appendWindowsRuntimeGitHubOutputs, readWindowsRuntimePin, serializeWindowsRuntimePin, validateWindowsRuntimePin, WINDOWS_RUNTIME_LICENSE_CLAIM, WINDOWS_RUNTIME_PIN_PATH, } from './windows-runtime-pin.mjs'; import { pinFromUpstreamRelease, refreshWindowsRuntimePin, runtimePinAgeDays, selectNewestWindowsRuntimePin, WINDOWS_RUNTIME_REFRESH_AFTER_DAYS, } from './update-windows-runtime-pin.mjs'; const CURRENT_PIN = readWindowsRuntimePin(); function releaseFixture({ date = '2026-08-28', commit = 'e8673660ab123456789012345678901234567890', digest = '470437b5dc9f8c74092fdfab668e89bedf7b1a6385a53ffadf241a6a7a4c6ffb', runId = '33215046953', publishedAt = `${date}T22:32:29Z`, } = {}) { const compactDate = date.replaceAll('-', ''); const shortCommit = commit.slice(0, 10); const tag = `${date}-${shortCommit}`; const name = `mpv-dev-lgpl-x86_64-${compactDate}-git-${shortCommit}.7z`; return { draft: false, prerelease: false, tag_name: tag, published_at: publishedAt, body: [ `MPV Git commit: https://github.com/mpv-player/mpv/commit/${commit}`, `Build Details: https://github.com/zhongfly/mpv-winbuild/actions/runs/${runId}`, ].join('\n'), assets: [ { name, digest: `sha256:${digest}`, browser_download_url: `https://github.com/zhongfly/mpv-winbuild/releases/download/${tag}/${name}`, }, { name: `mpv-dev-lgpl-x86_64-v3-${compactDate}-git-${shortCommit}.7z`, digest: `sha256:${'a'.repeat(64)}`, browser_download_url: 'https://example.invalid/v3.7z', }, ], }; } function response({ ok = true, status = 200, json } = {}) { return { ok, status, statusText: ok ? 'OK' : 'Not Found', json: async () => json, }; } function createPinFixture() { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'impv-win-pin-')); const pinPath = path.join(root, 'windows-runtime-pin.json'); fs.writeFileSync(pinPath, serializeWindowsRuntimePin(CURRENT_PIN)); return { root, pinPath }; } test('checked-in Windows runtime pin is internally consistent', () => { assert.equal(WINDOWS_RUNTIME_PIN_PATH.endsWith('.json'), true); assert.match(CURRENT_PIN.asset.sha256, /^[a-f0-9]{64}$/); assert.match(CURRENT_PIN.asset.name, /^mpv-dev-lgpl-x86_64-/); assert.doesNotMatch(CURRENT_PIN.asset.name, /-v3-/); assert.equal( CURRENT_PIN.upstream.licenseClaim, WINDOWS_RUNTIME_LICENSE_CLAIM ); }); test('pin validation rejects a softened license-verification statement', () => { const invalid = structuredClone(CURRENT_PIN); invalid.upstream.licenseClaim = 'Verified LGPL runtime.'; assert.throws( () => validateWindowsRuntimePin(invalid), /limited verification statement/ ); }); test('upstream release selection excludes the v3 archive and keeps evidence', () => { const older = releaseFixture({ date: '2026-08-27', commit: '182fa6ca49123456789012345678901234567890', publishedAt: '2026-08-27T12:41:23Z', }); const latest = releaseFixture(); const pin = selectNewestWindowsRuntimePin([older, latest]); assert.equal(pin.releaseTag, latest.tag_name); assert.equal(pin.asset.name, latest.assets[0].name); assert.equal(pin.asset.sha256, latest.assets[0].digest.slice(7)); assert.equal(pin.upstream.mpvCommit, latest.body.match(/[a-f0-9]{40}/)[0]); assert.doesNotMatch(pin.asset.name, /-v3-/); }); test('upstream release must provide GitHub digest and build evidence', () => { const missingDigest = releaseFixture(); delete missingDigest.assets[0].digest; assert.throws( () => pinFromUpstreamRelease(missingDigest), /must expose a GitHub SHA-256 digest/ ); const missingEvidence = releaseFixture(); missingEvidence.body = ''; assert.throws( () => pinFromUpstreamRelease(missingEvidence), /lacks the expected mpv commit or build-run evidence/ ); }); test('young available pin does not query releases or rewrite the file', async () => { const fixture = createPinFixture(); let requestCount = 0; try { const result = await refreshWindowsRuntimePin({ pinPath: fixture.pinPath, now: new Date('2026-08-29T00:00:00Z'), fetchImpl: async () => { requestCount += 1; return response(); }, }); assert.equal(result.changed, false); assert.equal(result.reason, 'current'); assert.equal(requestCount, 1); assert.equal( fs.readFileSync(fixture.pinPath, 'utf8'), serializeWindowsRuntimePin(CURRENT_PIN) ); } finally { fs.rmSync(fixture.root, { recursive: true, force: true }); } }); test('unavailable pin rotates to the newest downloadable release', async () => { const fixture = createPinFixture(); const latest = releaseFixture(); const requests = []; try { const result = await refreshWindowsRuntimePin({ pinPath: fixture.pinPath, now: new Date('2026-08-29T00:00:00Z'), fetchImpl: async (url, options = {}) => { requests.push([url, options.method ?? 'GET']); if (url === CURRENT_PIN.asset.url) { return response({ ok: false, status: 404 }); } if (url.startsWith('https://api.github.com/')) { return response({ json: [latest] }); } return response(); }, }); assert.equal(result.changed, true); assert.equal(result.reason, 'unavailable'); assert.equal( readWindowsRuntimePin(fixture.pinPath).releaseTag, latest.tag_name ); assert.deepEqual( requests.map((request) => request[1]), ['HEAD', 'GET', 'HEAD'] ); } finally { fs.rmSync(fixture.root, { recursive: true, force: true }); } }); test('age threshold rotates an available pin before upstream retention', async () => { const fixture = createPinFixture(); const latest = releaseFixture(); try { const result = await refreshWindowsRuntimePin({ pinPath: fixture.pinPath, now: new Date('2026-09-05T13:00:00Z'), fetchImpl: async (url) => url.startsWith('https://api.github.com/') ? response({ json: [latest] }) : response(), }); assert.equal( runtimePinAgeDays(CURRENT_PIN, new Date('2026-09-05T13:00:00Z')) >= WINDOWS_RUNTIME_REFRESH_AFTER_DAYS, true ); assert.equal(result.changed, true); assert.equal(result.reason, 'age-threshold'); } finally { fs.rmSync(fixture.root, { recursive: true, force: true }); } }); test('GitHub output exposes only the validated checked-in pin', () => { const root = fs.mkdtempSync(path.join(os.tmpdir(), 'impv-win-output-')); const outputPath = path.join(root, 'output'); try { appendWindowsRuntimeGitHubOutputs(CURRENT_PIN, outputPath); const output = fs.readFileSync(outputPath, 'utf8'); assert.match(output, new RegExp(`sha256=${CURRENT_PIN.asset.sha256}`)); assert.match( output, new RegExp(`release-tag=${CURRENT_PIN.releaseTag}`) ); } finally { fs.rmSync(root, { recursive: true, force: true }); } });