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
+33 -3
View File
@@ -4,6 +4,7 @@ import fs from 'node:fs/promises';
import path from 'node:path';
import test from 'node:test';
import vm from 'node:vm';
import { webcrypto } from 'node:crypto';
const root = path.resolve(import.meta.dirname, '..');
const GPX_NAMESPACE = 'http://www.topografix.com/GPX/1/1';
@@ -322,7 +323,7 @@ class FakeFileReader extends EventTarget {
async function loadLocalRoutes(options = {}) {
const sourcePath = path.join(root, 'public/js/local-routes.js');
const source = await fs.readFile(sourcePath, 'utf8');
const localStorage = new FakeStorage();
const localStorage = options.localStorage || new FakeStorage();
const indexedDB = new FakeIndexedDB();
const document = new EventTarget();
const events = [];
@@ -336,7 +337,7 @@ async function loadLocalRoutes(options = {}) {
EventTarget,
FileReader: FakeFileReader,
XMLSerializer: FakeXMLSerializer,
crypto: options.crypto || { randomUUID: () => '11111111-2222-4333-8444-555555555555' },
crypto: options.crypto || { randomUUID: () => '11111111-2222-4333-8444-555555555555', getRandomValues: bytes => webcrypto.getRandomValues(bytes) },
document,
globalThis: null,
indexedDB,
@@ -348,7 +349,7 @@ async function loadLocalRoutes(options = {}) {
context.globalThis = context;
await loadI18n(context);
vm.runInNewContext(source, context, { filename: sourcePath });
return { api: context.Wegwichtel.LocalRoutes, events, indexedDB, localStorage };
return { api: context.Wegwichtel.LocalRoutes, events, indexedDB, localStorage, context };
}
const sampleGpx = `<?xml version="1.0" encoding="UTF-8"?>
@@ -700,3 +701,32 @@ test('multiple Stationen with large local media keep Local Storage metadata smal
assert.doesNotMatch(metadata, /data:image/);
assert.equal(api.get(routeId).pois.length, 2);
});
test('256-bit creator identity survives reload, route deletion and requires explicit confirmation to reset', async () => {
const first = await loadLocalRoutes();
const identity = first.api.creatorId();
assert.match(identity, /^[a-f0-9]{64}$/);
const second = await loadLocalRoutes({ localStorage: first.localStorage });
assert.equal(second.api.creatorId(), identity);
const saved = second.api.save({ id: 'identity-test', route: { name: 'Test' }, pois: [] });
assert.equal(saved.route.creator_id, identity);
await second.api.remove('identity-test');
assert.equal(second.api.creatorId(), identity);
second.context.confirm = () => false;
assert.equal(second.api.forgetCreatorId(), false);
assert.equal(second.api.creatorId(), identity);
second.context.confirm = () => true;
assert.equal(second.api.forgetCreatorId(), true);
assert.notEqual(second.api.creatorId(), identity);
const other = await loadLocalRoutes();
assert.notEqual(other.api.creatorId(), identity);
});
test('creator identity never falls back to insecure randomness or replaces corrupt stored identity', async () => {
const { api, localStorage } = await loadLocalRoutes({ crypto: {} });
assert.throws(() => api.creatorId(), /Gerätekennung/);
localStorage.setItem('wegwichtel.creator-id.v1', 'broken');
assert.throws(() => api.creatorId(), /Gerätekennung/);
assert.equal(localStorage.getItem('wegwichtel.creator-id.v1'), 'broken');
});