Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
253 lines
7.7 KiB
TypeScript
253 lines
7.7 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import type { Host, Snippet } from './models';
|
|
import { getScriptApiReference } from './scriptApiReference.ts';
|
|
import {
|
|
applySnippetAgentPatch,
|
|
applySnippetCreateToVault,
|
|
applySnippetUpdateToVault,
|
|
buildSnippetFromAgentDraft,
|
|
deleteSnippetFromVault,
|
|
filterScriptSnippets,
|
|
serializeSnippetForAgentGet,
|
|
setHostConnectScriptIds,
|
|
} from './snippetAgentOps.ts';
|
|
|
|
const host: Host = {
|
|
id: 'host-a',
|
|
label: 'A',
|
|
hostname: 'a.example',
|
|
username: 'root',
|
|
os: 'linux',
|
|
protocol: 'ssh',
|
|
tags: [],
|
|
};
|
|
|
|
test('getScriptApiReference includes nct API and wrapper rules', () => {
|
|
const ref = getScriptApiReference();
|
|
assert.match(ref, /nct\.screen\.waitForPrompt/);
|
|
assert.match(ref, /nct\.session\.name/);
|
|
assert.match(ref, /Only JavaScript is executed/);
|
|
assert.match(ref, /onConnect/);
|
|
});
|
|
|
|
test('filterScriptSnippets returns only script kind', () => {
|
|
const snippets: Snippet[] = [
|
|
{ id: '1', label: 'cmd', command: 'ls', kind: 'snippet' },
|
|
{ id: '2', label: 'auto', command: 'nct.log(1)', kind: 'script' },
|
|
];
|
|
assert.deepEqual(filterScriptSnippets(snippets).map((s) => s.id), ['2']);
|
|
});
|
|
|
|
test('serializeSnippetForAgentGet includes script metadata', () => {
|
|
const snippet: Snippet = {
|
|
id: 's1',
|
|
label: 'Deploy',
|
|
command: 'await main();',
|
|
kind: 'script',
|
|
trigger: 'onConnect',
|
|
targets: ['host-a'],
|
|
targetGroups: ['Production'],
|
|
language: 'javascript',
|
|
};
|
|
const serialized = serializeSnippetForAgentGet(snippet);
|
|
assert.equal(serialized.kind, 'script');
|
|
assert.equal(serialized.trigger, 'onConnect');
|
|
assert.deepEqual(serialized.targets, ['host-a']);
|
|
assert.deepEqual(serialized.targetGroups, ['Production']);
|
|
});
|
|
|
|
test('agent serialization preserves missing and explicit empty group scopes', () => {
|
|
const unscoped = serializeSnippetForAgentGet({
|
|
id: 'legacy',
|
|
label: 'Legacy',
|
|
command: 'nct.log(1)',
|
|
kind: 'script',
|
|
trigger: 'onOutput',
|
|
triggerPattern: 'READY',
|
|
});
|
|
const disabled = serializeSnippetForAgentGet({
|
|
id: 'disabled',
|
|
label: 'Disabled',
|
|
command: 'nct.log(1)',
|
|
kind: 'script',
|
|
trigger: 'onOutput',
|
|
triggerPattern: 'READY',
|
|
targetGroups: [],
|
|
});
|
|
|
|
assert.equal(unscoped.targetGroups, undefined);
|
|
assert.deepEqual(disabled.targetGroups, []);
|
|
});
|
|
|
|
test('buildSnippetFromAgentDraft validates onOutput triggerPattern', () => {
|
|
const bad = buildSnippetFromAgentDraft(
|
|
{ label: 'x', command: 'nct.log(1)', trigger: 'onOutput' },
|
|
[],
|
|
{ forceKind: 'script' },
|
|
);
|
|
assert.equal(bad.ok, false);
|
|
if (bad.ok) return;
|
|
assert.match(bad.error, /triggerPattern/);
|
|
|
|
const good = buildSnippetFromAgentDraft(
|
|
{ label: 'x', command: 'nct.log(1)', trigger: 'onOutput', triggerPattern: 'ERROR' },
|
|
[],
|
|
{ forceKind: 'script' },
|
|
);
|
|
assert.equal(good.ok, true);
|
|
});
|
|
|
|
test('buildSnippetFromAgentDraft accepts multi-line run mode', () => {
|
|
const result = buildSnippetFromAgentDraft(
|
|
{ label: 'login', command: 'user\npass', multiLineRunMode: 'lineDelay' },
|
|
[],
|
|
);
|
|
assert.equal(result.ok, true);
|
|
if (!result.ok) return;
|
|
assert.equal(result.snippet.multiLineRunMode, 'lineDelay');
|
|
});
|
|
|
|
test('deleteSnippetFromVault removes snippet and host connect references', () => {
|
|
const snippets: Snippet[] = [
|
|
{
|
|
id: 'run',
|
|
label: 'Run',
|
|
command: 'nct.log(1)',
|
|
kind: 'script',
|
|
trigger: 'onConnect',
|
|
targets: ['host-a'],
|
|
},
|
|
];
|
|
const hosts = [{ ...host, connectScriptIds: ['run'] }];
|
|
const result = deleteSnippetFromVault(snippets, hosts, 'run');
|
|
if ('error' in result) {
|
|
assert.fail(result.error);
|
|
}
|
|
assert.equal(result.snippets.length, 0);
|
|
assert.deepEqual(result.hosts[0].connectScriptIds, []);
|
|
});
|
|
|
|
test('applySnippetCreateToVault syncs onConnect targets to host queue', () => {
|
|
const snippet: Snippet = {
|
|
id: 'run',
|
|
label: 'Run',
|
|
command: 'nct.log(1)',
|
|
kind: 'script',
|
|
trigger: 'onConnect',
|
|
targets: ['host-a'],
|
|
};
|
|
const result = applySnippetCreateToVault([], [host], snippet);
|
|
assert.deepEqual(result.hosts[0].connectScriptIds, ['run']);
|
|
});
|
|
|
|
test('applySnippetUpdateToVault syncs target changes', () => {
|
|
const snippet: Snippet = {
|
|
id: 'run',
|
|
label: 'Run',
|
|
command: 'nct.log(1)',
|
|
kind: 'script',
|
|
trigger: 'onConnect',
|
|
targets: ['host-a'],
|
|
};
|
|
const created = applySnippetCreateToVault([snippet], [host], snippet);
|
|
const updatedSnippet = { ...snippet, targets: [] as string[] | undefined, targetsAllHosts: true };
|
|
const updated = applySnippetUpdateToVault(created.snippets, created.hosts, updatedSnippet, snippet, ['host-a']);
|
|
assert.equal(updated.hosts[0].connectScriptIds?.includes('run'), false);
|
|
});
|
|
|
|
test('applySnippetUpdateToVault clears host queue when onConnect is demoted', () => {
|
|
const snippet: Snippet = {
|
|
id: 'run',
|
|
label: 'Run',
|
|
command: 'nct.log(1)',
|
|
kind: 'script',
|
|
trigger: 'onConnect',
|
|
targets: ['host-a'],
|
|
};
|
|
const host: Host = {
|
|
id: 'host-a',
|
|
label: 'A',
|
|
hostname: 'a.example',
|
|
username: 'root',
|
|
os: 'linux',
|
|
protocol: 'ssh',
|
|
tags: [],
|
|
connectScriptIds: ['run'],
|
|
};
|
|
const created = applySnippetCreateToVault([snippet], [host], snippet);
|
|
const manual = { ...snippet, trigger: 'manual' as const };
|
|
const updated = applySnippetUpdateToVault(created.snippets, created.hosts, manual, snippet);
|
|
assert.equal(updated.hosts[0].connectScriptIds?.includes('run'), false);
|
|
});
|
|
|
|
test('applySnippetAgentPatch clears targetsAllHosts when explicit targets are set', () => {
|
|
const existing: Snippet = {
|
|
id: 'run',
|
|
label: 'Run',
|
|
command: 'nct.log(1)',
|
|
kind: 'script',
|
|
trigger: 'onConnect',
|
|
targetsAllHosts: true,
|
|
};
|
|
const patched = applySnippetAgentPatch(existing, { targets: ['host-a'] });
|
|
assert.equal(patched.ok, true);
|
|
if (!patched.ok) return;
|
|
assert.equal(patched.snippet.targetsAllHosts, undefined);
|
|
assert.deepEqual(patched.snippet.targets, ['host-a']);
|
|
});
|
|
|
|
test('applySnippetAgentPatch supports dynamic groups alongside explicit hosts', () => {
|
|
const existing: Snippet = {
|
|
id: 'run',
|
|
label: 'Run',
|
|
command: 'nct.log(1)',
|
|
kind: 'script',
|
|
trigger: 'onConnect',
|
|
targets: ['host-a'],
|
|
};
|
|
const patched = applySnippetAgentPatch(existing, { targetGroups: ['Production'] });
|
|
assert.equal(patched.ok, true);
|
|
if (!patched.ok) return;
|
|
assert.deepEqual(patched.snippet.targets, ['host-a']);
|
|
assert.deepEqual(patched.snippet.targetGroups, ['Production']);
|
|
});
|
|
|
|
test('agent group targets normalize paths and preserve an explicit empty scope', () => {
|
|
const built = buildSnippetFromAgentDraft({
|
|
label: 'Run',
|
|
command: 'nct.log(1)',
|
|
kind: 'script',
|
|
targetGroups: [' Production\\Web ', 'Production//Web'],
|
|
}, []);
|
|
assert.equal(built.ok, true);
|
|
if (!built.ok) return;
|
|
assert.deepEqual(built.snippet.targetGroups, ['Production/Web']);
|
|
|
|
const patched = applySnippetAgentPatch(built.snippet, { targetGroups: [] });
|
|
assert.equal(patched.ok, true);
|
|
if (!patched.ok) return;
|
|
assert.deepEqual(patched.snippet.targetGroups, []);
|
|
});
|
|
|
|
test('applySnippetAgentPatch updates multi-line run mode', () => {
|
|
const existing: Snippet = {
|
|
id: 'login',
|
|
label: 'Login',
|
|
command: 'user\npass',
|
|
multiLineRunMode: 'lineDelay',
|
|
};
|
|
const patched = applySnippetAgentPatch(existing, { multiLineRunMode: 'paste' });
|
|
assert.equal(patched.ok, true);
|
|
if (!patched.ok) return;
|
|
assert.equal(patched.snippet.multiLineRunMode, 'paste');
|
|
});
|
|
|
|
test('setHostConnectScriptIds rejects non-onConnect scripts', () => {
|
|
const snippets: Snippet[] = [
|
|
{ id: 'manual', label: 'M', command: 'x', kind: 'script', trigger: 'manual' },
|
|
];
|
|
const result = setHostConnectScriptIds(host, ['manual'], snippets);
|
|
assert.equal(result.ok, false);
|
|
});
|