Preserve known legacy failures by reviewed identity, probe material delivery before and after releases, and retain originals and downloads.
92 lines
5.5 KiB
JavaScript
92 lines
5.5 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { createHash } from 'node:crypto'
|
|
import test from 'node:test'
|
|
import { AUDIT_LIMITS, auditPreviewBatch, auditUniquePreviews, main, metadataCandidates, validateAuditSnapshot } from './audit-legacy-material-previews.mjs'
|
|
import { LEGACY_DOMAINS } from './check-legacy-storage-boundaries.mjs'
|
|
|
|
const host = LEGACY_DOMAINS[0]
|
|
const source = n => `https://${host}/jsxiangche/preview-${n}.webp`
|
|
const row = (id, index = id) => ({ id, materialType: '详情模板', previewUrl: source(index) })
|
|
const html = '<!doctype html><html><body>Not Found. The server is powered by frp. Faithfully yours, frp.</body></html>'
|
|
const response = () => new Response(html, { status: 404, headers: { 'content-type': 'text/html' } })
|
|
|
|
test('snapshot must match exact count, identity, and registered legacy host before any GET', () => {
|
|
const value = { readOnly: true, count: 2, materials: [row('a'), row('b')] }
|
|
assert.equal(validateAuditSnapshot(value, { host, expectedCount: 2 }).length, 2)
|
|
assert.throws(() => validateAuditSnapshot(value, { host }), /exact_inventory_count_required/)
|
|
assert.throws(() => validateAuditSnapshot({ ...value, materials: [row('a'), row('a')] }, { host, expectedCount: 2 }), /unique_material_identity_required/)
|
|
assert.throws(() => validateAuditSnapshot({ ...value, materials: [row('a'), { ...row('b'), previewUrl: 'https://evil.example/a.webp' }] }, { host, expectedCount: 2 }), /inventory_host_changed/)
|
|
})
|
|
|
|
test('candidate mapping keeps same-material display/preview evidence and excludes ZIP-like main role', () => {
|
|
const material = { ...row('a'), displayImages: [source('display'), 'https://s3.jiansun.vip/gongxiangpan/p.png'],
|
|
assets: [{ id: 'main', assetType: 'main', fileUrl: source('looks-like-image'), fileSize: '10' },
|
|
{ id: 'good', assetType: 'main-preview', fileUrl: source('real-preview'), fileSize: '0' },
|
|
{ id: 'bad', assetType: 'preview', fileUrl: 'https://s3.jiansun.vip/gongxiangpan/download.zip', fileSize: '1000000000' }],
|
|
skus: [{ id: 'sku', previewUrl: source('sku'), filePath: 'https://s3.jiansun.vip/gongxiangpan/big.zip' }] }
|
|
const result = metadataCandidates(material)
|
|
assert.equal(result.materialId, 'a')
|
|
assert.equal(result.candidates.length, 4)
|
|
assert.equal(result.candidates.find(item => item.identity === 'good').declaredBytes, '0')
|
|
assert.ok(result.candidates.every(item => item.metadataOnly && item.bytesRead === 0 && item.originalIdentityVerified === false))
|
|
assert.deepEqual(result.excludedAssets.map(item => item.assetId), ['main', 'bad'])
|
|
assert.ok(!JSON.stringify(result.candidates).includes('big.zip'))
|
|
})
|
|
|
|
test('FRP failure still retains hash of the complete actual response body', async () => {
|
|
let calls = 0
|
|
const report = await auditPreviewBatch({ version: 1, reviewed: true, materials: [{ materialId: 'a', registeredSource: source('a') }] },
|
|
{ fetchImpl: async () => { calls++; return response() } })
|
|
assert.equal(calls, 1)
|
|
assert.equal(report.results[0].code, 'frp_default_404')
|
|
assert.equal(report.results[0].responseBodyComplete, true)
|
|
assert.equal(report.results[0].responseBodySha256, createHash('sha256').update(html).digest('hex'))
|
|
assert.equal(report.results[0].responseBytes, Buffer.byteLength(html))
|
|
})
|
|
|
|
test('batches stay at fifty, duplicate URLs map every ID, and at most two GETs run concurrently', async () => {
|
|
const materials = Array.from({ length: 103 }, (_, n) => row(`m-${n}`, n % 53))
|
|
let active = 0, maxActive = 0
|
|
const gets = new Map(), events = []
|
|
const result = await auditUniquePreviews(materials, { fetchImpl: async url => {
|
|
gets.set(url, (gets.get(url) || 0) + 1)
|
|
active++; maxActive = Math.max(maxActive, active)
|
|
await new Promise(resolve => setTimeout(resolve, 1))
|
|
active--
|
|
return response()
|
|
}, onBatch: async event => events.push(event) })
|
|
assert.equal(maxActive, 2)
|
|
assert.equal(result.actualGetAttempts, 53)
|
|
assert.equal(gets.size, 53)
|
|
assert.ok([...gets.values()].every(value => value === 1))
|
|
assert.deepEqual(result.batches.map(item => item.materials.length), [50, 3])
|
|
assert.equal(events.length, 2)
|
|
assert.equal(result.results.length, 103)
|
|
assert.equal(new Set(result.results.map(item => item.materialId)).size, 103)
|
|
assert.equal(result.allRowsAccounted, true)
|
|
assert.ok(result.results.every(item => item.code === 'frp_default_404' && item.responseBodyComplete))
|
|
assert.equal(result.results.find(item => item.materialId === 'm-53').checkedRepresentativeId, 'm-0')
|
|
assert.equal(AUDIT_LIMITS.concurrency, 2)
|
|
})
|
|
|
|
test('a non-preview source is recorded without GET and a timeout is never automatically retried', async () => {
|
|
const materials = [row('a'), { ...row('b'), previewUrl: 'http://127.0.0.1/private' }]
|
|
let calls = 0
|
|
const result = await auditUniquePreviews(materials, { timeoutMs: 5, fetchImpl: () => { calls++; return new Promise(() => {}) } })
|
|
assert.equal(calls, 1)
|
|
assert.equal(result.results[0].classification, 'timeout')
|
|
assert.equal(result.results[0].responseBodySha256, null)
|
|
assert.equal(result.results[1].attempts, 0)
|
|
assert.equal(result.results[1].classification, 'source_rejected')
|
|
assert.equal(result.allRowsAccounted, true)
|
|
assert.equal(result.retries, 0)
|
|
})
|
|
|
|
test('default invocation is plan-only without SSH, database, images or file writes', async () => {
|
|
const result = await main([])
|
|
assert.equal(result.planOnly, true)
|
|
assert.equal(result.expectedCount, 472)
|
|
assert.equal(result.productionWrites, 0)
|
|
await assert.rejects(main(['--run']), /run_requires_new_absolute_output_directory/)
|
|
})
|