const test = require('node:test'); const assert = require('node:assert'); const http = require('node:http'); const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const { uploadItemsViaGiteaApi, createUploadCancelledError, isUploadCancelledError, throwIfUploadCancelled } = require('../src/git/apiHandler.js'); test('Abbruchfehler: Erkennung und throwIfUploadCancelled', () => { assert.strictEqual(isUploadCancelledError(createUploadCancelledError()), true); assert.strictEqual(isUploadCancelledError(new Error('anderer Fehler')), false); assert.strictEqual(isUploadCancelledError(null), false); assert.doesNotThrow(() => throwIfUploadCancelled(undefined)); assert.doesNotThrow(() => throwIfUploadCancelled(new AbortController().signal)); const c = new AbortController(); c.abort(); assert.throws(() => throwIfUploadCancelled(c.signal), (e) => isUploadCancelledError(e)); }); function makeTempFile() { const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gm-cancel-')); const file = path.join(dir, 'a.txt'); fs.writeFileSync(file, 'hallo'); return { dir, file }; } test('API-Upload: bereits abgebrochenes Signal sendet nichts', async () => { const { dir, file } = makeTempFile(); try { const c = new AbortController(); c.abort(); await assert.rejects( uploadItemsViaGiteaApi({ token: 't', url: 'http://127.0.0.1:1', owner: 'o', repo: 'r', branch: 'main', items: [{ localFile: file, targetPath: 'a.txt' }], message: 'm', signal: c.signal }), (e) => isUploadCancelledError(e) ); } finally { fs.rmSync(dir, { recursive: true, force: true }); } }); test('API-Upload: Abbruch während der Übertragung wirft Abbruchfehler und landet nicht in "failed"', async () => { const { dir, file } = makeTempFile(); let postSeen = false; const server = http.createServer((req, res) => { if (req.method === 'POST') { postSeen = true; setTimeout(() => { res.writeHead(201, { 'content-type': 'application/json' }); res.end('{}'); }, 3000); return; } res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ default_branch: 'main', tree: [], entries: [] })); }); await new Promise(r => server.listen(0, '127.0.0.1', r)); const { port } = server.address(); try { const c = new AbortController(); const started = Date.now(); setTimeout(() => c.abort(), 400); await assert.rejects( uploadItemsViaGiteaApi({ token: 't', url: `http://127.0.0.1:${port}`, owner: 'o', repo: 'r', branch: 'main', items: [{ localFile: file, targetPath: 'a.txt' }], message: 'm', signal: c.signal }), (e) => isUploadCancelledError(e) ); assert.ok(postSeen, 'Der POST muss gestartet sein, sonst prüft der Test nichts'); assert.ok(Date.now() - started < 2500, 'Abbruch muss sofort greifen, nicht erst nach der Serverantwort'); } finally { server.closeAllConnections?.(); await new Promise(r => server.close(r)); fs.rmSync(dir, { recursive: true, force: true }); } }); const { createPauseGate } = require('../src/features/pauseGate.js'); function gitea(onPost) { return http.createServer((req, res) => { if (req.method === 'POST') { onPost(); res.writeHead(201, { 'content-type': 'application/json' }); res.end('{}'); return; } res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ default_branch: 'main', tree: [], entries: [] })); }); } test('API-Upload: während der Pause wird nichts gesendet, nach resume() schon', async () => { const { dir, file } = makeTempFile(); let posts = 0; const server = gitea(() => { posts++; }); await new Promise(r => server.listen(0, '127.0.0.1', r)); const { port } = server.address(); const gate = createPauseGate(); try { gate.pause(); const upload = uploadItemsViaGiteaApi({ token: 't', url: `http://127.0.0.1:${port}`, owner: 'o', repo: 'r', branch: 'main', items: [{ localFile: file, targetPath: 'a.txt' }], message: 'm', waitIfPaused: (s) => gate.wait(s) }); await new Promise(r => setTimeout(r, 700)); assert.strictEqual(posts, 0, 'Während der Pause darf kein Paket gesendet werden'); gate.resume(); const result = await upload; assert.strictEqual(posts, 1); assert.deepStrictEqual(result.uploaded, ['a.txt']); } finally { await new Promise(r => server.close(r)); fs.rmSync(dir, { recursive: true, force: true }); } }); test('API-Upload: Abbruch während der Pause beendet den Upload sofort', async () => { const { dir, file } = makeTempFile(); let posts = 0; const server = gitea(() => { posts++; }); await new Promise(r => server.listen(0, '127.0.0.1', r)); const { port } = server.address(); const gate = createPauseGate(); try { gate.pause(); const c = new AbortController(); const upload = uploadItemsViaGiteaApi({ token: 't', url: `http://127.0.0.1:${port}`, owner: 'o', repo: 'r', branch: 'main', items: [{ localFile: file, targetPath: 'a.txt' }], message: 'm', signal: c.signal, waitIfPaused: (s) => gate.wait(s) }); setTimeout(() => c.abort(), 400); await assert.rejects(upload, (e) => isUploadCancelledError(e)); assert.strictEqual(posts, 0); assert.strictEqual(gate.waitingCount(), 0); } finally { await new Promise(r => server.close(r)); fs.rmSync(dir, { recursive: true, force: true }); } });