update editor and add tests

This commit is contained in:
Florian Zumpe
2026-09-26 20:40:05 +02:00
parent bdc0aabc07
commit 96b379d8d6
14 changed files with 2099 additions and 587 deletions
+35 -52
View File
@@ -9,58 +9,41 @@ async function read(relativePath) {
return fs.readFile(path.join(root, relativePath), 'utf8');
}
function normalizedTokens(line) {
return line
.trim()
.replaceAll(',', ' ')
.replaceAll(';', ' ')
.replaceAll('(', ' ')
.replaceAll(')', ' ')
.split(' ')
.map(token => token.trim().toUpperCase())
.filter(Boolean);
}
function routeTableLines(schema) {
const lines = schema.split('\n');
const start = lines.findIndex(line => normalizedTokens(line).join(' ') === 'CREATE TABLE IF NOT EXISTS ROUTES');
assert.notEqual(start, -1, 'database/schema.sql must define the routes table');
const end = lines.findIndex((line, index) => index > start && normalizedTokens(line).includes('STRICT'));
assert.notEqual(end, -1, 'the routes table must be declared STRICT');
return lines.slice(start, end + 1);
}
function hasActivatedAtTextDefaultNull(lines) {
return lines.some(line => {
const tokens = normalizedTokens(line);
return tokens[0] === 'ACTIVATED_AT'
&& tokens[1] === 'TEXT'
&& tokens.includes('DEFAULT')
&& tokens.includes('NULL');
});
}
function containsTokenSequence(sql, sequence) {
const tokens = sql
.split('\n')
.flatMap(normalizedTokens);
return tokens.some((_, index) => sequence.every((token, offset) => tokens[index + offset] === token));
}
test('initial database schema creates routes with a nullable activation timestamp', async () => {
test('fresh routes schema stores timestamps as STRICT-compatible TEXT values', async () => {
const schema = await read('database/schema.sql');
const migration = await read('database/migrations/20260901_add_route_activated_at.sql');
const routes = routeTableLines(schema);
const routesTable = schema.match(/CREATE TABLE IF NOT EXISTS routes\s*\((?<body>[\s\S]*?)\)\s*STRICT\s*;/i);
assert.equal(hasActivatedAtTextDefaultNull(routes), true);
assert.equal(containsTokenSequence(routes.join('\n'), ['ACTIVATED_AT', 'DATETIME']), false);
assert.equal(containsTokenSequence(routes.join('\n'), ['ON', 'UPDATE', 'CURRENT_TIMESTAMP']), false);
assert.equal(containsTokenSequence(schema, ['CREATE', 'INDEX', 'IF', 'NOT', 'EXISTS', 'IDX_ROUTES_ACTIVATION', 'ON', 'ROUTES']), true);
assert.equal(containsTokenSequence(schema, ['CREATE', 'TRIGGER', 'IF', 'NOT', 'EXISTS', 'POIS_UPDATED_AT']), true);
assert.equal(containsTokenSequence(migration, ['ALTER', 'TABLE', 'ROUTES', 'ADD', 'COLUMN', 'ACTIVATED_AT', 'TEXT']), true);
assert.equal(containsTokenSequence(migration, ['CREATE', 'INDEX', 'IF', 'NOT', 'EXISTS', 'IDX_ROUTES_ACTIVATION', 'ON', 'ROUTES']), true);
assert.equal(containsTokenSequence(migration, ['ACTIVATED_AT', 'DATETIME']), false);
assert.equal(migration.includes('activated_at is NULL'), true);
assert.ok(routesTable?.groups?.body, 'routes must be a STRICT table');
const body = routesTable.groups.body;
assert.match(body, /created_at\s+TEXT\s+NOT NULL\s+DEFAULT\s+CURRENT_TIMESTAMP/i);
assert.match(body, /updated_at\s+TEXT\s+NOT NULL\s+DEFAULT\s+CURRENT_TIMESTAMP/i);
assert.match(body, /deleted_at\s+TEXT\s+DEFAULT\s+NULL/i);
assert.match(body, /activated_at\s+TEXT\s+DEFAULT\s+NULL/i);
assert.doesNotMatch(body, /activated_at\s+DATETIME/i);
assert.doesNotMatch(body, /ON\s+UPDATE\s+CURRENT_TIMESTAMP/i);
});
test('fresh schema owns timestamp triggers and activation index', async () => {
const schema = await read('database/schema.sql');
assert.match(schema, /CREATE TRIGGER IF NOT EXISTS routes_updated_at/i);
assert.match(schema, /AFTER UPDATE ON routes/i);
assert.match(schema, /SET updated_at\s*=\s*CURRENT_TIMESTAMP/i);
assert.match(schema, /CREATE TRIGGER IF NOT EXISTS pois_updated_at/i);
assert.match(schema, /AFTER UPDATE ON pois/i);
assert.match(schema, /CREATE INDEX IF NOT EXISTS idx_routes_activation\s+ON routes\s*\(\s*status\s*,\s*activated_at\s*\)/i);
});
test('public route listing can require activated routes without changing base service semantics', async () => {
const service = await read('src/services/routes-service.js');
const api = await read('src/routes/api.js');
const client = await read('public/js/api-client.js');
assert.match(service, /publishedOnly\s*=\s*false/);
assert.match(service, /activated_at\s+IS\s+NOT\s+NULL/i);
assert.match(service, /activatedAt:\s*row\.activated_at/);
assert.match(api, /publishedOnly:\s*req\.query\.activatedOnly\s*===\s*['"]true['"]/);
assert.match(client, /activatedOnly:\s*['"]true['"]/);
});