98 lines
4.3 KiB
JavaScript
98 lines
4.3 KiB
JavaScript
// tests/kb-update/test-template-generation.test.mjs
|
|
// Structural-regex tests for scripts/kb-update/templates/* (Step 8).
|
|
// Verifies that each template file exists, contains the documented sentinel
|
|
// strings, and exposes the documented placeholder set. No template execution
|
|
// or real scheduling occurs in this test — that lives in Wave 6 live-test.
|
|
|
|
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { readFileSync, existsSync } from 'node:fs';
|
|
import { join, dirname } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const TEMPLATES_DIR = join(__dirname, '..', '..', 'scripts', 'kb-update', 'templates');
|
|
|
|
const PLIST = join(TEMPLATES_DIR, 'com.fromaitochitta.ms-ai-architect.kb-update.plist');
|
|
const SERVICE = join(TEMPLATES_DIR, 'ms-ai-architect-kb-update.service');
|
|
const TIMER = join(TEMPLATES_DIR, 'ms-ai-architect-kb-update.timer');
|
|
const PS1 = join(TEMPLATES_DIR, 'ms-ai-architect-kb-update.ps1');
|
|
const README = join(TEMPLATES_DIR, 'README.md');
|
|
|
|
function readTpl(p) {
|
|
assert.equal(existsSync(p), true, `template missing: ${p}`);
|
|
return readFileSync(p, 'utf8');
|
|
}
|
|
|
|
test('plist — exists with required keys and placeholders', () => {
|
|
const content = readTpl(PLIST);
|
|
assert.match(content, /<key>Label<\/key>/);
|
|
assert.match(content, /<key>StartCalendarInterval<\/key>/);
|
|
assert.match(content, /<key>ProgramArguments<\/key>/);
|
|
assert.match(content, /<key>StandardOutPath<\/key>/);
|
|
assert.match(content, /<key>StandardErrorPath<\/key>/);
|
|
assert.match(content, /<key>EnvironmentVariables<\/key>/);
|
|
assert.match(content, /<key>RunAtLoad<\/key>\s*<false\/>/);
|
|
assert.match(content, /\{\{NODE_BIN\}\}/);
|
|
assert.match(content, /\{\{PLUGIN_ROOT\}\}/);
|
|
assert.match(content, /\{\{LOG_FILE\}\}/);
|
|
assert.match(content, /\{\{SCHEDULE_HOUR\}\}/);
|
|
assert.match(content, /\{\{SCHEDULE_MINUTE\}\}/);
|
|
assert.match(content, /\{\{SCHEDULE_DAY_OF_WEEK\}\}/);
|
|
});
|
|
|
|
test('systemd .timer — exists with OnCalendar and Persistent', () => {
|
|
const content = readTpl(TIMER);
|
|
assert.match(content, /\[Unit\]/);
|
|
assert.match(content, /\[Timer\]/);
|
|
assert.match(content, /\[Install\]/);
|
|
assert.match(content, /OnCalendar=Wed/);
|
|
assert.match(content, /Persistent=true/);
|
|
assert.match(content, /WantedBy=timers\.target/);
|
|
});
|
|
|
|
test('systemd .service — exists with [Unit], [Service] and ExecStart', () => {
|
|
const content = readTpl(SERVICE);
|
|
assert.match(content, /\[Unit\]/);
|
|
assert.match(content, /\[Service\]/);
|
|
assert.match(content, /ExecStart=/);
|
|
assert.match(content, /\{\{NODE_BIN\}\}/);
|
|
assert.match(content, /\{\{PLUGIN_ROOT\}\}/);
|
|
});
|
|
|
|
test('PowerShell ps1 — exists with Register-ScheduledTask and InteractiveToken', () => {
|
|
const content = readTpl(PS1);
|
|
assert.match(content, /Register-ScheduledTask/);
|
|
assert.match(content, /InteractiveToken/);
|
|
assert.match(content, /New-ScheduledTaskTrigger/);
|
|
assert.match(content, /-Weekly/);
|
|
assert.match(content, /-DaysOfWeek\s+Wednesday/);
|
|
assert.match(content, /\{\{NODE_BIN\}\}/);
|
|
assert.match(content, /\{\{PLUGIN_ROOT\}\}/);
|
|
});
|
|
|
|
test('README — exists and references each template by filename', () => {
|
|
const content = readTpl(README);
|
|
assert.match(content, /com\.fromaitochitta\.ms-ai-architect\.kb-update\.plist/);
|
|
assert.match(content, /ms-ai-architect-kb-update\.service/);
|
|
assert.match(content, /ms-ai-architect-kb-update\.timer/);
|
|
assert.match(content, /ms-ai-architect-kb-update\.ps1/);
|
|
});
|
|
|
|
test('plist + service + ps1 reference NODE_BIN and PLUGIN_ROOT', () => {
|
|
// The .timer is a pure trigger — it activates the .service, which is
|
|
// the only systemd unit that needs to know the binary + plugin root.
|
|
// launchd and Windows put the command directly in the trigger spec, so
|
|
// they need both placeholders themselves.
|
|
for (const tpl of [PLIST, SERVICE, PS1]) {
|
|
const content = readFileSync(tpl, 'utf8');
|
|
assert.match(content, /\{\{NODE_BIN\}\}/, `${tpl} missing NODE_BIN placeholder`);
|
|
assert.match(content, /\{\{PLUGIN_ROOT\}\}/, `${tpl} missing PLUGIN_ROOT placeholder`);
|
|
}
|
|
});
|
|
|
|
test('.timer is placeholder-free literal (Wed 04:23 hardcoded per plan)', () => {
|
|
const content = readFileSync(TIMER, 'utf8');
|
|
assert.match(content, /OnCalendar=Wed \*-\*-\* 04:23:00/);
|
|
assert.doesNotMatch(content, /\{\{[A-Z_]+\}\}/);
|
|
});
|