'use strict'; const { test } = require('node:test'); const assert = require('node:assert/strict'); const fs = require('node:fs'), path = require('node:path'), vm = require('node:vm'); // Synthetic JPEG marker bytes only; no real photograph or service is involved. const jpeg = '/9j/2Q==', dataURL = 'data:image/jpeg;base64,' + jpeg; function setup(result, options = {}) { const writes = [], draws = [], calls = []; const platform = { getImageInfo(options) { options.success({ width: 2, height: 1 }); }, createCanvasContext(id) { assert.equal(id, 'compress-canvas'); const context = { clearRect() {}, drawImage(src) { draws.push(src); }, draw(reserve, complete) { complete(); }, toDataURL(value) { calls.push(value); if (options.nativePromiseFailure) { value.fail({ errorMessage: '原生 JPEG 导出失败' }); return Promise.reject(Error('原生 JPEG 导出失败')); } if (options.promiseOnly) return Promise.resolve(result); value.success(result); if (options.dualPromise) return Promise.resolve(result); } }; if (options.fileExport) context.toTempFilePath = value => { calls.push(value); value.success({ apFilePath: 'PRIVATE_TEMP_JPEG_PATH' }); }; return context; }, getFileSystemManager: () => ({ readFile(value) { if (options.readFailure) value.fail({ errorMessage: '读取失败' }); else value.success({ data: result }); } }), canvasPutImageData(options) { writes.push(options); options.success(); } }; const module = { exports: {} }; vm.runInNewContext(fs.readFileSync(path.resolve(__dirname, '../utils/image.js'), 'utf8'), { module, my: platform, Promise, setTimeout, clearTimeout, ArrayBuffer, Uint8Array, Uint8ClampedArray }); const page = { alive: true, setData(value, complete) { complete(); } }; return { image: module.exports, page, writes, draws, calls }; } test('legacy/native Canvas file export validates actual JPEG bytes rather than trusting a requested file type', async () => { const fixture = setup(jpeg, { fileExport: true }); assert.equal(await fixture.image.compressToBase64('native-takePhoto-path', fixture.page), dataURL); assert.deepEqual(fixture.draws, ['native-takePhoto-path']); assert.equal(fixture.calls[0].fileType, 'jpg'); for (const value of ['iVBORw0KGgo=', '/9j/invalid', 'data:image/png;base64,' + jpeg]) { const bad = setup(value, { fileExport: true }); await assert.rejects(bad.image.compressToBase64('photo', bad.page), /JPEG 转换失败/); } const empty = setup('', { fileExport: true }); await assert.rejects(empty.image.compressToBase64('photo', empty.page), /内容为空/); const readFailure = setup(jpeg, { fileExport: true, readFailure: true }); await assert.rejects(readFailure.image.compressToBase64('photo', readFailure.page), /读取失败/); }); function exportImage(fixture, kind) { if (kind === 'compression') return fixture.image.compressToBase64('synthetic-photo', fixture.page); return fixture.image.captureFrameToBase64({ width: 2, height: 1, pixelFormat: 'RGBA', capturedImage: new Uint8Array([10, 20, 30, 255, 40, 50, 60, 255]).buffer }, fixture.page); } for (const kind of ['compression', 'raw AR frame']) { test(kind + ' handles native legacy Canvas Promise and callback exports without duplicate completion or unhandled rejection', async () => { for (const options of [{ promiseOnly: true }, { dualPromise: true }]) { const fixture = setup(dataURL, options); assert.equal(await exportImage(fixture, kind), dataURL); assert.equal(fixture.calls.length, 1); } const failure = setup(null, { nativePromiseFailure: true }); await assert.rejects(exportImage(failure, kind), /原生 JPEG 导出失败/); await new Promise(resolve => setImmediate(resolve)); }); test(kind + ' accepts Taobao string and documented native wrapper shapes', async () => { const results = [dataURL, jpeg, { dataURL }, { dataUrl: dataURL }, { base64: dataURL }, { base64: jpeg }]; for (const result of results) { const fixture = setup(result); assert.equal(await exportImage(fixture, kind), dataURL); assert.equal(fixture.calls.length, 1); assert.equal(fixture.calls[0].fileType, 'jpg'); assert.equal(fixture.calls[0].quality, .88); if (kind === 'compression') assert.deepEqual(fixture.draws, ['synthetic-photo']); else { assert.equal(fixture.writes.length, 1); assert.deepEqual(Array.from(fixture.writes[0].data), [10, 20, 30, 255, 40, 50, 60, 255]); } } }); test(kind + ' rejects empty, malformed, unknown and non-JPEG export responses', async () => { const invalid = [null, undefined, '', {}, { data: dataURL }, { dataURL: jpeg }, { dataUrl: jpeg }, { base64: '' }, 'data:image/jpeg;base64,', { dataURL: 'data:image/jpeg;base64,' }, 'data:image/png;base64,/9j/2Q==', { base64: 'data:image/png;base64,/9j/2Q==' }, 'iVBORw0KGgo=', { base64: 'iVBORw0KGgo=' }, 'data:image/jpeg;base64,iVBORw0KGgo=', '/9j/frame', '/9j/2Q=', '/9j/2Q== ', '/9j/2_==', { dataURL: 'https://example.invalid/photo.jpg' }]; for (const result of invalid) { const fixture = setup(result); await assert.rejects(exportImage(fixture, kind), /JPEG 转换失败/, JSON.stringify(result)); assert.equal(fixture.calls.length, 1); } }); }