98 lines
4.1 KiB
JavaScript
98 lines
4.1 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
import { spawn, spawnSync } from 'node:child_process';
|
|
import http from 'node:http';
|
|
import test from 'node:test';
|
|
|
|
const root = path.resolve(import.meta.dirname, '..');
|
|
const read = relative => fs.readFile(path.join(root, relative), 'utf8');
|
|
|
|
test('upload MIME types are detected from content and not trusted request metadata', async () => {
|
|
const upload = await read('src/middleware/upload.js');
|
|
const media = await read('src/services/media-service.js');
|
|
const pkg = JSON.parse(await read('package.json'));
|
|
|
|
assert.equal(pkg.dependencies['file-type'], '22.0.1');
|
|
assert.equal(pkg.dependencies['@file-type/av'], '0.2.0');
|
|
assert.match(upload, /fileTypeFromFile/);
|
|
assert.match(upload, /customDetectors:\s*\[detectAv\]/);
|
|
assert.match(upload, /detectGpx/);
|
|
assert.doesNotMatch(upload, /allowed\.has\(file\.mimetype\)/);
|
|
assert.doesNotMatch(upload, /contentType ist erforderlich/);
|
|
assert.match(media, /file\.detectedExtension/);
|
|
});
|
|
|
|
test('Python API tools are syntax-valid and contain reusable 401 authentication handling', async () => {
|
|
const toolsDir = path.join(root, 'tools/python');
|
|
const names = (await fs.readdir(toolsDir)).filter(name => name.endsWith('.py'));
|
|
const expected = [
|
|
'append_route.py', 'common.py', 'create_audio.py', 'create_picture.py', 'create_poi.py',
|
|
'create_route.py', 'delete_audio.py', 'delete_picture.py', 'delete_poi.py', 'delete_route.py',
|
|
'restore_route.py', 'update_audio.py', 'update_picture.py', 'update_poi.py', 'update_route.py'
|
|
];
|
|
assert.deepEqual(
|
|
names.toSorted((left, right) => left.localeCompare(right)),
|
|
expected.toSorted((left, right) => left.localeCompare(right))
|
|
);
|
|
|
|
const compile = spawnSync('python3', ['-m', 'py_compile', ...names.map(name => path.join(toolsDir, name))], {
|
|
cwd: root,
|
|
encoding: 'utf8'
|
|
});
|
|
assert.equal(compile.status, 0, compile.stderr);
|
|
await fs.rm(path.join(toolsDir, '__pycache__'), { recursive: true, force: true });
|
|
|
|
const common = await read('tools/python/common.py');
|
|
assert.match(common, /class ApiClient/);
|
|
assert.match(common, /error\.code == 401/);
|
|
assert.match(common, /self\.prompt_credentials\(\)/);
|
|
assert.match(common, /getpass\.getpass/);
|
|
assert.match(common, /os\.getenv\(API_USER_ENV/);
|
|
assert.match(common, /os\.getenv\(API_PASSWORD_ENV/);
|
|
assert.match(common, /def encode_multipart/);
|
|
assert.doesNotMatch(common, /Content-Type:.*application\/octet-stream/);
|
|
});
|
|
|
|
|
|
test('Python client retries the same request with API_USER and API_PASSWORD after HTTP 401', async t => {
|
|
const apiUser = process.env.API_USER || 'tester';
|
|
const apiPassword = process.env.API_PASSWORD || 'secret';
|
|
const expectedAuthorization = `Basic ${Buffer.from(`${apiUser}:${apiPassword}`).toString('base64')}`;
|
|
const receivedAuthorization = [];
|
|
|
|
const server = http.createServer((req, res) => {
|
|
receivedAuthorization.push(req.headers.authorization || null);
|
|
if (req.headers.authorization !== expectedAuthorization) {
|
|
res.writeHead(401, { 'Content-Type': 'application/json', 'WWW-Authenticate': 'Basic realm="test"' });
|
|
res.end(JSON.stringify({ message: 'Zugangsdaten erforderlich.' }));
|
|
return;
|
|
}
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ ok: true }));
|
|
});
|
|
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
t.after(() => server.close());
|
|
const { port } = server.address();
|
|
|
|
const script = `
|
|
from common import ApiClient
|
|
result = ApiClient('http://127.0.0.1:${port}').request('GET', '/protected')
|
|
assert result == {'ok': True}
|
|
`;
|
|
const child = spawn('python3', ['-c', script], {
|
|
env: {
|
|
...process.env,
|
|
API_USER: apiUser,
|
|
API_PASSWORD: apiPassword,
|
|
PYTHONPATH: path.join(root, 'tools/python')
|
|
},
|
|
stdio: ['ignore', 'pipe', 'pipe']
|
|
});
|
|
const stderr = [];
|
|
child.stderr.on('data', chunk => stderr.push(chunk.toString()));
|
|
const status = await new Promise(resolve => child.once('exit', resolve));
|
|
assert.equal(status, 0, stderr.join(''));
|
|
assert.deepEqual(receivedAuthorization, [null, expectedAuthorization]);
|
|
});
|