import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import { dirname, join } from 'node:path' import { describe, it } from 'node:test' import { fileURLToPath } from 'node:url' import { assembleSite, buildManifest, contentTypeFor, hashAsset, hashAssets, isIgnoredSitePath, normalizeSitePath, planUploadBatches, renderHeaders, mergeHeadersText, renderRedirects, SPA_REDIRECT_RULE, } from '../src/static-site.ts' import { entriesToSiteFiles, readArchive, selectSiteEntries, } from '../src/archive.ts' import { buildFormData, buildRequestBody } from '../src/request-body.ts' import { nextPageQuery, extractListItems } from '../src/api-v4-list.ts' import { scanSiteFiles, DEFAULT_FORBID } from '../src/scan.ts' import { assertPagesProjectName, buildDeploymentForm, deploymentState, } from '../src/pages-direct-upload.ts' import { accounts, resolveKnownAccountId, KCD_CLOUDFLARE_ACCOUNT_ID } from '../src/auth.ts' const fixtures = join(dirname(fileURLToPath(import.meta.url)), 'fixtures') describe('normalizeSitePath', () => { it('strips leading slash and dots', () => { assert.equal(normalizeSitePath('/assets/app.js'), 'assets/app.js') assert.equal(normalizeSitePath('./index.html'), 'index.html') }) it('rejects .. and empty', () => { assert.throws(() => normalizeSitePath('../x'), /Invalid/) assert.throws(() => normalizeSitePath(''), /Invalid/) assert.throws(() => normalizeSitePath('a/'), /directory/) }) }) describe('content types and ignore', () => { it('maps common extensions', () => { assert.equal(contentTypeFor('a.html'), 'text/html') assert.equal(contentTypeFor('a.js'), 'application/javascript') assert.equal(contentTypeFor('a.weird'), 'application/octet-stream') }) it('ignores junk segments', () => { assert.equal(isIgnoredSitePath('.DS_Store'), true) assert.equal(isIgnoredSitePath('node_modules/x.js'), true) assert.equal(isIgnoredSitePath('assets/app.js'), false) }) }) describe('hash + manifest', () => { it('hashes base64+ext to 32 hex chars and is stable', async () => { const a = await hashAsset(btoa('hello'), 'index.html') const b = await hashAsset(btoa('hello'), 'index.html') assert.equal(a, b) assert.match(a, /^[0-9a-f]{32}$/) const c = await hashAsset(btoa('hello'), 'index.txt') assert.notEqual(a, c) }) it('builds a sorted path→hash manifest', async () => { const assets = await hashAssets([ { path: 'b.txt', bytes: new TextEncoder().encode('b'), contentType: 'text/plain' }, { path: 'a.txt', bytes: new TextEncoder().encode('a'), contentType: 'text/plain' }, ]) assert.deepEqual(Object.keys(buildManifest(assets)), ['/a.txt', '/b.txt']) }) it('batches uploads by size with unique hashes', () => { const files = [ { hash: 'a', size: 6 }, { hash: 'b', size: 5 }, { hash: 'a', size: 6 }, { hash: 'c', size: 4 }, ] const batches = planUploadBatches(files, { maxBatchBytes: 10, maxBatchFiles: 10 }) assert.equal(batches.flat().length, 3) assert.ok(batches.every((batch) => batch.reduce((s, f) => s + f.size, 0) <= 10)) }) }) describe('headers / redirects', () => { it('renders object headers and rule redirects', () => { assert.equal( renderHeaders({ '/*': { 'X-Frame-Options': 'DENY' } }), '/*\n X-Frame-Options: DENY', ) assert.equal(renderRedirects([{ from: '/a', to: '/b', status: 301 }]), '/a /b 301') }) it('merges repeated _headers patterns into one block', () => { assert.equal( mergeHeadersText('/*\n X-A: 1\n# note\n/assets/*\n Cache-Control: max-age=60\n/*\n X-B: 2'), '/*\n X-A: 1\n X-B: 2\n/assets/*\n Cache-Control: max-age=60', ) }) it('rejects bad redirects', () => { assert.throws(() => renderRedirects([{ from: '/a space', to: '/b' } as never]), /whitespace/) }) }) describe('assembleSite', () => { it('merges spa + noindex and removes config from assets', () => { const site = assembleSite({ files: { 'index.html': '

hi

', '_headers': '/*\n X-Test: 1', }, spa: true, noindex: true, }) assert.equal(site.assets.some((f) => f.path === '_headers'), false) assert.match(site.config._headers ?? '', /X-Test/) assert.match(site.config._headers ?? '', /noindex/) assert.equal((site.config._headers ?? '').match(/^\/\*$/gm)?.length, 1) assert.equal((site.config._redirects ?? '').trim(), SPA_REDIRECT_RULE) }) it('spa:false adds a 404.html', () => { const site = assembleSite({ files: { 'index.html': 'x' }, spa: false }) assert.ok(site.assets.some((f) => f.path === '404.html')) }) it('rejects _worker.js', () => { assert.throws( () => assembleSite({ files: { 'index.html': 'x', '_worker.js': 'export default {}' } }), /_worker\.js/, ) }) it('files override archive entries', () => { const site = assembleSite({ archiveFiles: [{ path: 'index.html', bytes: new TextEncoder().encode('old'), contentType: 'text/html' }], files: { 'index.html': 'new' }, }) assert.equal(new TextDecoder().decode(site.assets[0].bytes), 'new') assert.ok(site.warnings.some((w) => /overrode/.test(w))) }) }) describe('archives', () => { it('reads zip and strips a shared top folder', async () => { const { entries } = await readArchive(readFileSync(join(fixtures, 'site.zip'))) const selected = selectSiteEntries(entries) assert.equal(selected.root, 'site') const files = entriesToSiteFiles(selected.entries) assert.ok(files.some((f) => f.path === 'index.html')) assert.ok(files.some((f) => f.path === 'assets/app.js')) assert.equal(files.some((f) => f.path === '.DS_Store'), false) assert.equal(files.some((f) => f.path === '_headers'), true) }) it('reads stored (uncompressed) zip entries', async () => { const { entries } = await readArchive(readFileSync(join(fixtures, 'site-stored.zip'))) const files = entriesToSiteFiles(selectSiteEntries(entries).entries) assert.deepEqual(files.map((f) => f.path).sort(), ['a/b.txt', 'index.html']) assert.equal(new TextDecoder().decode(files.find((f) => f.path === 'a/b.txt')!.bytes), 'bee') }) it('reads deflated zip bytes correctly', async () => { const { entries } = await readArchive(readFileSync(join(fixtures, 'site.zip'))) const index = entries.find((e) => e.path === 'site/index.html')! assert.match(new TextDecoder().decode(index.data), /

Hello<\/h1>/) }) it('reads tar.gz and tar', async () => { const tgz = await readArchive(readFileSync(join(fixtures, 'site.tgz'))) assert.equal(tgz.format, 'tar.gz') assert.ok(tgz.entries.some((e) => e.path === 'index.html' || e.path.endsWith('/index.html'))) const tar = await readArchive(readFileSync(join(fixtures, 'site.tar'))) assert.equal(tar.format, 'tar') }) it('sourceRoot with * strips a GitHub-style top folder', async () => { const { entries } = await readArchive(readFileSync(join(fixtures, 'gh.tgz'))) const selected = selectSiteEntries(entries, '*/dist') assert.match(selected.root, /repo-/) const files = entriesToSiteFiles(selected.entries) assert.deepEqual(files.map((f) => f.path), ['index.html']) }) }) describe('scan', () => { it('includes built-in credential patterns by default', () => { assert.ok(DEFAULT_FORBID.length >= 5) const result = scanSiteFiles([ { path: 'a.txt', bytes: new TextEncoder().encode('token sk_live_abcdefghijklmnopqrstuvwxyz end'), contentType: 'text/plain' }, ]) assert.equal(result.ok, false) assert.equal(result.findings[0].pattern, 'stripe-secret-key') assert.match(result.findings[0].preview, /\*/) }) it('accepts /regex/ strings and literals', () => { const files = [{ path: 'a.html', bytes: new TextEncoder().encode('INTERNAL_ONLY_MARKER'), contentType: 'text/html' }] assert.equal(scanSiteFiles(files, { forbid: ['INTERNAL_ONLY_MARKER'], defaults: false }).ok, false) assert.equal(scanSiteFiles(files, { forbid: ['/internal_only_marker/i'], defaults: false }).ok, false) assert.equal(scanSiteFiles(files, { forbid: ['nope'], defaults: false }).ok, true) }) it('skips binary files', () => { const result = scanSiteFiles( [{ path: 'a.png', bytes: new Uint8Array([1, 2, 3]), contentType: 'image/png' }], { forbid: ['x'], defaults: false }, ) assert.equal(result.scannedFiles, 0) assert.equal(result.skippedBinaryFiles, 1) }) }) describe('form / request body', () => { it('builds multipart form fields', () => { const form = buildFormData({ manifest: '{}', _headers: { filename: '_headers', content: '/*\n X: 1' }, }) assert.ok(form.get('manifest') === '{}') assert.ok(form.get('_headers') instanceof File) }) it('rejects body+form together and ignores JSON body on GET', () => { assert.throws(() => buildRequestBody({ body: {}, form: { a: '1' } }, 'POST'), /only one/) assert.equal(buildRequestBody({ body: { a: 1 } }, 'GET').kind, 'none') assert.equal(buildRequestBody({ body: { a: 1 } }, 'POST').kind, 'json') assert.equal(buildRequestBody({ form: { a: '1' } }, 'POST').kind, 'form') }) }) describe('api-v4-list helpers (fixtures)', () => { it('walks page-number result_info from a real Pages list shape', () => { const info = JSON.parse(readFileSync(join(fixtures, 'result-info.json'), 'utf8')) assert.deepEqual( nextPageQuery(info.pagesProjectsPage1, { page: 1, received: 10, perPage: 10 }), { page: 2 }, ) assert.equal(nextPageQuery(info.pagesProjectsPage5, { page: 5, received: 7, perPage: 10 }), null) }) it('walks cursor result_info from a real R2 list shape', () => { const info = JSON.parse(readFileSync(join(fixtures, 'result-info.json'), 'utf8')) assert.deepEqual( nextPageQuery(info.r2BucketsCursor, { page: 1, received: 20 }), { cursor: info.r2BucketsCursor.cursor }, ) assert.equal(nextPageQuery(info.r2BucketsCursor, { page: 1, received: 0 }), null) }) it('extracts items from array or single-array-property results', () => { assert.deepEqual(extractListItems([{ id: 1 }]), [{ id: 1 }]) assert.deepEqual(extractListItems({ buckets: [{ name: 'a' }] }), [{ name: 'a' }]) assert.deepEqual(extractListItems({ buckets: [{ name: 'a' }] }, 'buckets'), [{ name: 'a' }]) }) }) describe('pages helpers', () => { it('validates project names and reads deployment fixtures', () => { assert.equal(assertPagesProjectName('mega-week3-plan'), 'mega-week3-plan') assert.throws(() => assertPagesProjectName('Bad Name'), /Invalid/) const live = JSON.parse(readFileSync(join(fixtures, 'pages-deployment-live.json'), 'utf8')) assert.deepEqual(deploymentState(live), { stage: 'deploy:success', live: true, failed: false }) assert.equal(deploymentState({ id: 'x', latest_stage: { name: 'queued', status: 'idle' } }).live, false) }) it('builds deployment form fields', () => { const form = buildDeploymentForm({ manifest: { '/index.html': 'abc' }, branch: 'main', message: 'hi', config: { _headers: '/*\n X: 1\n', _redirects: '/* /index.html 200\n' }, }) assert.equal(form.manifest, '{"/index.html":"abc"}') assert.equal(form.branch, 'main') assert.deepEqual(form._headers, { filename: '_headers', content: '/*\n X: 1\n' }) }) }) describe('accounts metadata', () => { it('documents every alias with can/cannot and known account ids', () => { const list = accounts() assert.deepEqual(list.map((a) => a.account), ['default', 'kody', 'pages']) const pages = list.find((a) => a.account === 'pages')! assert.equal(pages.accountId, KCD_CLOUDFLARE_ACCOUNT_ID) assert.equal(pages.accountName, 'KCD') assert.ok(pages.can.some((c) => /Pages/.test(c))) assert.ok(pages.cannot.some((c) => /GET \/accounts/.test(c))) assert.equal(resolveKnownAccountId({ account: 'pages' }), KCD_CLOUDFLARE_ACCOUNT_ID) assert.equal(resolveKnownAccountId({ account: 'default' }), null) }) })