Files
NetMesh/domain/codebuddyElicitationForm.test.ts
zhaolei 3c72efcb7f
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
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

270 lines
7.2 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {
buildCodebuddyElicitationContent,
initialCodebuddyElicitationValues,
parseCodebuddyElicitationFields,
selectedCodebuddyOptionKey,
toggleCodebuddyArrayOption,
validateCodebuddyElicitationValues,
} from './codebuddyElicitationForm';
test('CodeBuddy elicitation options preserve typed enum and const values', () => {
const fields = parseCodebuddyElicitationFields({
type: 'object',
properties: {
retries: {
type: 'integer',
enum: [1, 2],
default: 1,
},
enabled: {
type: 'boolean',
oneOf: [
{ const: true, title: 'Enabled' },
{ const: false, title: 'Disabled' },
],
},
mixed: {
type: 'array',
items: {
enum: [1, '1', false],
},
},
},
});
assert.equal(fields[0].options[1].value, 2);
assert.equal(fields[1].options[1].value, false);
assert.equal(selectedCodebuddyOptionKey(fields[0].options, 1), 'enum:0');
assert.deepEqual(initialCodebuddyElicitationValues(fields), { retries: 1 });
let mixed: unknown[] = [];
mixed = toggleCodebuddyArrayOption(mixed, 1, true);
mixed = toggleCodebuddyArrayOption(mixed, '1', true);
mixed = toggleCodebuddyArrayOption(mixed, false, true);
mixed = toggleCodebuddyArrayOption(mixed, '1', true);
assert.deepEqual(mixed, [1, '1', false]);
assert.deepEqual(buildCodebuddyElicitationContent({
retries: 2,
enabled: false,
mixed,
omitted: undefined,
}), {
retries: 2,
enabled: false,
mixed: [1, '1', false],
});
});
test('CodeBuddy elicitation content excludes stale fields from a replaced schema', () => {
const fields = parseCodebuddyElicitationFields({
type: 'object',
properties: {
current: { type: 'string' },
},
});
assert.deepEqual(buildCodebuddyElicitationContent({
stale: 'must not leak',
current: 'kept',
omitted: undefined,
}, fields), {
current: 'kept',
});
});
test('CodeBuddy elicitation validation enforces numeric and array constraints', () => {
const fields = parseCodebuddyElicitationFields({
type: 'object',
properties: {
retries: {
type: 'integer',
title: 'Retries',
minimum: 10,
maximum: 20,
},
choices: {
type: 'array',
title: 'Choices',
minItems: 2,
maxItems: 2,
items: { enum: ['a', 'b', 'c'] },
},
},
required: ['retries', 'choices'],
});
assert.deepEqual(
validateCodebuddyElicitationValues(fields, {
retries: 1,
choices: ['a'],
}).map(({ fieldId, code, limit }) => ({ fieldId, code, limit })),
[
{ fieldId: 'retries', code: 'minimum', limit: 10 },
{ fieldId: 'choices', code: 'minItems', limit: 2 },
],
);
assert.deepEqual(validateCodebuddyElicitationValues(fields, {
retries: 10.5,
choices: ['a', 'b', 'c'],
}).map(({ fieldId, code, limit }) => ({ fieldId, code, limit })), [
{ fieldId: 'retries', code: 'notInteger', limit: undefined },
{ fieldId: 'choices', code: 'maxItems', limit: 2 },
]);
assert.deepEqual(validateCodebuddyElicitationValues(fields, {
retries: 12,
choices: ['a', 'b'],
}), []);
});
test('CodeBuddy elicitation validation reports notInteger for fractional numeric input', () => {
const fields = parseCodebuddyElicitationFields({
type: 'object',
properties: {
retries: {
type: 'integer',
title: 'Retries',
minimum: 2,
maximum: 5,
},
},
required: ['retries'],
});
const issues = validateCodebuddyElicitationValues(fields, { retries: 3.5 });
assert.equal(issues.length, 1);
assert.equal(issues[0].code, 'notInteger');
assert.equal(issues[0].fieldId, 'retries');
assert.equal(issues[0].fieldTitle, 'Retries');
});
test('CodeBuddy elicitation validation enforces string lengths, formats, and options', () => {
const fields = parseCodebuddyElicitationFields({
type: 'object',
properties: {
name: {
type: 'string',
minLength: 2,
maxLength: 3,
},
email: {
type: 'string',
format: 'email',
},
date: {
type: 'string',
format: 'date',
},
dateTime: {
type: 'string',
format: 'date-time',
},
environment: {
type: 'string',
enum: ['staging', 'production'],
},
optional: {
type: 'string',
default: null,
},
},
required: ['name', 'email', 'date', 'dateTime', 'environment'],
});
assert.deepEqual(initialCodebuddyElicitationValues(fields), {});
assert.deepEqual(
validateCodebuddyElicitationValues(fields, {
name: 'a',
email: 'not-an-email',
date: '2026-02-30',
dateTime: '2026-02-30T12:00:00Z',
environment: 'unknown',
}).map(({ fieldId, code }) => ({ fieldId, code })),
[
{ fieldId: 'name', code: 'minLength' },
{ fieldId: 'email', code: 'format' },
{ fieldId: 'date', code: 'format' },
{ fieldId: 'dateTime', code: 'format' },
{ fieldId: 'environment', code: 'option' },
],
);
assert.deepEqual(validateCodebuddyElicitationValues(fields, {
name: '猫猫',
email: 'cat@example.com',
date: '2026-07-27',
dateTime: '2026-07-27T15:30:00+08:00',
environment: 'staging',
}), []);
});
test('CodeBuddy elicitation validation follows standard email and date-time boundaries', () => {
const fields = parseCodebuddyElicitationFields({
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
},
dateTime: {
type: 'string',
format: 'date-time',
},
},
required: ['email', 'dateTime'],
});
assert.deepEqual(validateCodebuddyElicitationValues(fields, {
email: 'cat+alerts@example.com',
dateTime: '2026-07-27t15:30:00z',
}), []);
assert.deepEqual(validateCodebuddyElicitationValues(fields, {
email: 'cat@example.com',
dateTime: '1990-12-31T23:59:60Z',
}), []);
assert.deepEqual(
validateCodebuddyElicitationValues(fields, {
email: 'cat@example..com',
dateTime: '2026-07-27T15:30:00Z',
}).map(({ fieldId, code }) => ({ fieldId, code })),
[{ fieldId: 'email', code: 'format' }],
);
});
test('CodeBuddy required fields allow empty values unless size constraints reject them', () => {
const fields = parseCodebuddyElicitationFields({
type: 'object',
properties: {
note: { type: 'string' },
choices: {
type: 'array',
items: { enum: ['a', 'b'] },
},
constrainedNote: {
type: 'string',
minLength: 1,
},
constrainedChoices: {
type: 'array',
minItems: 1,
items: { enum: ['a', 'b'] },
},
},
required: ['note', 'choices', 'constrainedNote', 'constrainedChoices'],
});
assert.deepEqual(
validateCodebuddyElicitationValues(fields, {
note: '',
choices: [],
constrainedNote: '',
constrainedChoices: [],
}).map(({ fieldId, code }) => ({ fieldId, code })),
[
{ fieldId: 'constrainedNote', code: 'minLength' },
{ fieldId: 'constrainedChoices', code: 'minItems' },
],
);
});