Spaces:
Running
Running
| import { | |
| DimensionCheckError, | |
| assertImageDimensions, | |
| buildDimensionCheckFailureBody, | |
| isDimensionCheckError, | |
| parseExpectedDimensions, | |
| sanitizeImageResponse | |
| } from '../skills/gpt-image-playground-agent/scripts/lib/dimension-check.mjs'; | |
| import assert from 'node:assert/strict'; | |
| import { createServer } from 'node:http'; | |
| import { test } from 'node:test'; | |
| test('assertImageDimensions records dimensions for inline base64 images', async () => { | |
| const response = { | |
| images: [{ id: 'ok', b64_json: pngBase64(320, 240) }] | |
| }; | |
| const checked = await assertImageDimensions({ | |
| response, | |
| expected: { width: 320, height: 240 }, | |
| baseUrl: 'http://127.0.0.1:1', | |
| authHeaders: {} | |
| }); | |
| assert.deepEqual(checked.images[0].dimensions, { width: 320, height: 240 }); | |
| assert.equal(checked.images[0].b64_json, response.images[0].b64_json); | |
| }); | |
| test('assertImageDimensions rejects mismatched dimensions with sanitized response', async () => { | |
| await assert.rejects( | |
| () => | |
| assertImageDimensions({ | |
| response: { images: [{ id: 'bad', b64_json: pngBase64(512, 512) }] }, | |
| expected: { width: 1024, height: 1024 }, | |
| baseUrl: 'http://127.0.0.1:1', | |
| authHeaders: {} | |
| }), | |
| (error) => { | |
| assert.equal(error instanceof DimensionCheckError, true); | |
| assert.equal(error.code, 'dimension_check_failed'); | |
| assert.deepEqual(error.expectedDimensions, { width: 1024, height: 1024 }); | |
| assert.deepEqual(error.actualDimensions, { width: 512, height: 512 }); | |
| assert.equal(error.response.images[0].b64_json, undefined); | |
| assert.equal(Object.prototype.hasOwnProperty.call(error.response.images[0], 'b64_json'), false); | |
| assert.equal(error.response.images[0].b64_json_length, pngBase64(512, 512).length); | |
| assert.deepEqual(error.response.images[0].dimensions, { width: 512, height: 512 }); | |
| return true; | |
| } | |
| ); | |
| }); | |
| test('assertImageDimensions rejects missing and empty image lists', async () => { | |
| for (const response of [{}, { images: [] }]) { | |
| await assert.rejects( | |
| () => | |
| assertImageDimensions({ | |
| response, | |
| expected: { width: 1024, height: 1024 }, | |
| baseUrl: 'http://127.0.0.1:1', | |
| authHeaders: {} | |
| }), | |
| /响应中没有可验收的图片/ | |
| ); | |
| } | |
| }); | |
| test('assertImageDimensions reads same-origin content URLs with auth headers', async () => { | |
| const requests = []; | |
| await withServer( | |
| (request, response) => { | |
| requests.push({ url: request.url, authorization: request.headers.authorization }); | |
| response.writeHead(200, { 'content-type': 'image/png' }); | |
| response.end(pngBuffer(640, 360)); | |
| }, | |
| async (baseUrl) => { | |
| const checked = await assertImageDimensions({ | |
| response: { images: [{ id: 'url-image', content_url: '/artifact/url-image.png' }] }, | |
| expected: { width: 640, height: 360 }, | |
| baseUrl, | |
| authHeaders: () => ({ Authorization: 'Bearer test-token' }) | |
| }); | |
| assert.deepEqual(checked.images[0].dimensions, { width: 640, height: 360 }); | |
| assert.equal(requests[0].url, '/artifact/url-image.png'); | |
| assert.equal(requests[0].authorization, 'Bearer test-token'); | |
| } | |
| ); | |
| }); | |
| test('assertImageDimensions rejects path fields unless callers opt in explicitly', async () => { | |
| await assert.rejects( | |
| () => | |
| assertImageDimensions({ | |
| response: { images: [{ path: '/artifact/from-path.png' }] }, | |
| expected: { width: 1, height: 1 }, | |
| baseUrl: 'http://127.0.0.1:1', | |
| authHeaders: {} | |
| }), | |
| /dimension-check 需要 b64_json 或 absolute_content_url 或 content_url/ | |
| ); | |
| await assert.rejects( | |
| () => | |
| assertImageDimensions({ | |
| response: { images: [{}] }, | |
| expected: { width: 1, height: 1 }, | |
| baseUrl: 'http://127.0.0.1:1', | |
| authHeaders: {}, | |
| readUrlFields: ['absolute_path', 'path'] | |
| }), | |
| /dimension-check 需要 b64_json 或 absolute_path 或 path/ | |
| ); | |
| }); | |
| test('assertImageDimensions converts download failures and cross-origin URLs to dimension failures', async () => { | |
| await withServer( | |
| (_request, response) => { | |
| response.writeHead(404, { 'content-type': 'application/json' }); | |
| response.end(JSON.stringify({ error: 'missing' })); | |
| }, | |
| async (baseUrl) => { | |
| await assert.rejects( | |
| () => | |
| assertImageDimensions({ | |
| response: { images: [{ content_url: '/missing.png' }] }, | |
| expected: { width: 1, height: 1 }, | |
| baseUrl, | |
| authHeaders: {} | |
| }), | |
| /下载产物失败,状态码 404/ | |
| ); | |
| } | |
| ); | |
| await assert.rejects( | |
| () => | |
| assertImageDimensions({ | |
| response: { images: [{ content_url: 'https://evil.example.test/image.png' }] }, | |
| expected: { width: 1, height: 1 }, | |
| baseUrl: 'https://space.example.test', | |
| authHeaders: {} | |
| }), | |
| /不同 origin/ | |
| ); | |
| await assert.rejects( | |
| () => | |
| assertImageDimensions({ | |
| response: { images: [{ absolute_content_url: 'https://evil.example.test/image.png' }] }, | |
| expected: { width: 1, height: 1 }, | |
| baseUrl: 'https://space.example.test', | |
| authHeaders: {} | |
| }), | |
| /absolute_content_url 指向不同 origin/ | |
| ); | |
| }); | |
| test('assertImageDimensions times out stalled streamed downloads', async () => { | |
| await withServer( | |
| (_request, response) => { | |
| response.writeHead(200, { 'content-type': 'image/png' }); | |
| response.write(pngBuffer(1, 1).subarray(0, 8)); | |
| }, | |
| async (baseUrl) => { | |
| await assert.rejects( | |
| () => | |
| assertImageDimensions({ | |
| response: { images: [{ content_url: '/stalled.png' }] }, | |
| expected: { width: 1, height: 1 }, | |
| baseUrl, | |
| authHeaders: {}, | |
| timeoutMs: 25 | |
| }), | |
| /下载产物超时,已等待 25ms/ | |
| ); | |
| } | |
| ); | |
| }); | |
| test('assertImageDimensions falls back to default URL fields for invalid readUrlFields', async () => { | |
| await withServer( | |
| (_request, response) => { | |
| response.writeHead(200, { 'content-type': 'image/png' }); | |
| response.end(pngBuffer(16, 9)); | |
| }, | |
| async (baseUrl) => { | |
| const checked = await assertImageDimensions({ | |
| response: { images: [{ content_url: '/default-field.png' }] }, | |
| expected: { width: 16, height: 9 }, | |
| baseUrl, | |
| authHeaders: {}, | |
| readUrlFields: null | |
| }); | |
| assert.deepEqual(checked.images[0].dimensions, { width: 16, height: 9 }); | |
| } | |
| ); | |
| }); | |
| test('assertImageDimensions enforces the max image byte limit', async () => { | |
| await assert.rejects( | |
| () => | |
| assertImageDimensions({ | |
| response: { images: [{ b64_json: pngBase64(1, 1) }] }, | |
| expected: { width: 1, height: 1 }, | |
| baseUrl: 'http://127.0.0.1:1', | |
| authHeaders: {}, | |
| maxImageBytes: 8 | |
| }), | |
| /图片数据超过 8 字节限制/ | |
| ); | |
| await withServer( | |
| (_request, response) => { | |
| response.writeHead(200, { | |
| 'content-type': 'image/png', | |
| 'content-length': String(pngBuffer(1, 1).length) | |
| }); | |
| response.end(pngBuffer(1, 1)); | |
| }, | |
| async (baseUrl) => { | |
| await assert.rejects( | |
| () => | |
| assertImageDimensions({ | |
| response: { images: [{ content_url: '/large.png' }] }, | |
| expected: { width: 1, height: 1 }, | |
| baseUrl, | |
| authHeaders: {}, | |
| maxImageBytes: 8 | |
| }), | |
| /图片数据超过 8 字节限制/ | |
| ); | |
| } | |
| ); | |
| }); | |
| test('dimension-check helpers expose stable structured failure bodies', () => { | |
| const sanitized = sanitizeImageResponse({ images: [{ id: 'secret', b64_json: pngBase64(1, 1) }] }); | |
| assert.equal(sanitized.images[0].b64_json, undefined); | |
| assert.equal(Object.prototype.hasOwnProperty.call(sanitized.images[0], 'b64_json'), false); | |
| assert.equal(sanitized.images[0].b64_json_length, pngBase64(1, 1).length); | |
| assert.deepEqual(parseExpectedDimensions('1024x768'), { width: 1024, height: 768 }); | |
| assert.equal(parseExpectedDimensions('auto'), undefined); | |
| assert.equal(isDimensionCheckError(new Error('plain')), false); | |
| const error = new DimensionCheckError('bad size', { | |
| expected: { width: 10, height: 10 }, | |
| actual: { width: 8, height: 8 }, | |
| response: { images: [{ id: 'bad', b64_json: pngBase64(8, 8) }] }, | |
| nextStep: 'inspect' | |
| }); | |
| const body = buildDimensionCheckFailureBody(error, { transport: 'agent_json' }); | |
| assert.equal(body.billable, true); | |
| assert.equal(body.error.code, 'dimension_check_failed'); | |
| assert.deepEqual(body.error.expected_dimensions, { width: 10, height: 10 }); | |
| assert.deepEqual(body.error.actual_dimensions, { width: 8, height: 8 }); | |
| assert.equal(body.validation_failure_kind, 'generated_artifact_failed_dimension_check'); | |
| assert.deepEqual(body.routing, { transport: 'agent_json' }); | |
| assert.equal(body.response.images[0].b64_json, undefined); | |
| assert.equal(Object.prototype.hasOwnProperty.call(body.response.images[0], 'b64_json'), false); | |
| assert.equal(body.next_step, 'inspect'); | |
| const malformed = buildDimensionCheckFailureBody( | |
| new DimensionCheckError('bad metadata', { | |
| expected: { width: 'wide', height: 10 }, | |
| actual: { width: 8, height: 0 } | |
| }), | |
| undefined | |
| ); | |
| assert.equal(malformed.error.expected_dimensions, null); | |
| assert.equal(malformed.error.actual_dimensions, null); | |
| }); | |
| function pngBase64(width, height) { | |
| return pngBuffer(width, height).toString('base64'); | |
| } | |
| function pngBuffer(width, height) { | |
| const buffer = Buffer.alloc(24); | |
| buffer[0] = 0x89; | |
| buffer.write('PNG', 1, 'ascii'); | |
| buffer[4] = 0x0d; | |
| buffer[5] = 0x0a; | |
| buffer[6] = 0x1a; | |
| buffer[7] = 0x0a; | |
| buffer.writeUInt32BE(13, 8); | |
| buffer.write('IHDR', 12, 'ascii'); | |
| buffer.writeUInt32BE(width, 16); | |
| buffer.writeUInt32BE(height, 20); | |
| return buffer; | |
| } | |
| async function withServer(handler, run) { | |
| const server = createServer(handler); | |
| await new Promise((resolve, reject) => { | |
| server.once('error', reject); | |
| server.listen(0, '127.0.0.1', resolve); | |
| }); | |
| const address = server.address(); | |
| try { | |
| await run(`http://127.0.0.1:${address.port}`); | |
| } finally { | |
| await new Promise((resolve, reject) => { | |
| server.close((error) => (error ? reject(error) : resolve())); | |
| }); | |
| } | |
| } | |