mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-11 22:06:17 -08:00
test(refactor): [BASE-02] freeze real-browser public API contracts
This commit is contained in:
1 parent
833cc11674
commit
b382ec08f7
12 files changed
+4034
-22
No files matched your search
@@ -0,0 +1,65 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { hash, refactorDir } from './releases.mjs'
|
||||
|
||||
export const checkIds = [
|
||||
'API-01.version', 'API-01.defaults-independent', 'API-01.default-language',
|
||||
'API-01.instance-registration', 'API-01.container-selector', 'API-02.video',
|
||||
'API-02.bound-query', 'API-06.sync-chapter', 'API-06.add-return',
|
||||
'API-06.result-call', 'API-04.on-return', 'API-04.off',
|
||||
'API-05.destroy-return', 'API-05.instance-cleanup',
|
||||
]
|
||||
|
||||
export function validateApiReport(report) {
|
||||
assert.equal(report.kind, 'public-api')
|
||||
assert.deepEqual(report.errors, [], 'Browser capture failed')
|
||||
assert.deepEqual(report.checks, checkIds.map(id => ({ id, passed: true })), 'Missing or failed API checks')
|
||||
assert(report.environment.userAgent && report.environment.language, 'Missing browser environment')
|
||||
assert(Number.isFinite(Date.parse(report.capture.capturedAt)), 'Missing capture timestamp')
|
||||
const releases = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/releases.json'), 'utf8')).releases
|
||||
const expectedSources = releases.map(release => {
|
||||
const member = `package/${release.manifest.main.replace(/^\.\//, '')}`
|
||||
return { name: release.name, version: release.version, integrity: release.integrity, member, sha256: release.files[member] }
|
||||
})
|
||||
assert.deepEqual(report.capture.releases, expectedSources, 'Published sources differ')
|
||||
assert.deepEqual(report.scripts, [...expectedSources.map(release => `/releases/${release.name}/${release.member.slice('package/'.length)}`), '/fixtures/api.js'], 'Unexpected loaded scripts')
|
||||
assert.equal(report.capture.fixtureHashAlgorithm, 'sha256-lf')
|
||||
for (const name of ['api.html', 'api.js']) {
|
||||
const source = fs.readFileSync(path.join(refactorDir, 'fixtures', name), 'utf8').replaceAll('\r\n', '\n')
|
||||
assert.equal(report.capture.fixtures[name], hash(source), `Fixture changed: ${name}; review the baseline explicitly`)
|
||||
}
|
||||
for (const section of ['static', 'prototype', 'emitterPrototype', 'defaults', 'constants', 'config', 'utilityDescriptors', 'instance', 'instanceResolved', 'chapterFactory', 'chapterResult', 'integrations']) {
|
||||
assert(report.snapshot?.[section] && Object.keys(report.snapshot[section]).length, `Missing API section: ${section}`)
|
||||
}
|
||||
assert.deepEqual(report.snapshot.defaults.lang, { $environment: 'navigator.language.toLowerCase()' })
|
||||
}
|
||||
|
||||
export function compareApiSnapshots(expected, actual) {
|
||||
assert.deepEqual(actual, expected, 'Public API snapshot differs; investigate instead of overwriting the baseline')
|
||||
}
|
||||
|
||||
export function verifyFrozenApi() {
|
||||
const frozen = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/public-api.json'), 'utf8'))
|
||||
assert.equal(frozen.schemaVersion, 1)
|
||||
validateApiReport(frozen.report)
|
||||
assert.equal(hash(JSON.stringify(frozen.report.snapshot)), frozen.verification.snapshotSha256, 'Frozen API snapshot digest differs')
|
||||
return frozen
|
||||
}
|
||||
|
||||
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
const frozen = verifyFrozenApi()
|
||||
const index = process.argv.indexOf('--compare')
|
||||
if (index !== -1) {
|
||||
assert(process.argv[index + 1], '--compare requires a captured report path')
|
||||
const captured = JSON.parse(fs.readFileSync(process.argv[index + 1], 'utf8'))
|
||||
validateApiReport(captured)
|
||||
compareApiSnapshots(frozen.report.snapshot, captured.snapshot)
|
||||
console.log('Published API capture matches the frozen snapshot')
|
||||
}
|
||||
else {
|
||||
assert(process.argv.slice(2).every(arg => arg === '--check'), 'Use --check or --compare <report>')
|
||||
console.log(`Frozen API verified: ${checkIds.length} browser checks; browser is not rerun by this command`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { test } from 'node:test'
|
||||
import { compareApiSnapshots, validateApiReport, verifyFrozenApi } from './api.mjs'
|
||||
|
||||
test('frozen API reports require passing checks and the exact published source', () => {
|
||||
const { report } = verifyFrozenApi()
|
||||
const missing = structuredClone(report)
|
||||
missing.checks.pop()
|
||||
assert.throws(() => validateApiReport(missing), /Missing or failed API checks/)
|
||||
const wrong = structuredClone(report)
|
||||
wrong.capture.releases[0].version = '999.0.0'
|
||||
assert.throws(() => validateApiReport(wrong), /Published sources differ/)
|
||||
const failed = structuredClone(report)
|
||||
failed.errors.push('constructor failed')
|
||||
assert.throws(() => validateApiReport(failed), /Browser capture failed/)
|
||||
})
|
||||
|
||||
test('API comparison detects descriptor, method and default-value regressions', () => {
|
||||
const { snapshot } = verifyFrozenApi().report
|
||||
compareApiSnapshots(snapshot, structuredClone(snapshot))
|
||||
for (const change of [
|
||||
copy => { copy.instance.play.enumerable = !copy.instance.play.enumerable },
|
||||
copy => { copy.static.NOTICE_TIME.writable = !copy.static.NOTICE_TIME.writable },
|
||||
copy => { delete copy.prototype.destroy },
|
||||
copy => { copy.defaults.volume = 0.25 },
|
||||
copy => { copy.constants.SEEK_STEP += 1 },
|
||||
copy => { delete copy.integrations.controls.resolved.show },
|
||||
copy => { copy.integrations.controls.resolved.show.set = 'undefined' },
|
||||
]) {
|
||||
const changed = structuredClone(snapshot)
|
||||
change(changed)
|
||||
assert.throws(() => compareApiSnapshots(snapshot, changed), /Public API snapshot differs/)
|
||||
}
|
||||
})
|
||||
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
|
||||
import fs from 'node:fs'
|
||||
import http from 'node:http'
|
||||
import path from 'node:path'
|
||||
import { ensureArchive, readMember, refactorDir } from './releases.mjs'
|
||||
import { ensureArchive, hash, readMember, refactorDir } from './releases.mjs'
|
||||
|
||||
const root = path.resolve(refactorDir, '..')
|
||||
const baseline = JSON.parse(fs.readFileSync(path.join(refactorDir, 'baselines/releases.json'), 'utf8'))
|
||||
@@ -48,6 +48,16 @@ const server = http.createServer(async (req, res) => {
|
||||
}
|
||||
const report = JSON.parse(Buffer.concat(chunks).toString('utf8'))
|
||||
assert.equal(report.kind, 'public-api')
|
||||
report.capture = {
|
||||
capturedAt: new Date().toISOString(),
|
||||
releases: baseline.releases.map(release => ({
|
||||
name: release.name, version: release.version, integrity: release.integrity,
|
||||
member: `package/${release.manifest.main.replace(/^\.\//, '')}`,
|
||||
sha256: release.files[`package/${release.manifest.main.replace(/^\.\//, '')}`],
|
||||
})),
|
||||
fixtureHashAlgorithm: 'sha256-lf',
|
||||
fixtures: Object.fromEntries(['api.html', 'api.js'].map(name => [name, hash(fs.readFileSync(path.join(refactorDir, 'fixtures', name), 'utf8').replaceAll('\r\n', '\n'))])),
|
||||
}
|
||||
const output = path.join(refactorDir, '.cache/reports')
|
||||
fs.mkdirSync(output, { recursive: true })
|
||||
fs.writeFileSync(path.join(output, 'api.json'), `${JSON.stringify(report, null, 2)}\n`)
|
||||
|
||||
Reference in new issue
Block a user