Spaces:
Running
Running
| import { | |
| buildEditRequestHash, | |
| completeAgentExecutionState, | |
| hydrateAgentReplayResponse, | |
| readIdempotencyKey | |
| } from './agent-image-service'; | |
| import type { AgentArtifactRecord, AgentStateStore } from './agent-state-store'; | |
| import assert from 'node:assert/strict'; | |
| import { describe, it } from 'node:test'; | |
| describe('buildEditRequestHash', () => { | |
| it('includes uploaded file bytes so same metadata with different content conflicts', async () => { | |
| const first = await buildEditRequestHash(makeEditForm([1, 2, 3, 4])); | |
| const second = await buildEditRequestHash(makeEditForm([9, 8, 7, 6])); | |
| assert.notEqual(first, second); | |
| }); | |
| it('keeps the hash stable for identical edit form content', async () => { | |
| const first = await buildEditRequestHash(makeEditForm([1, 2, 3, 4])); | |
| const second = await buildEditRequestHash(makeEditForm([1, 2, 3, 4])); | |
| assert.equal(first, second); | |
| }); | |
| }); | |
| describe('readIdempotencyKey', () => { | |
| it('rejects control characters before forwarding the value upstream', () => { | |
| const headers = new Headers({ 'Idempotency-Key': `agent${String.fromCharCode(31)}key` }); | |
| assert.throws( | |
| () => readIdempotencyKey(headers), | |
| (error) => { | |
| assert.ok(error instanceof Error); | |
| assert.match(error.message, /控制字符/); | |
| return true; | |
| } | |
| ); | |
| }); | |
| }); | |
| describe('hydrateAgentReplayResponse', () => { | |
| it('rejects base64 replay when stored artifact filepath escapes the image directory', async () => { | |
| const store = createReplayStore([ | |
| { | |
| id: 'artifact-escape', | |
| requestId: 'request-escape', | |
| filename: 'escape.png', | |
| filepath: '/etc/passwd', | |
| contentUrl: '/api/agent/artifacts/artifact-escape/content', | |
| metadataUrl: '/api/agent/artifacts/artifact-escape', | |
| outputFormat: 'png', | |
| mimeType: 'image/png', | |
| sizeBytes: 1, | |
| width: 1, | |
| height: 1, | |
| model: 'gpt-image-2', | |
| promptHash: 'hash', | |
| createdAt: '2026-05-12T00:00:00.000Z' | |
| } | |
| ]); | |
| await assert.rejects( | |
| () => | |
| hydrateAgentReplayResponse( | |
| store, | |
| { requestId: 'request-escape', requestJson: { response_mode: 'base64' } }, | |
| { | |
| request_id: 'request-escape', | |
| idempotency_key: 'idem-escape', | |
| cached: false, | |
| images: [ | |
| { | |
| id: 'artifact-escape', | |
| filename: 'escape.png', | |
| content_url: '/api/agent/artifacts/artifact-escape/content', | |
| metadata_url: '/api/agent/artifacts/artifact-escape', | |
| output_format: 'png', | |
| mime_type: 'image/png', | |
| size_bytes: 1, | |
| width: 1, | |
| height: 1 | |
| } | |
| ], | |
| created_at: '2026-05-12T00:00:00.000Z' | |
| } | |
| ), | |
| /目录之外/ | |
| ); | |
| }); | |
| }); | |
| describe('completeAgentExecutionState', () => { | |
| it('does not ask the state store to upsert artifacts a second time', async () => { | |
| let completedArtifacts: AgentArtifactRecord[] | undefined; | |
| const artifact: AgentArtifactRecord = { | |
| id: 'artifact-complete-no-upsert', | |
| requestId: 'request-complete-no-upsert', | |
| filename: 'artifact-complete-no-upsert.png', | |
| filepath: '/tmp/artifact-complete-no-upsert.png', | |
| contentUrl: '/api/agent/artifacts/artifact-complete-no-upsert/content', | |
| metadataUrl: '/api/agent/artifacts/artifact-complete-no-upsert', | |
| outputFormat: 'png', | |
| mimeType: 'image/png', | |
| sizeBytes: 1, | |
| width: 1, | |
| height: 1, | |
| model: 'gpt-image-2', | |
| promptHash: 'hash', | |
| createdAt: '2026-05-12T00:00:00.000Z' | |
| }; | |
| const store: AgentStateStore = { | |
| ...createReplayStore([]), | |
| async completeRequest(input) { | |
| completedArtifacts = input.artifacts; | |
| } | |
| }; | |
| await completeAgentExecutionState(store, { | |
| response: { | |
| request_id: 'request-complete-no-upsert', | |
| idempotency_key: 'idem-complete-no-upsert', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.000Z' | |
| }, | |
| stateResponse: { | |
| request_id: 'request-complete-no-upsert', | |
| idempotency_key: 'idem-complete-no-upsert', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.000Z' | |
| }, | |
| artifacts: [artifact] | |
| }); | |
| assert.deepEqual(completedArtifacts, []); | |
| }); | |
| }); | |
| function makeEditForm(bytes: number[]): FormData { | |
| const formData = new FormData(); | |
| formData.append('prompt', 'same prompt'); | |
| formData.append('model', 'gpt-image-2'); | |
| formData.append('response_mode', 'path'); | |
| formData.append('image_0', new File([Buffer.from(bytes)], 'input.png', { type: 'image/png' })); | |
| return formData; | |
| } | |
| function createReplayStore(artifacts: AgentArtifactRecord[]): AgentStateStore { | |
| return { | |
| async init() {}, | |
| async recoverExpiredRequests() { | |
| return 0; | |
| }, | |
| async purgeExpiredRequests() { | |
| return 0; | |
| }, | |
| async beginRequest() { | |
| throw new Error('not implemented'); | |
| }, | |
| async refreshRequestLease() { | |
| return false; | |
| }, | |
| async saveArtifacts() {}, | |
| async completeRequest() {}, | |
| async failRequest() {}, | |
| async getRequest() { | |
| return undefined; | |
| }, | |
| async getRequestByIdempotencyKey() { | |
| return undefined; | |
| }, | |
| async getArtifact() { | |
| return undefined; | |
| }, | |
| async listArtifactsForRequest() { | |
| return artifacts; | |
| }, | |
| async listArtifactFilepaths() { | |
| return artifacts.map((artifact) => artifact.filepath); | |
| }, | |
| async deleteArtifact() { | |
| return false; | |
| } | |
| }; | |
| } | |