Files
DocsGPT/frontend/src/agents/workflow/codeNodeConfig.test.ts
T
Alex d573bab4a0 Add the code node to the workflow builder
Expose the workflow code node in the builder UI: a draggable palette entry, a
canvas node, and a config panel for the code, inputs, output variable, timeout,
and an optional JSON schema (validated before save). The panel serializes to the
same config the engine reads, so a code node built in the UI runs and produces an
artifact reference; workflows without code nodes load unchanged.
2026-06-24 15:47:49 +01:00

174 lines
4.7 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
createDefaultCodeConfig,
normalizeCodeConfig,
parseCodeInputs,
parseCodeJsonSchemaDraft,
serializeCodeConfig,
stringifyCodeInputs,
validateCodeJsonSchema,
} from './codeNodeConfig';
describe('validateCodeJsonSchema', () => {
it('treats undefined/null as valid (no schema)', () => {
expect(validateCodeJsonSchema(undefined)).toBeNull();
expect(validateCodeJsonSchema(null)).toBeNull();
});
it('rejects arrays and non-objects', () => {
expect(validateCodeJsonSchema([])).toMatch(/valid JSON object/);
expect(validateCodeJsonSchema('x')).toMatch(/valid JSON object/);
});
it('requires a type or schema field', () => {
expect(validateCodeJsonSchema({})).toMatch(/"type" or "schema"/);
expect(validateCodeJsonSchema({ type: 'object' })).toBeNull();
expect(validateCodeJsonSchema({ schema: {} })).toBeNull();
});
});
describe('parseCodeJsonSchemaDraft', () => {
it('returns undefined schema and no error for blank input', () => {
expect(parseCodeJsonSchemaDraft(' ')).toEqual({
schema: undefined,
error: null,
});
});
it('flags invalid JSON', () => {
const result = parseCodeJsonSchemaDraft('{ not json');
expect(result.schema).toBeUndefined();
expect(result.error).toMatch(/valid JSON/);
});
it('flags structurally invalid schema', () => {
const result = parseCodeJsonSchemaDraft('{"foo": 1}');
expect(result.schema).toBeUndefined();
expect(result.error).toMatch(/"type" or "schema"/);
});
it('parses a valid schema', () => {
const result = parseCodeJsonSchemaDraft(
'{"type":"object","properties":{}}',
);
expect(result.error).toBeNull();
expect(result.schema).toEqual({ type: 'object', properties: {} });
});
});
describe('inputs (de)serialization', () => {
it('splits on newlines and commas and trims', () => {
expect(parseCodeInputs('A1\nfoo, bar\n\n baz ')).toEqual([
'A1',
'foo',
'bar',
'baz',
]);
});
it('round-trips through stringify', () => {
const inputs = ['A1', 'state_var'];
expect(parseCodeInputs(stringifyCodeInputs(inputs))).toEqual(inputs);
});
it('stringifies undefined to empty string', () => {
expect(stringifyCodeInputs(undefined)).toBe('');
});
});
describe('createDefaultCodeConfig', () => {
it('returns python defaults with empty code and inputs', () => {
expect(createDefaultCodeConfig()).toEqual({
code: '',
language: 'python',
inputs: [],
});
});
});
describe('serializeCodeConfig', () => {
it('omits optional fields when empty', () => {
const serialized = serializeCodeConfig({
code: 'print(1)',
language: 'python',
inputs: ['', ' '],
output_variable: ' ',
});
expect(serialized).toEqual({
code: 'print(1)',
language: 'python',
inputs: [],
});
expect('output_variable' in serialized).toBe(false);
expect('timeout' in serialized).toBe(false);
expect('json_schema' in serialized).toBe(false);
});
it('keeps all CodeNodeConfig fields when present', () => {
const serialized = serializeCodeConfig({
code: 'print(1)',
language: 'python',
inputs: ['A1', 'state_var'],
output_variable: 'result',
timeout: 30,
json_schema: { type: 'object' },
});
expect(serialized).toEqual({
code: 'print(1)',
language: 'python',
inputs: ['A1', 'state_var'],
output_variable: 'result',
timeout: 30,
json_schema: { type: 'object' },
});
});
it('falls back to python defaults for an undefined config', () => {
expect(serializeCodeConfig(undefined)).toEqual({
code: '',
language: 'python',
inputs: [],
});
});
});
describe('normalizeCodeConfig', () => {
it('tolerates a missing/empty payload', () => {
expect(normalizeCodeConfig(undefined)).toEqual({
code: '',
language: 'python',
inputs: [],
});
});
it('coerces bad field types without throwing', () => {
const config = normalizeCodeConfig({
code: 42,
language: '',
inputs: ['A1', 5, 'B2'],
output_variable: '',
timeout: 'soon',
json_schema: [],
} as unknown as Record<string, unknown>);
expect(config).toEqual({
code: '',
language: 'python',
inputs: ['A1', 'B2'],
});
});
it('preserves a fully populated saved config (round-trip with serialize)', () => {
const saved = {
code: 'print(1)',
language: 'python',
inputs: ['A1'],
output_variable: 'result',
timeout: 30,
json_schema: { type: 'object' },
};
const normalized = normalizeCodeConfig(saved);
expect(serializeCodeConfig(normalized)).toEqual(saved);
});
});