included serverside mime detection
Sonarqube Scanner / Build and analyze (push) Successful in 1m6s

This commit is contained in:
Florian Zumpe
2026-06-17 11:42:43 +02:00
parent 3b3069ca8c
commit 0def9b23db
11 changed files with 574 additions and 126 deletions
+57 -19
View File
@@ -7,6 +7,19 @@ import { spawn } from 'node:child_process';
import test from 'node:test';
const root = path.resolve(import.meta.dirname, '..');
const PNG_FILE = Buffer.from(
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9ZQmcAAAAASUVORK5CYII=',
'base64'
);
const MP3_FILE = Buffer.concat([
Buffer.from('49443304000000000000', 'hex'),
Buffer.from('fffb9064', 'hex'),
Buffer.alloc(512)
]);
const WAV_FILE = Buffer.from(
'524946462400000057415645666d74201000000001000100401f0000803e0000020010006461746100000000',
'hex'
);
async function freePort() {
return new Promise((resolve, reject) => {
@@ -79,7 +92,7 @@ test('pictures and audio are managed as individual REST resources', { timeout: 3
const gpx = await fs.readFile(path.join(root, 'examples', 'sample-route.gpx'));
const routeForm = new FormData();
routeForm.append('name', 'Medien-API-Test');
routeForm.append('gpx', new Blob([gpx], { type: 'application/gpx+xml' }), 'route.gpx');
routeForm.append('gpx', new Blob([gpx], { type: 'application/octet-stream' }), 'route.txt');
const { body: route } = await requestJson(`${baseUrl}/api/routes`, {
method: 'POST',
body: routeForm
@@ -139,7 +152,7 @@ test('pictures and audio are managed as individual REST resources', { timeout: 3
pictureForm.append('poiId', String(otherPoi.id));
pictureForm.append('caption', 'Erste Bildbeschreibung');
pictureForm.append('sequence', '0');
pictureForm.append('picture', new Blob(['picture-one'], { type: 'image/png' }), 'bild.png');
pictureForm.append('picture', new Blob([PNG_FILE], { type: 'application/octet-stream' }), 'bild.txt');
const { response: pictureResponse, body: picture } = await requestJson(
`${baseUrl}/api/routes/${route.id}/pois/${poi.id}/pictures`,
{ method: 'POST', body: pictureForm },
@@ -151,7 +164,7 @@ test('pictures and audio are managed as individual REST resources', { timeout: 3
assert.equal(picture.url, `/api/routes/${route.id}/pois/${poi.id}/pictures/${picture.id}`);
const { response: pictureFileResponse, body: pictureFile } = await requestBuffer(`${baseUrl}${picture.url}`);
assert.match(pictureFileResponse.headers.get('content-type') || '', /image\/png/);
assert.equal(pictureFile.toString(), 'picture-one');
assert.deepEqual(pictureFile, PNG_FILE);
const { body: pictureMetadata } = await requestJson(`${baseUrl}${picture.url}?metadata=true`);
assert.equal(pictureMetadata.caption, 'Erste Bildbeschreibung');
assert.equal(pictureMetadata.poiId, poi.id);
@@ -169,9 +182,8 @@ test('pictures and audio are managed as individual REST resources', { timeout: 3
caption: 'Bild aus JSON',
sequence: 1,
picture: {
filename: 'bild-json.webp',
contentType: 'image/webp',
base64: Buffer.from('picture-json').toString('base64')
filename: 'bild-json.bin',
base64: PNG_FILE.toString('base64')
}
};
const { body: jsonPicture } = await requestJson(
@@ -186,8 +198,8 @@ test('pictures and audio are managed as individual REST resources', { timeout: 3
assert.equal(jsonPicture.poiId, otherPoi.id);
assert.equal(jsonPicture.caption, 'Bild aus JSON');
const { response: jsonPictureResponse, body: jsonPictureFile } = await requestBuffer(`${baseUrl}${jsonPicture.url}`);
assert.match(jsonPictureResponse.headers.get('content-type') || '', /image\/webp/);
assert.equal(jsonPictureFile.toString(), 'picture-json');
assert.match(jsonPictureResponse.headers.get('content-type') || '', /image\/png/);
assert.deepEqual(jsonPictureFile, PNG_FILE);
await requestJson(
`${baseUrl}/api/routes/${route.id}/pois/${otherPoi.id}/pictures`,
@@ -199,6 +211,22 @@ test('pictures and audio are managed as individual REST resources', { timeout: 3
400
);
await requestJson(
`${baseUrl}/api/routes/${route.id}/pois/${otherPoi.id}/pictures`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
picture: {
filename: 'behauptetes-bild.png',
contentType: 'image/png',
base64: Buffer.from('kein Bild').toString('base64')
}
})
},
415
);
const { body: updatedPicture } = await requestJson(
`${baseUrl}/api/routes/${route.id}/pois/${poi.id}/pictures/${picture.id}`,
{
@@ -211,7 +239,7 @@ test('pictures and audio are managed as individual REST resources', { timeout: 3
assert.equal(updatedPicture.sequence, 2);
const audioForm = new FormData();
audioForm.append('audio', new Blob(['audio-one'], { type: 'audio/mpeg' }), 'ansage.mp3');
audioForm.append('audio', new Blob([MP3_FILE], { type: 'application/octet-stream' }), 'ansage.txt');
const { response: audioResponse, body: audio } = await requestJson(
`${baseUrl}/api/routes/${route.id}/pois/${poi.id}/audio`,
{ method: 'POST', body: audioForm },
@@ -222,12 +250,12 @@ test('pictures and audio are managed as individual REST resources', { timeout: 3
assert.equal(audio.url, `/api/routes/${route.id}/pois/${poi.id}/audio`);
const { response: audioFileResponse, body: audioFile } = await requestBuffer(`${baseUrl}${audio.url}`);
assert.match(audioFileResponse.headers.get('content-type') || '', /audio\/mpeg/);
assert.equal(audioFile.toString(), 'audio-one');
assert.deepEqual(audioFile, MP3_FILE);
const { body: audioMetadata } = await requestJson(`${baseUrl}${audio.url}?metadata=true`);
assert.equal(audioMetadata.poiId, poi.id);
const duplicateAudio = new FormData();
duplicateAudio.append('audio', new Blob(['duplicate'], { type: 'audio/mpeg' }), 'doppelt.mp3');
duplicateAudio.append('audio', new Blob([MP3_FILE], { type: 'image/png' }), 'doppelt.png');
await requestJson(`${baseUrl}/api/routes/${route.id}/pois/${poi.id}/audio`, {
method: 'POST',
body: duplicateAudio
@@ -235,9 +263,8 @@ test('pictures and audio are managed as individual REST resources', { timeout: 3
const replacementAudio = {
audio: {
filename: 'ansage-neu.ogg',
contentType: 'audio/ogg',
base64: Buffer.from('audio-two').toString('base64')
filename: 'ansage-neu.bin',
base64: WAV_FILE.toString('base64')
}
};
const { body: replacedAudio } = await requestJson(
@@ -250,8 +277,8 @@ test('pictures and audio are managed as individual REST resources', { timeout: 3
);
assert.equal(replacedAudio.url, `/api/routes/${route.id}/pois/${poi.id}/audio`);
const { response: replacedAudioResponse, body: replacedAudioFile } = await requestBuffer(`${baseUrl}${replacedAudio.url}`);
assert.match(replacedAudioResponse.headers.get('content-type') || '', /audio\/ogg/);
assert.equal(replacedAudioFile.toString(), 'audio-two');
assert.match(replacedAudioResponse.headers.get('content-type') || '', /audio\/wav/);
assert.deepEqual(replacedAudioFile, WAV_FILE);
const { body: routeWithMedia } = await requestJson(`${baseUrl}/api/routes/${route.id}`);
assert.equal(routeWithMedia.pois[0].images[0].caption, 'Aktualisierte Bildbeschreibung');
@@ -278,11 +305,22 @@ test('pictures and audio are managed as individual REST resources', { timeout: 3
);
assert.equal(deletedPicture.deleted, true);
const { body: deletedJsonPicture } = await requestJson(
`${baseUrl}/api/routes/${route.id}/pois/${otherPoi.id}/pictures/${jsonPicture.id}`,
const otherAudioForm = new FormData();
otherAudioForm.append('audio', new Blob([MP3_FILE], { type: 'text/plain' }), 'falsch.txt');
await requestJson(
`${baseUrl}/api/routes/${route.id}/pois/${otherPoi.id}/audio`,
{ method: 'POST', body: otherAudioForm },
201
);
const { body: deletedPoi } = await requestJson(
`${baseUrl}/api/routes/${route.id}/pois/${otherPoi.id}`,
{ method: 'DELETE' }
);
assert.equal(deletedJsonPicture.deleted, true);
assert.equal(deletedPoi.deleted, true);
await requestJson(`${baseUrl}/api/routes/${route.id}/pois/${otherPoi.id}`, {}, 404);
await requestJson(`${baseUrl}/api/routes/${route.id}/pois/${otherPoi.id}/pictures/${jsonPicture.id}`, {}, 404);
await requestJson(`${baseUrl}/api/routes/${route.id}/pois/${otherPoi.id}/audio`, {}, 404);
await requestJson(`${baseUrl}/api/routes/${route.id}/pois/${poi.id}/pictures/${picture.id}`, {}, 404);
await requestJson(`${baseUrl}/api/routes/${route.id}/pois/${poi.id}/audio`, {}, 404);