[glm-grade=A] fix(config): teach schema KNOWN_KEYS the licenseBackup/_version writer keys (DC-091)
Every startup logged two false-positive 'Unknown config key — possible typo?' warns: licenseBackup (written by src/managers/license-manager.js:510 activation persistence) and _version (stamped by src/config/migrations.js). Both are first-party writers the validator was never taught about (DC-091). - config-schema.js: add both keys to KNOWN_KEYS with a source-of-writes comment - config-schema.test.js (new): 5 regression tests — live production config keyset validates with zero unknown-key warnings, writer keys never warn, genuine typos still warn (exact string), license/licenseBackup sync guard, _version recognized at every migration value Verified: full jest suite 111 suites / 2621 tests green (baseline 110/2616). Warns reproduced in live container logs 2026-08-22T23:53:54Z; live config.json contains both keys (licenseBackup activation, _version 2). Judge: GLM-5.3 cold read via delegate_task (deleg_30e52384, 36s) — grade A, ship. Verdict URN: urn:ump:ermkvz6ifbp5svga5cnapv5jhm7c5b7qdbwjjerfpxbwcrolm2za (readback verified) Codex quota-walled until 2026-08-29; GLM stand-in per Sami 2026-08-17 directive.
This commit is contained in:
@@ -0,0 +1,72 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regression tests for config-schema.js KNOWN_KEYS — DC-091.
|
||||||
|
*
|
||||||
|
* Bug: license-manager.js persists config.licenseBackup (activation
|
||||||
|
* restore-on-restart) and src/config/migrations.js stamps config._version,
|
||||||
|
* but neither key was in KNOWN_KEYS — so every startup logged
|
||||||
|
* `Unknown config key "licenseBackup" / "_version" — possible typo?`
|
||||||
|
* false positives (verified in live dashcaddy-api container logs,
|
||||||
|
* 2026-08-22T23:53:54Z restart).
|
||||||
|
*
|
||||||
|
* These tests pin: (1) the live production config key set validates with
|
||||||
|
* zero unknown-key warnings, (2) genuine typos still warn, (3) the schema
|
||||||
|
* stays in sync with the first-party writer keys.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { validateConfig } = require('../src/utilities/config-schema');
|
||||||
|
|
||||||
|
describe('config-schema KNOWN_KEYS vs first-party writers (DC-091)', () => {
|
||||||
|
// Exact key set of the live production config.json (DNS2, verified
|
||||||
|
// 2026-08-23). If a new key appears here, teach KNOWN_KEYS about it —
|
||||||
|
// or fix the writer if it's a typo.
|
||||||
|
const LIVE_CONFIG_KEYS = [
|
||||||
|
'_version', 'configurationType', 'customFavicon', 'customLogo',
|
||||||
|
'dashboardHost', 'dashboardTitle', 'dns', 'dnsServers', 'language',
|
||||||
|
'license', 'licenseBackup', 'logoPosition', 'pylon', 'setupComplete',
|
||||||
|
'timestamp', 'tld', 'updatedAt'
|
||||||
|
];
|
||||||
|
|
||||||
|
test('live production config key set produces zero unknown-key warnings', () => {
|
||||||
|
const config = {};
|
||||||
|
for (const key of LIVE_CONFIG_KEYS) {
|
||||||
|
// Minimal valid-ish values; validateConfig only cares about shape
|
||||||
|
// for these keys, and unknown-key detection is the target here.
|
||||||
|
config[key] = key === '_version' ? 2 : (key === 'dnsServers' ? {} : 'x');
|
||||||
|
}
|
||||||
|
const result = validateConfig(config);
|
||||||
|
const unknownWarnings = result.warnings.filter((w) => w.includes('Unknown config key'));
|
||||||
|
expect(unknownWarnings).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('licenseBackup and _version (first-party writer keys) do not warn', () => {
|
||||||
|
const result = validateConfig({ licenseBackup: { code: 'DC-...' }, _version: 2 });
|
||||||
|
expect(result.warnings).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('genuine typos still warn (guard against over-allowing)', () => {
|
||||||
|
const result = validateConfig({ dashboadTitle: 'typo' });
|
||||||
|
expect(result.warnings).toEqual([
|
||||||
|
'Unknown config key "dashboadTitle" — possible typo?'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('KNOWN_KEYS stays in sync with license-manager writer keys', () => {
|
||||||
|
// license-manager writes config.licenseBackup and config.license — both
|
||||||
|
// must be recognized. We assert via validateConfig (public surface)
|
||||||
|
// rather than importing the private KNOWN_KEYS array.
|
||||||
|
const result = validateConfig({ license: { code: 'DC-...' }, licenseBackup: { code: 'DC-...' } });
|
||||||
|
expect(result.warnings.filter((w) => w.includes('Unknown config key'))).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('config-schema sync guard: migrations writer', () => {
|
||||||
|
test('_version is recognized at every migration version value', () => {
|
||||||
|
// migrations.js bumps _version 0→1→2; the key itself must never warn.
|
||||||
|
for (const v of [0, 1, 2, 99]) {
|
||||||
|
const result = validateConfig({ _version: v });
|
||||||
|
expect(result.warnings).toEqual([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -14,7 +14,11 @@ const KNOWN_KEYS = [
|
|||||||
'configurationType', 'defaults', 'customLogo', 'customFavicon',
|
'configurationType', 'defaults', 'customLogo', 'customFavicon',
|
||||||
'dashboardTitle', 'tailscale', 'license', 'skipped',
|
'dashboardTitle', 'tailscale', 'license', 'skipped',
|
||||||
'routingMode', 'domain', 'email', 'defaultIP', 'pylon',
|
'routingMode', 'domain', 'email', 'defaultIP', 'pylon',
|
||||||
'customLogoDark', 'customLogoLight', 'language'
|
'customLogoDark', 'customLogoLight', 'language',
|
||||||
|
// license-manager.js persists the last activation to config.licenseBackup
|
||||||
|
// (restore-on-restart path); src/config/migrations.js stamps _version.
|
||||||
|
// Both are first-party writes — see DC-091.
|
||||||
|
'licenseBackup', '_version'
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user