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
270 lines
7.2 KiB
TypeScript
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' },
|
|
],
|
|
);
|
|
});
|