mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-08 06:13:18 +00:00
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.
This commit is contained in:
1 parent
e7f765b628
commit
d573bab4a0
8 files changed
+673
-10
No files matched your search
@@ -0,0 +1,173 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user