update editor and add tests
This commit is contained in:
@@ -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['"]/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user