mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-17 11:42:40 -06:00
The non-security gaps named in the public-API self-review: - gap-fix: zone_id (playlist items) + layout_id (device PUT) accepted and returned on read, INCLUDING the cross-tenant rejection (the is_template OR workspace_id guard - the security-relevant one). - docs serving: /openapi.yaml serves the spec, /docs returns the Redoc page. - i18n drift-guard: apitoken.* keys have full parity across en/es/fr/de/pt (a key missing in one locale fails CI). - token lifecycle branches: token-create workspace-membership validation and last_used_at stamping (integration), plus the must_change_password gate (unit test via the in-memory DB injection - cross-process WAL visibility is unreliable for that branch in-process). 119 tests total (was 108), all in the existing node --test job. Closes #92 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
31 lines
1.4 KiB
JavaScript
31 lines
1.4 KiB
JavaScript
'use strict';
|
|
|
|
// i18n drift-guard for the public-API token UI: the apitoken.* keys must have full parity
|
|
// across all five locales. A key added to en (or any locale) without the others fails CI,
|
|
// so the Settings "API Tokens" UI can't ship a missing translation. No server needed.
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
const LOCALES = ['en', 'es', 'fr', 'de', 'pt'];
|
|
const I18N_DIR = path.join(__dirname, '..', '..', 'frontend', 'js', 'i18n');
|
|
|
|
function apitokenKeys(locale) {
|
|
const text = fs.readFileSync(path.join(I18N_DIR, locale + '.js'), 'utf8');
|
|
return new Set((text.match(/['"]apitoken\.[a-z_]+['"]/g) || []).map(s => s.replace(/['"]/g, '')));
|
|
}
|
|
|
|
test('i18n: apitoken.* keys have full parity across all 5 locales (drift fails CI)', () => {
|
|
const base = apitokenKeys('en');
|
|
assert.ok(base.size >= 20, `en should define the apitoken keys (found ${base.size})`);
|
|
for (const loc of LOCALES) {
|
|
const keys = apitokenKeys(loc);
|
|
const missing = [...base].filter(k => !keys.has(k));
|
|
const extra = [...keys].filter(k => !base.has(k));
|
|
assert.deepEqual(missing, [], `${loc}.js is missing apitoken keys present in en: ${missing.join(', ')}`);
|
|
assert.deepEqual(extra, [], `${loc}.js has apitoken keys not in en: ${extra.join(', ')}`);
|
|
}
|
|
});
|