allow update for uploaded routes and implement creator id for routes
Sonarqube Scanner / Prepare environment (push) Successful in 8s
Sonarqube Scanner / Build and test (push) Successful in 21s
Sonarqube Scanner / SonarQube analysis (push) Failing after 1m3s
Sonarqube Scanner / Deploy to Server (push) Successful in 6s

This commit is contained in:
Florian Zumpe
2026-10-03 16:43:08 +02:00
parent 4b8bdcad5f
commit 0db91660b3
20 changed files with 423 additions and 91 deletions
+64 -5
View File
@@ -188,6 +188,7 @@ function submitEvent() {
}
function buildEditorContext() {
const controls = { failPicture: false, approved: false, recording: null };
const ids = [
'editor-add-poi',
'editor-button',
@@ -254,13 +255,22 @@ function buildEditorContext() {
async function fetch(url, options = {}) {
const method = options.method || 'GET';
calls.push({ body: options.body, method, url });
if (controls.failPicture && url.endsWith('/pictures/24') && method === 'PUT') {
controls.failPicture = false;
return jsonResponse({ message: 'Testfehler' }, false, 500);
}
if (String(url).startsWith('data:')) {
return { ok: true, status: 200, blob: async () => new Blob(['media'], { type: 'application/octet-stream' }) };
}
if (url === '/api/routes?activatedOnly=true' && method === 'GET') return jsonResponse({ routes: [serverRoute] });
if (url === '/api/routes' && method === 'POST') return jsonResponse({ id: 22, name: 'Neue lokale Strecke', pois: [] }, true, 201);
if (url === '/api/routes/22/pois' && method === 'POST') return jsonResponse({ id: 23, title: 'Aussicht' }, true, 201);
if (/^\/api\/routes\/22\/pois\/23\/(audio|pictures)$/.test(url) && method === 'POST') return jsonResponse({ ok: true }, true, 201);
if (/^\/api\/routes\/22\/pois\/23\/(audio|pictures)(?:\/24)?$/.test(url) && (method === 'POST' || method === 'PUT')) return jsonResponse({ id: 24 }, true, 201);
if (url === '/api/routes/22' && method === 'GET') return jsonResponse({ id: 22, activatedAt: controls.approved ? '2026-10-03' : null, pois: [{ id: 23, audioUrl: '/audio', images: [{ id: 24 }] }] });
if (url === '/api/routes/22' && method === 'PUT') return jsonResponse({ id: 22 });
if (url === '/api/routes/22/pois/23' && method === 'PUT') return jsonResponse({ id: 23 });
if (/^\/api\/routes\/22\/pois\/23(?:\/audio|\/pictures\/24)?$/.test(url) && method === 'DELETE') return jsonResponse({ deleted: true });
if (url === '/api/routes/22' && method === 'DELETE') return jsonResponse({ deleted: true });
if (url === '/api/routes/1' && method === 'GET') return jsonResponse({ route: serverRoute });
if (url === '/api/routes/1' && method === 'DELETE') return jsonResponse({ deleted: true });
@@ -280,6 +290,7 @@ function buildEditorContext() {
const mediaBlobs = new Map();
const storedMediaCalls = [];
const LocalRoutes = {
creatorId: () => "a".repeat(64),
createPoiId: () => 9001,
createRouteId: () => 'local-new',
emptyGpx: name => `<gpx><metadata><name>${name}</name></metadata><trk><trkseg></trkseg></trk></gpx>`,
@@ -354,17 +365,25 @@ function buildEditorContext() {
Wegwichtel: {
App: { currentPosition: () => ({ lat: 50.123456789, lon: 10.987654321, accuracy: 4.321 }) },
Config: { apiBase: '/api/' },
RouteRecorder: {
beginEditing(id) { controls.recording = id; },
endEditing() { controls.recording = null; },
renderPoints() {},
editGpx: gpx => gpx,
get routeId() { return controls.recording; },
get position() { return { lat: 50.123456789, lon: 10.987654321, accuracy: 4.321 }; }
},
LocalRoutes
}
};
context.globalThis = context;
return { calls, context, elements, packages, saves, storedMediaCalls };
return { calls, context, elements, packages, saves, storedMediaCalls, controls };
}
test('public editor workflow saves new route and GPX Station locally without POSTing a route', async () => {
const sourcePath = path.join(root, 'public/js/editor-ui.js');
const source = await fs.readFile(sourcePath, 'utf8');
const { calls, context, elements, packages, saves } = buildEditorContext();
const { calls, context, elements, packages, saves, controls } = buildEditorContext();
await loadI18n(context);
@@ -390,6 +409,7 @@ test('public editor workflow saves new route and GPX Station locally without POS
assert.equal(calls.some(call => call.method === 'POST' && call.url === '/api/routes'), false);
assert.match(elements.get('editor-route-activated').textContent, /Dieser Entwurf bleibt auf deinem Gerät/);
assert.equal(elements.get('editor-status').textContent, 'Gespeichert.');
assert.equal(controls.recording, 'local-new');
assert.equal(elements.get('editor-route-upload').hidden, false);
assert.equal(elements.get('editor-route-upload').disabled, false);
assert.equal(elements.get('editor-route-gpx-group').hidden, true);
@@ -452,9 +472,35 @@ test('public editor workflow saves new route and GPX Station locally without POS
local = packages.get('local-new');
assert.equal(local.route.serverRouteId, '22');
assert.ok(local.route.uploadedAt);
assert.equal(elements.get('editor-route-upload').disabled, true);
assert.equal(elements.get('editor-route-upload').textContent, 'Schon zur Prüfung gesendet');
assert.equal(elements.get('editor-route-upload').disabled, false);
assert.equal(elements.get('editor-route-upload').textContent, 'Erneut zur Prüfung senden');
assert.equal(elements.get('editor-status').textContent, 'Zur Prüfung gesendet.');
elements.get('editor-route-upload').dispatchEvent(pointerEvent());
for (let i = 0; i < 10; i += 1) await tick();
assert.equal(calls.filter(call => call.method === 'POST' && call.url === '/api/routes').length, 1);
assert.equal(calls.filter(call => call.method === 'POST' && call.url === '/api/routes/22/pois').length, 1);
assert.equal(calls.filter(call => call.method === 'POST' && call.url === '/api/routes/22/pois/23/pictures').length, 1);
assert.ok(calls.some(call => call.method === 'PUT' && call.url === '/api/routes/22'));
assert.ok(calls.some(call => call.method === 'PUT' && call.url === '/api/routes/22/pois/23/audio'));
assert.ok(calls.some(call => call.method === 'PUT' && call.url === '/api/routes/22/pois/23/pictures/24'));
controls.failPicture = true;
elements.get('editor-route-upload').dispatchEvent(pointerEvent());
for (let i = 0; i < 10; i += 1) await tick();
assert.equal(packages.get('local-new').pois[0].serverPictureId, undefined);
assert.equal(packages.get('local-new').pois[0].images[0].serverPictureId, 24);
assert.equal(packages.get('local-new').route.serverRouteId, '22');
assert.equal(elements.get('editor-route-upload').disabled, false);
elements.get('editor-route-upload').dispatchEvent(pointerEvent());
for (let i = 0; i < 10; i += 1) await tick();
assert.equal(calls.filter(call => call.method === 'POST' && call.url === '/api/routes').length, 1);
controls.approved = true;
const writesBeforeApproval = calls.filter(call => call.method !== 'GET').length;
elements.get('editor-route-upload').dispatchEvent(pointerEvent());
for (let i = 0; i < 10; i += 1) await tick();
assert.equal(calls.filter(call => call.method !== 'GET').length, writesBeforeApproval);
controls.approved = false;
const poiList = elements.get('editor-poi-list');
const poiItem = poiList.children.find(child => child.dataset.poiId);
@@ -481,15 +527,28 @@ test('public editor workflow saves new route and GPX Station locally without POS
await tick();
local = packages.get('local-new');
assert.equal(local.pois[0].audio, null);
elements.get('editor-route-upload').dispatchEvent(pointerEvent());
for (let i = 0; i < 10; i += 1) await tick();
assert.ok(calls.some(call => call.method === 'DELETE' && call.url === '/api/routes/22/pois/23/audio'));
assert.ok(calls.some(call => call.method === 'DELETE' && call.url === '/api/routes/22/pois/23/pictures/24'));
const currentList = elements.get('editor-poi-list');
const currentItem = currentList.children.find(child => child.dataset.poiId);
currentList.dispatchEvent(pointerEvent(currentItem.children[1].children[2]));
assert.equal(elements.get('editor-poi-delete').hidden, false);
elements.get('editor-poi-delete').dispatchEvent(pointerEvent());
await tick();
await tick();
local = packages.get('local-new');
assert.equal(local.pois.length, 0);
elements.get('editor-route-upload').dispatchEvent(pointerEvent());
for (let i = 0; i < 10; i += 1) await tick();
assert.ok(calls.some(call => call.method === 'DELETE' && call.url === '/api/routes/22/pois/23'));
context.Wegwichtel.EditorUi.close();
assert.equal(controls.recording, null);
assert.equal(elements.get('editor-dialog').open, false);
assert.equal(elements.get('editor-button').attributes.get('aria-expanded'), 'false');
});