import assert from 'node:assert/strict'; import { putToVm } from './upload/put_to_vm.js'; import { uploadViaVM } from './upload/upload_via_vm.js'; if (typeof globalThis.File === 'undefined') { globalThis.File = class File extends Blob { constructor(parts, name, options = {}) { super(parts, options); this.name = name; this.lastModified = options.lastModified || Date.now(); } }; } globalThis.crypto = globalThis.crypto || { randomUUID: () => 'test-upload-id' }; let lastXHR; class FakeXHR { constructor() { this.upload = {}; this.status = 0; this.statusText = ''; this.timeout = 0; lastXHR = this; } open(method, url) { this.method = method; this.url = url; } send(body) { this.body = body; } complete(status = 200) { this.status = status; this.onload(); } } globalThis.XMLHttpRequest = FakeXHR; let fetchCalls = []; let fetchResponder; globalThis.fetch = async (url, options) => { fetchCalls.push({ url, options }); return fetchResponder(fetchCalls.length); }; const tick = () => new Promise((resolve) => setTimeout(resolve, 0)); async function waitForNextXHR(previous) { for (let attempt = 0; attempt < 20 && lastXHR === previous; attempt += 1) await tick(); assert.notEqual(lastXHR, previous, 'next PUT request was not started'); return lastXHR; } async function testPutToVmSuccess() { const file = new File(['payload'], 'a.pdf'); const request = putToVm(file, 'https://vm.example/a'); assert.equal(lastXHR.method, 'PUT'); assert.equal(lastXHR.body, file); lastXHR.complete(); await request; } async function testUploadIncludesPerFilePayloadAndResult() { fetchCalls = []; fetchResponder = async (callNumber) => ({ ok: true, status: 200, statusText: 'OK', json: async () => ({ ok: true, results: [{ ok: true, doc_id: `doc-${callNumber}`, parsed: { status: 'parsed' } }], }), }); const files = [ { id: 'one', name: 'one.pdf', path: 'one.pdf', file: new File(['1'], 'one.pdf') }, { id: 'two', name: 'two.pdf', path: 'archive.zip/folder/two.pdf', file: new File(['2'], 'two.pdf') }, ]; const completed = []; const previous = lastXHR; const request = uploadViaVM(files, { vmUploadUrl: 'https://vm.example/contracts-upload/', backendUploadUrl: '/api/upload_refs', getExtraPayload: (entry) => ({ batch_id: 'batch-1', contract_id: 'contract-1', zip_source: entry.id === 'two' ? 'archive.zip' : null, }), onFileComplete: (event) => completed.push(event), }); const firstXHR = await waitForNextXHR(previous); firstXHR.complete(); const secondXHR = await waitForNextXHR(firstXHR); secondXHR.complete(); const result = await request; assert.equal(result.ok, true); assert.equal(fetchCalls.length, 2); const firstBody = JSON.parse(fetchCalls[0].options.body); const secondBody = JSON.parse(fetchCalls[1].options.body); assert.equal(firstBody.batch_id, 'batch-1'); assert.equal(firstBody.contract_id, 'contract-1'); assert.equal(firstBody.files.length, 1); assert.equal(secondBody.zip_source, 'archive.zip'); assert.equal(completed.length, 2); assert.equal(completed[1].result.doc_id, 'doc-2'); assert.equal(completed[1].source.id, 'two'); } async function testUploadReportsPutFailure() { fetchCalls = []; const previous = lastXHR; const request = uploadViaVM([new File(['x'], 'x.pdf')], { vmUploadUrl: 'https://vm.example/', }); const xhr = await waitForNextXHR(previous); xhr.onerror(); const result = await request; assert.equal(result.ok, false); assert.equal(fetchCalls.length, 0); assert.match(result.error, /Ошибка отправки файла/); } async function testUploadReportsBackendFailure() { fetchCalls = []; fetchResponder = async () => ({ ok: false, status: 502, statusText: 'Bad Gateway', json: async () => ({ ok: false, error: 'failed' }), }); const previous = lastXHR; const request = uploadViaVM([new File(['x'], 'x.pdf')], { vmUploadUrl: 'https://vm.example/', backendUploadUrl: '/api/upload_refs', }); const xhr = await waitForNextXHR(previous); xhr.complete(); const result = await request; assert.equal(result.ok, false); assert.match(result.error, /HTTP 502/); } async function testAbortBeforeUpload() { const controller = new AbortController(); controller.abort(); const previous = lastXHR; const result = await uploadViaVM([new File(['x'], 'x.pdf')], { vmUploadUrl: 'https://vm.example/', signal: controller.signal, }); assert.equal(result.aborted, true); assert.equal(lastXHR, previous); } const tests = [ ['PUT via WebDAV', testPutToVmSuccess], ['per-file extra payload and sink result', testUploadIncludesPerFilePayloadAndResult], ['PUT failure', testUploadReportsPutFailure], ['backend failure', testUploadReportsBackendFailure], ['abort before upload', testAbortBeforeUpload], ]; for (const [name, test] of tests) { await test(); console.log(`PASS ${name}`); }