refactor tests
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

This commit is contained in:
Florian Zumpe
2026-09-30 01:43:38 +02:00
parent bbb0ec48f9
commit 0ead30e740
7 changed files with 727 additions and 387 deletions
+45 -30
View File
@@ -40,6 +40,14 @@ function assertHttpError(expectedStatus, expectedMessage) {
};
}
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);
}
test('nicht freigegebene Strecken bleiben schreibbar', () => {
const db = fakeDatabase({ id: 7, activatedAt: null });
assert.deepEqual(assertRouteWritable(db, 7), { id: 7, activatedAt: null });
@@ -110,42 +118,49 @@ test('DELETE einer freigegebenen Strecke ist nur direkt lokal zulässig', () =>
);
});
test('alle vorhandenen Schreibpfade verwenden die zentrale Freigabeprüfung', async () => {
test('alle mutierenden API-Pfade prüfen den Freigabestatus vor Upload oder Handler', async () => {
const api = await fs.readFile(path.join(root, 'src/routes/api.js'), 'utf8');
const writablePatterns = [
/api\.put\('\/routes\/:id',\s*requireWritableRoute\('id'\),\s*upload\.single\('gpx'\)/,
/api\.post\('\/routes\/:id\/append',\s*requireWritableRoute\('id'\),\s*upload\.single\('gpx'\)/,
/api\.post\('\/routes\/:id\/pois',\s*requireWritableRoute\('id'\),\s*upload\.none\(\)/,
/api\.put\('\/routes\/:routeId\/pois\/:poiId',\s*requireWritableRoute\('routeId'\),\s*upload\.none\(\)/,
/api\.post\('\/routes\/:routeId\/pois\/:poiId\/pictures',\s*requireWritableRoute\('routeId'\),\s*upload\.single\('picture'\)/,
/api\.put\('\/routes\/:routeId\/pois\/:poiId\/pictures\/:pictureId',\s*requireWritableRoute\('routeId'\),\s*upload\.single\('picture'\)/,
/api\.post\('\/routes\/:routeId\/pois\/:poiId\/audio',\s*requireWritableRoute\('routeId'\),\s*upload\.single\('audio'\)/,
/api\.put\('\/routes\/:routeId\/pois\/:poiId\/audio',\s*requireWritableRoute\('routeId'\),\s*upload\.single\('audio'\)/,
/api\.post\('\/routes\/:id\/restore',\s*requireWritableRoute\('id'\)/
const writableRoutes = [
['put', '/routes/:id', 'id'],
['post', '/routes/:id/append', 'id'],
['post', '/routes/:id/pois', 'id'],
['put', '/routes/:routeId/pois/:poiId', 'routeId'],
['post', '/routes/:routeId/pois/:poiId/pictures', 'routeId'],
['put', '/routes/:routeId/pois/:poiId/pictures/:pictureId', 'routeId'],
['post', '/routes/:routeId/pois/:poiId/audio', 'routeId'],
['put', '/routes/:routeId/pois/:poiId/audio', 'routeId'],
['post', '/routes/:id/restore', 'id']
];
for (const pattern of writablePatterns) assert.match(api, pattern);
for (const [method, endpoint, parameter] of writableRoutes) {
const declaration = routeDeclaration(api, method, endpoint);
const guard = `requireWritableRoute('${parameter}')`;
assert.ok(declaration.includes(guard), `${method.toUpperCase()} ${endpoint} has no release-write guard`);
const uploadPosition = declaration.indexOf('upload.');
if (uploadPosition >= 0) {
assert.ok(
declaration.indexOf(guard) < uploadPosition,
`${method.toUpperCase()} ${endpoint} must check writability before processing uploads`
);
}
}
});
test('Route-, POI-, Bild- und Audio-DELETE verwenden die lokale Freigabeausnahme', async () => {
test('Route-, Streckenpunkt-, Bild- und Audio-DELETE verwenden die lokale Freigabeausnahme', async () => {
const api = await fs.readFile(path.join(root, 'src/routes/api.js'), 'utf8');
const deletePatterns = [
/api\.delete\('\/routes\/:id',\s*requireRouteDeleteAllowed\('id'\)/,
/api\.delete\('\/routes\/:routeId\/pois\/:poiId',\s*requireRouteDeleteAllowed\('routeId'\)/,
/api\.delete\('\/routes\/:routeId\/pois\/:poiId\/pictures\/:pictureId',\s*requireRouteDeleteAllowed\('routeId'\)/,
/api\.delete\('\/routes\/:routeId\/pois\/:poiId\/audio',\s*requireRouteDeleteAllowed\('routeId'\)/
const deleteRoutes = [
['delete', '/routes/:id', 'id'],
['delete', '/routes/:routeId/pois/:poiId', 'routeId'],
['delete', '/routes/:routeId/pois/:poiId/pictures/:pictureId', 'routeId'],
['delete', '/routes/:routeId/pois/:poiId/audio', 'routeId']
];
for (const pattern of deletePatterns) assert.match(api, pattern);
});
test('nginx kennzeichnet weitergeleitete Internet-Aufrufe für die Local-DELETE-Prüfung', async () => {
const nginx = await fs.readFile(path.join(root, 'nginx/wegwichtel.conf'), 'utf8');
assert.match(nginx, /proxy_pass\s+http:\/\/127\.0\.0\.1:47145;/);
assert.match(nginx, /proxy_set_header\s+X-Real-IP\s+\$remote_addr;/);
assert.match(nginx, /proxy_set_header\s+X-Forwarded-For\s+\$proxy_add_x_forwarded_for;/);
for (const [method, endpoint, parameter] of deleteRoutes) {
const declaration = routeDeclaration(api, method, endpoint);
assert.ok(
declaration.includes(`requireRouteDeleteAllowed('${parameter}')`),
`${method.toUpperCase()} ${endpoint} has no local-delete guard`
);
}
});