-
Notifications
You must be signed in to change notification settings - Fork 3.8k
Expand file tree
/
Copy pathbuild-config.test.ts
More file actions
153 lines (138 loc) · 5.72 KB
/
Copy pathbuild-config.test.ts
File metadata and controls
153 lines (138 loc) · 5.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import type { WorkflowInputField } from '@/lib/workflows/input-format'
import {
buildCustomBlockConfig,
CUSTOM_BLOCK_TILE_COLOR,
type CustomBlockRow,
isCustomBlockType,
isReservedOutputName,
} from '@/blocks/custom/build-config'
import type { BlockIcon } from '@/blocks/types'
const icon: BlockIcon = () => null as never
const row: CustomBlockRow = {
type: 'custom_block_abc123',
name: 'Invoice Parser',
description: 'Extracts fields from an invoice',
workflowId: 'wf-1',
}
function findSub(config: ReturnType<typeof buildCustomBlockConfig>, id: string) {
return config.subBlocks.find((s) => s.id === id)
}
describe('isCustomBlockType', () => {
it('matches only the custom_block_ prefix', () => {
expect(isCustomBlockType('custom_block_abc')).toBe(true)
expect(isCustomBlockType('agent')).toBe(false)
expect(isCustomBlockType(undefined)).toBe(false)
expect(isCustomBlockType(null)).toBe(false)
})
})
describe('isReservedOutputName', () => {
it('rejects the system output fields case-insensitively', () => {
expect(isReservedOutputName('cost')).toBe(true)
expect(isReservedOutputName('Cost')).toBe(true)
expect(isReservedOutputName(' success ')).toBe(true)
expect(isReservedOutputName('error')).toBe(true)
expect(isReservedOutputName('result')).toBe(false)
expect(isReservedOutputName('cost_2')).toBe(false)
expect(isReservedOutputName('summary')).toBe(false)
})
})
describe('buildCustomBlockConfig', () => {
const fields: WorkflowInputField[] = [
{ name: 'title', type: 'string' },
{ name: 'count', type: 'number' },
{ name: 'flag', type: 'boolean' },
{ name: 'payload', type: 'object' },
{ name: 'items', type: 'array' },
{ name: 'docs', type: 'file[]' },
]
it('carries the row identity and always wires the workflow_executor tool', () => {
const config = buildCustomBlockConfig(row, fields, { icon })
expect(config.type).toBe('custom_block_abc123')
expect(config.name).toBe('Invoice Parser')
expect(config.sourceWorkflowId).toBe('wf-1')
expect(config.category).toBe('tools')
expect(config.bgColor).toBe(CUSTOM_BLOCK_TILE_COLOR)
expect(config.hideFromToolbar).toBeUndefined()
expect(config.tools.access).toEqual(['workflow_executor'])
expect(config.tools.config?.tool({})).toBe('workflow_executor')
})
it('hides a disabled block from the toolbar while keeping it resolvable', () => {
expect(buildCustomBlockConfig(row, fields, { icon }).hideFromToolbar).toBeUndefined()
expect(
buildCustomBlockConfig(row, fields, { icon, hideFromToolbar: true }).hideFromToolbar
).toBe(true)
})
it('bakes the bound workflowId as a hidden sub-block', () => {
const config = buildCustomBlockConfig(row, fields, { icon })
const wf = findSub(config, 'workflowId')
expect(wf?.hidden).toBe(true)
expect(wf?.value?.({})).toBe('wf-1')
})
it('maps each input field type to the right sub-block', () => {
const config = buildCustomBlockConfig(row, fields, { icon })
expect(findSub(config, 'title')?.type).toBe('short-input')
expect(findSub(config, 'count')?.type).toBe('short-input')
expect(findSub(config, 'flag')?.type).toBe('switch')
expect(findSub(config, 'payload')?.type).toBe('code')
expect(findSub(config, 'payload')?.language).toBe('json')
expect(findSub(config, 'items')?.type).toBe('code')
expect(findSub(config, 'docs')?.type).toBe('file-upload')
expect(findSub(config, 'docs')?.multiple).toBe(true)
})
it('advertises no data fields — and no whole-result fallback — without curation', () => {
const config = buildCustomBlockConfig(row, fields, { icon })
// Curation is required at publish, so an uncurated row exposes only the
// system fields. `result` must not come back: it would advertise the child's
// raw terminal state (agent toolCalls/thinking, nested workflow ids).
expect(Object.keys(config.outputs).sort()).toEqual([
'error',
'errorRef',
'errorType',
'success',
])
expect(config.outputs.result).toBeUndefined()
expect(config.outputs.childWorkflowId).toBeUndefined()
expect(config.outputs.childTraceSpans).toBeUndefined()
})
it('exposes only curated outputs as named fields', () => {
const config = buildCustomBlockConfig(
{ ...row, exposedOutputs: [{ blockId: 'b1', path: 'content', name: 'email' }] },
fields,
{ icon }
)
expect(config.outputs.email).toEqual({ type: 'json', description: 'Output: content' })
expect(config.outputs.result).toBeUndefined()
expect(config.outputs.success).toBeDefined()
expect(config.outputs.childWorkflowId).toBeUndefined()
})
it('anchors the sub-block on the stable field id, showing the name as title', () => {
const config = buildCustomBlockConfig(row, [{ id: 'fld-1', name: 'title', type: 'string' }], {
icon,
})
const sub = findSub(config, 'fld-1')
expect(sub).toBeDefined()
expect(sub?.title).toBe('title')
expect(findSub(config, 'title')).toBeUndefined()
})
it('falls back to the field name as id when a field has no stable id', () => {
const config = buildCustomBlockConfig(row, [{ name: 'legacy', type: 'string' }], { icon })
expect(findSub(config, 'legacy')?.title).toBe('legacy')
})
it('assembles inputMapping from non-reserved, non-empty params', () => {
const config = buildCustomBlockConfig(row, fields, { icon })
const mappingFn = findSub(config, 'inputMapping')?.value
const json = mappingFn?.({
workflowId: 'wf-1',
inputMapping: 'ignored',
triggerMode: true,
title: 'Acme',
count: 3,
empty: '',
})
expect(JSON.parse(json as string)).toEqual({ title: 'Acme', count: 3 })
})
})