mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-06 04:14:19 +00:00
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.
174 lines
4.7 KiB
TypeScript
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);
|
|
});
|
|
});
|