51 lines
3.6 KiB
JavaScript
51 lines
3.6 KiB
JavaScript
'use strict';
|
|
|
|
const assert = require('node:assert/strict');
|
|
const crypto = require('crypto');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const test = require('node:test');
|
|
const { createAppServer } = require('../../src/server/app-server');
|
|
|
|
test('streaming import routes require auth and CSRF and return resumable offsets', async () => {
|
|
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-upload-http-'));
|
|
fs.mkdirSync(path.join(root, 'cache'), { recursive: true });
|
|
const values = new Map();
|
|
const settings = { get: (key, fallback) => values.has(key) ? values.get(key) : fallback, set: (key, value) => values.set(key, value) };
|
|
const secretValues = new Map();
|
|
const secrets = { get: key => secretValues.get(key), set: (key, value) => secretValues.set(key, value), delete: key => secretValues.delete(key) };
|
|
const registry = { describe: () => [], invoke: async () => ({}), isMutating: () => false, requestBodyLimit: () => 1024 };
|
|
const logger = { info() {}, warn() {}, error() {}, audit() {} };
|
|
const app = createAppServer({ appRoot: path.resolve(__dirname, '../..'), paths: { root, cache: path.join(root, 'cache') }, settings, secrets, registry, logger, ftp: { disconnect: async () => {} }, capabilities: { features: {} }, version: 'test' });
|
|
const address = await app.listen(0, '127.0.0.1');
|
|
const base = `http://127.0.0.1:${address.port}`;
|
|
const common = { Host: `127.0.0.1:${address.port}`, Origin: base, 'Content-Type': 'application/json' };
|
|
try {
|
|
const denied = await fetch(`${base}/api/import/uploads`, { method: 'POST', headers: common, body: JSON.stringify({ files: [{ relativePath: 'game.pkg', size: 4 }] }) });
|
|
assert.equal(denied.status, 401);
|
|
const setup = await fetch(`${base}/api/setup`, { method: 'POST', headers: common, body: JSON.stringify({ username: 'uploader', password: 'secure-password-123', allowedRoots: [root], safetyConfirmed: true }) });
|
|
assert.equal(setup.status, 201);
|
|
const cookie = setup.headers.get('set-cookie').split(';')[0];
|
|
const csrf = (await setup.json()).csrfToken;
|
|
const missingCsrf = await fetch(`${base}/api/import/uploads`, { method: 'POST', headers: { ...common, Cookie: cookie }, body: JSON.stringify({ files: [{ relativePath: 'game.pkg', size: 4 }] }) });
|
|
assert.equal(missingCsrf.status, 403);
|
|
const createdResponse = await fetch(`${base}/api/import/uploads`, { method: 'POST', headers: { ...common, Cookie: cookie, 'X-CSRF-Token': csrf }, body: JSON.stringify({ files: [{ relativePath: 'folder/game.pkg', size: 4 }] }) });
|
|
assert.equal(createdResponse.status, 201);
|
|
const created = (await createdResponse.json()).result;
|
|
assert.equal(JSON.stringify(created).includes(root), false);
|
|
const chunkUrl = `${base}/api/import/uploads/${created.id}/files/${created.files[0].id}`;
|
|
const chunk = Buffer.from('data');
|
|
const chunkHeaders = { Host: common.Host, Origin: base, Cookie: cookie, 'X-CSRF-Token': csrf, 'Upload-Offset': '0', 'X-Chunk-SHA256': crypto.createHash('sha256').update(chunk).digest('hex'), 'Content-Type': 'application/octet-stream' };
|
|
const deniedChunk = await fetch(chunkUrl, { method: 'PUT', headers: { ...chunkHeaders, 'X-CSRF-Token': '' }, body: chunk });
|
|
assert.equal(deniedChunk.status, 403);
|
|
const uploaded = await fetch(chunkUrl, { method: 'PUT', headers: chunkHeaders, body: chunk });
|
|
assert.equal(uploaded.status, 200);
|
|
assert.equal(uploaded.headers.get('upload-offset'), '4');
|
|
assert.equal((await uploaded.json()).result.state, 'uploaded');
|
|
} finally {
|
|
await app.close();
|
|
fs.rmSync(root, { recursive: true, force: true });
|
|
}
|
|
});
|