mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-09 03:46:16 -08:00
95 lines
4.5 KiB
JavaScript
95 lines
4.5 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import fs from 'node:fs'
|
|
import path from 'node:path'
|
|
// eslint-disable-next-line test/no-import-node-test -- Exercise real filesystem and workflow rejection paths.
|
|
import test from 'node:test'
|
|
import YAML from 'yaml'
|
|
import { validateCIWorkflow, validatePagesWorkflow } from '../refactor/scripts/ci-workflow.mjs'
|
|
import { compareCompiled, inside, inventory, validateArtifact } from '../scripts/pages/artifact.ts'
|
|
|
|
function fixture(t) {
|
|
const root = fs.mkdtempSync(path.resolve('refactor/.cache/pages-test-'))
|
|
t.after(() => {
|
|
assert.equal(path.dirname(root), path.resolve('refactor/.cache'))
|
|
assert(path.basename(root).startsWith('pages-test-'))
|
|
fs.rmSync(root, { recursive: true, force: true })
|
|
})
|
|
fs.writeFileSync(path.join(root, 'CNAME'), 'github.artplayer.org\n')
|
|
fs.writeFileSync(path.join(root, '.nojekyll'), '')
|
|
fs.writeFileSync(path.join(root, 'index.html'), '<!doctype html><title>Player</title>')
|
|
return root
|
|
}
|
|
const contract = { domain: 'github.artplayer.org', requiredPaths: ['index.html', '.nojekyll', 'CNAME'] }
|
|
|
|
test('Pages preserves hidden files and rejects missing routes, empty pages and changed domains', (t) => {
|
|
const root = fixture(t)
|
|
const files = validateArtifact(root, contract)
|
|
assert.equal(files['.nojekyll'].bytes, 0)
|
|
assert.match(files['index.html'].sha256, /^[a-f0-9]{64}$/)
|
|
assert.throws(() => validateArtifact(root, { ...contract, requiredPaths: [...contract.requiredPaths, 'mobile.html'] }), /Missing required/)
|
|
fs.writeFileSync(path.join(root, 'CNAME'), 'example.com')
|
|
assert.throws(() => validateArtifact(root, contract), /domain changed/)
|
|
fs.writeFileSync(path.join(root, 'index.html'), '')
|
|
assert.throws(() => validateArtifact(root, contract), /Empty Pages/)
|
|
})
|
|
|
|
test('Pages rejects directory escape, symbolic directories and hard-linked files', (t) => {
|
|
const root = fixture(t)
|
|
for (const file of ['../outside', '/outside', 'nested/../../outside', 'nested\\outside', '.'])
|
|
assert.throws(() => inside(root, file))
|
|
fs.linkSync(path.join(root, 'index.html'), path.join(root, 'hard.html'))
|
|
assert.throws(() => inventory(root), /hard links/)
|
|
fs.unlinkSync(path.join(root, 'hard.html'))
|
|
const directory = path.join(root, 'assets')
|
|
fs.mkdirSync(directory)
|
|
fs.symlinkSync(directory, path.join(root, 'redirect'), 'junction')
|
|
assert.throws(() => inventory(root), /symbolic links/)
|
|
})
|
|
|
|
test('Pages cannot upload stale compiled output or omit one distribution format', (t) => {
|
|
const root = fixture(t)
|
|
fs.mkdirSync(path.join(root, 'packages/artplayer/dist'), { recursive: true })
|
|
fs.mkdirSync(path.join(root, 'compiled'))
|
|
for (const suffix of ['.js', '.legacy.js', '.mjs']) {
|
|
fs.writeFileSync(path.join(root, `packages/artplayer/dist/artplayer${suffix}`), suffix)
|
|
fs.writeFileSync(path.join(root, `compiled/artplayer${suffix}`), suffix)
|
|
}
|
|
assert.equal(compareCompiled(root, root, ['artplayer']).length, 3)
|
|
fs.writeFileSync(path.join(root, 'compiled/artplayer.js'), 'old')
|
|
assert.throws(() => compareCompiled(root, root, ['artplayer']), /Stale compiled/)
|
|
fs.unlinkSync(path.join(root, 'compiled/artplayer.js'))
|
|
assert.throws(() => compareCompiled(root, root, ['artplayer']), /ENOENT/)
|
|
})
|
|
|
|
test('Pages deployment guard rejects untrusted, bypassed and rebuilding deployments', () => {
|
|
const source = fs.readFileSync('.github/workflows/pages.yml', 'utf8')
|
|
validatePagesWorkflow(source)
|
|
for (const mutate of [
|
|
w => w.on.push = {},
|
|
w => w.jobs.validate.if = 'true',
|
|
w => w.jobs.deploy.needs = [],
|
|
w => w.jobs.deploy.permissions.contents = 'write',
|
|
w => w.jobs.deploy.steps[0].with = { 'artifact-name': 'arbitrary' },
|
|
w => w.jobs.deploy.steps.push({ run: 'yarn build' }),
|
|
w => w.concurrency['cancel-in-progress'] = true,
|
|
]) {
|
|
const workflow = YAML.parse(source)
|
|
mutate(workflow)
|
|
assert.throws(() => validatePagesWorkflow(YAML.stringify(workflow)))
|
|
}
|
|
})
|
|
|
|
test('Pages upload cannot bypass preflight or upload the working directory', () => {
|
|
const source = fs.readFileSync('.github/workflows/nodejs.yml', 'utf8')
|
|
for (const mutate of [
|
|
w => w.jobs.checks.steps.find(s => s.id === 'pages').if = 'false',
|
|
w => w.jobs.checks.steps.find(s => s.uses?.startsWith('actions/upload-pages-artifact@')).with.path = 'docs',
|
|
w => w.jobs.checks.steps.find(s => s.id === 'pages').run = 'echo passed',
|
|
w => w.jobs.checks.steps.find(s => s.run?.startsWith('yarn test:pages:browser')).if = 'false',
|
|
]) {
|
|
const workflow = YAML.parse(source)
|
|
mutate(workflow)
|
|
assert.throws(() => validateCIWorkflow(YAML.stringify(workflow)))
|
|
}
|
|
})
|