Files
Wegwichtel/test/api-docs.test.js
T
Florian Zumpe 0ead30e740
Sonarqube Scanner / Prepare environment (push) Successful in 6s
Sonarqube Scanner / Build and test (push) Successful in 25s
Sonarqube Scanner / SonarQube analysis (push) Failing after 1m20s
Sonarqube Scanner / Deploy to Server (push) Failing after 1s
refactor tests
2026-09-30 01:43:38 +02:00

111 lines
4.5 KiB
JavaScript

import assert from 'node:assert/strict';
import fs from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
const root = path.resolve(import.meta.dirname, '..');
const ignoredDirectories = new Set([
'.git',
'coverage',
'data',
'node_modules',
'storage'
]);
async function read(relativePath) {
return fs.readFile(path.join(root, relativePath), 'utf8');
}
async function markdownFiles(directory = root) {
const result = [];
const entries = await fs.readdir(directory, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) {
if (ignoredDirectories.has(entry.name)) continue;
result.push(...await markdownFiles(path.join(directory, entry.name)));
continue;
}
if (!entry.isFile() || !entry.name.toLowerCase().endsWith('.md')) continue;
result.push(path.join(directory, entry.name));
}
return result;
}
function routeDeclaration(source, method, endpoint) {
const marker = `api.${method}('${endpoint}'`;
const start = source.indexOf(marker);
assert.notEqual(start, -1, `API route missing: ${method.toUpperCase()} ${endpoint}`);
const end = source.indexOf('\n', start);
return source.slice(start, end === -1 ? source.length : end);
}
function assertMiddleware(declaration, middleware) {
assert.ok(
declaration.includes(middleware),
`Expected ${middleware} in route declaration: ${declaration}`
);
}
test('complete REST API documentation is kept outside the README', async () => {
const files = await markdownFiles();
const readmePath = files.find(file => path.basename(file).toLowerCase() === 'readme.md');
assert.ok(readmePath, 'README.md is missing');
const readme = await fs.readFile(readmePath, 'utf8');
const outsideReadme = files.filter(file => file !== readmePath);
const candidates = [];
for (const file of outsideReadme) {
const content = await fs.readFile(file, 'utf8');
const endpointMentions = content.match(/\/api\/[a-z0-9_:/?=&.-]+/gi) || [];
if (endpointMentions.length >= 5) candidates.push({ file, content, endpointMentions });
}
assert.ok(candidates.length > 0, 'No standalone REST API documentation was found.');
const apiDocument = candidates.sort((a, b) => b.endpointMentions.length - a.endpointMentions.length)[0];
assert.match(apiDocument.content, /\/api\/health/);
assert.match(apiDocument.content, /\/api\/routes/);
assert.match(apiDocument.content, /pois/);
assert.match(apiDocument.content, /pictures/);
assert.match(apiDocument.content, /audio/);
const readmeEndpointMentions = readme.match(/\/api\/[a-z0-9_:/?=&.-]+/gi) || [];
assert.ok(
apiDocument.endpointMentions.length > readmeEndpointMentions.length,
`The standalone API documentation (${path.relative(root, apiDocument.file)}) must remain more complete than README.md.`
);
});
test('Streckenpunkt media endpoints keep upload validation and release-write guards', async () => {
const api = await read('src/routes/api.js');
const picturePost = routeDeclaration(api, 'post', '/routes/:routeId/pois/:poiId/pictures');
assertMiddleware(picturePost, "requireWritableRoute('routeId')");
assertMiddleware(picturePost, "upload.single('picture')");
assertMiddleware(picturePost, "inspectUpload('picture', 'picture'");
const picturePut = routeDeclaration(api, 'put', '/routes/:routeId/pois/:poiId/pictures/:pictureId');
assertMiddleware(picturePut, "requireWritableRoute('routeId')");
assertMiddleware(picturePut, "upload.single('picture')");
assertMiddleware(picturePut, "inspectUpload('picture', 'picture'");
const audioPost = routeDeclaration(api, 'post', '/routes/:routeId/pois/:poiId/audio');
assertMiddleware(audioPost, "requireWritableRoute('routeId')");
assertMiddleware(audioPost, "upload.single('audio')");
assertMiddleware(audioPost, "inspectUpload('audio', 'audio'");
const audioPut = routeDeclaration(api, 'put', '/routes/:routeId/pois/:poiId/audio');
assertMiddleware(audioPut, "requireWritableRoute('routeId')");
assertMiddleware(audioPut, "upload.single('audio')");
assertMiddleware(audioPut, "inspectUpload('audio', 'audio'");
const pictureDelete = routeDeclaration(api, 'delete', '/routes/:routeId/pois/:poiId/pictures/:pictureId');
assertMiddleware(pictureDelete, "requireRouteDeleteAllowed('routeId')");
const audioDelete = routeDeclaration(api, 'delete', '/routes/:routeId/pois/:poiId/audio');
assertMiddleware(audioDelete, "requireRouteDeleteAllowed('routeId')");
assert.doesNotMatch(api, /\/pois\/:poiId\/images/);
});