busy-cli 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -0
- package/dist/builders/context.d.ts +50 -0
- package/dist/builders/context.d.ts.map +1 -0
- package/dist/builders/context.js +190 -0
- package/dist/cache/index.d.ts +100 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +270 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +463 -0
- package/dist/commands/package.d.ts +96 -0
- package/dist/commands/package.d.ts.map +1 -0
- package/dist/commands/package.js +285 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/loader.d.ts +6 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +361 -0
- package/dist/merge.d.ts +16 -0
- package/dist/merge.d.ts.map +1 -0
- package/dist/merge.js +102 -0
- package/dist/package/manifest.d.ts +59 -0
- package/dist/package/manifest.d.ts.map +1 -0
- package/dist/package/manifest.js +265 -0
- package/dist/parser.d.ts +28 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +220 -0
- package/dist/parsers/frontmatter.d.ts +14 -0
- package/dist/parsers/frontmatter.d.ts.map +1 -0
- package/dist/parsers/frontmatter.js +110 -0
- package/dist/parsers/imports.d.ts +48 -0
- package/dist/parsers/imports.d.ts.map +1 -0
- package/dist/parsers/imports.js +147 -0
- package/dist/parsers/links.d.ts +12 -0
- package/dist/parsers/links.d.ts.map +1 -0
- package/dist/parsers/links.js +79 -0
- package/dist/parsers/localdefs.d.ts +6 -0
- package/dist/parsers/localdefs.d.ts.map +1 -0
- package/dist/parsers/localdefs.js +132 -0
- package/dist/parsers/operations.d.ts +32 -0
- package/dist/parsers/operations.d.ts.map +1 -0
- package/dist/parsers/operations.js +313 -0
- package/dist/parsers/sections.d.ts +15 -0
- package/dist/parsers/sections.d.ts.map +1 -0
- package/dist/parsers/sections.js +173 -0
- package/dist/parsers/tools.d.ts +30 -0
- package/dist/parsers/tools.d.ts.map +1 -0
- package/dist/parsers/tools.js +178 -0
- package/dist/parsers/triggers.d.ts +35 -0
- package/dist/parsers/triggers.d.ts.map +1 -0
- package/dist/parsers/triggers.js +219 -0
- package/dist/providers/base.d.ts +60 -0
- package/dist/providers/base.d.ts.map +1 -0
- package/dist/providers/base.js +34 -0
- package/dist/providers/github.d.ts +18 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +109 -0
- package/dist/providers/gitlab.d.ts +18 -0
- package/dist/providers/gitlab.d.ts.map +1 -0
- package/dist/providers/gitlab.js +101 -0
- package/dist/providers/index.d.ts +13 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +17 -0
- package/dist/providers/local.d.ts +31 -0
- package/dist/providers/local.d.ts.map +1 -0
- package/dist/providers/local.js +116 -0
- package/dist/providers/url.d.ts +16 -0
- package/dist/providers/url.d.ts.map +1 -0
- package/dist/providers/url.js +45 -0
- package/dist/registry/index.d.ts +99 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +320 -0
- package/dist/types/schema.d.ts +3259 -0
- package/dist/types/schema.d.ts.map +1 -0
- package/dist/types/schema.js +258 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +23 -0
- package/dist/utils/slugify.d.ts +14 -0
- package/dist/utils/slugify.d.ts.map +1 -0
- package/dist/utils/slugify.js +28 -0
- package/package.json +61 -0
- package/src/__tests__/cache.test.ts +393 -0
- package/src/__tests__/cli-package.test.ts +667 -0
- package/src/__tests__/fixtures/automated-workflow.busy.md +84 -0
- package/src/__tests__/fixtures/concept.busy.md +30 -0
- package/src/__tests__/fixtures/document.busy.md +44 -0
- package/src/__tests__/fixtures/simple-operation.busy.md +45 -0
- package/src/__tests__/fixtures/tool-document.busy.md +71 -0
- package/src/__tests__/fixtures/tool.busy.md +54 -0
- package/src/__tests__/imports.test.ts +244 -0
- package/src/__tests__/integration.test.ts +432 -0
- package/src/__tests__/operations.test.ts +408 -0
- package/src/__tests__/package-manifest.test.ts +455 -0
- package/src/__tests__/providers.test.ts +672 -0
- package/src/__tests__/registry.test.ts +402 -0
- package/src/__tests__/schema.test.ts +467 -0
- package/src/__tests__/tools.test.ts +376 -0
- package/src/__tests__/triggers.test.ts +312 -0
- package/src/builders/context.ts +294 -0
- package/src/cache/index.ts +312 -0
- package/src/cli/index.ts +514 -0
- package/src/commands/package.ts +392 -0
- package/src/index.ts +46 -0
- package/src/loader.ts +474 -0
- package/src/merge.ts +126 -0
- package/src/package/manifest.ts +349 -0
- package/src/parser.ts +278 -0
- package/src/parsers/frontmatter.ts +135 -0
- package/src/parsers/imports.ts +196 -0
- package/src/parsers/links.ts +108 -0
- package/src/parsers/localdefs.ts +166 -0
- package/src/parsers/operations.ts +404 -0
- package/src/parsers/sections.ts +230 -0
- package/src/parsers/tools.ts +215 -0
- package/src/parsers/triggers.ts +252 -0
- package/src/providers/base.ts +77 -0
- package/src/providers/github.ts +129 -0
- package/src/providers/gitlab.ts +121 -0
- package/src/providers/index.ts +25 -0
- package/src/providers/local.ts +129 -0
- package/src/providers/url.ts +56 -0
- package/src/registry/index.ts +408 -0
- package/src/types/schema.ts +369 -0
- package/src/utils/logger.ts +25 -0
- package/src/utils/slugify.ts +31 -0
- package/tsconfig.json +21 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration Tests - Full document parsing matching busy-python behavior
|
|
3
|
+
*
|
|
4
|
+
* These tests verify the complete parsing pipeline produces
|
|
5
|
+
* output matching busy-python's parse_document() function.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeAll } from 'vitest';
|
|
9
|
+
import { readFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { parseDocument, resolveImports } from '../parser';
|
|
12
|
+
import type { BusyDocument, ToolDocument, Import } from '../types/schema';
|
|
13
|
+
|
|
14
|
+
const FIXTURES_DIR = join(__dirname, 'fixtures');
|
|
15
|
+
|
|
16
|
+
function loadFixture(name: string): string {
|
|
17
|
+
return readFileSync(join(FIXTURES_DIR, name), 'utf-8');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('parseDocument', () => {
|
|
21
|
+
describe('Basic Document Parsing', () => {
|
|
22
|
+
it('should parse document metadata', () => {
|
|
23
|
+
const content = loadFixture('document.busy.md');
|
|
24
|
+
const doc = parseDocument(content);
|
|
25
|
+
|
|
26
|
+
expect(doc.metadata.name).toBe('Document');
|
|
27
|
+
expect(doc.metadata.type).toBe('[Document]');
|
|
28
|
+
expect(doc.metadata.description).toBe('Base document type for all BUSY documents.');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should parse imports with concept names and paths', () => {
|
|
32
|
+
const content = loadFixture('document.busy.md');
|
|
33
|
+
const doc = parseDocument(content);
|
|
34
|
+
|
|
35
|
+
expect(doc.imports).toHaveLength(1);
|
|
36
|
+
expect(doc.imports[0].conceptName).toBe('Concept');
|
|
37
|
+
expect(doc.imports[0].path).toBe('./concept.busy.md');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should parse local definitions', () => {
|
|
41
|
+
const content = loadFixture('document.busy.md');
|
|
42
|
+
const doc = parseDocument(content);
|
|
43
|
+
|
|
44
|
+
expect(doc.definitions.length).toBeGreaterThan(0);
|
|
45
|
+
const importsDef = doc.definitions.find((d) => d.name === 'Imports Section');
|
|
46
|
+
expect(importsDef).toBeDefined();
|
|
47
|
+
expect(importsDef?.content).toContain('external document references');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should parse setup section', () => {
|
|
51
|
+
const content = loadFixture('document.busy.md');
|
|
52
|
+
const doc = parseDocument(content);
|
|
53
|
+
|
|
54
|
+
expect(doc.setup).toBeDefined();
|
|
55
|
+
expect(doc.setup).toContain('evaluated before any operations');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should parse operations with structured steps', () => {
|
|
59
|
+
const content = loadFixture('document.busy.md');
|
|
60
|
+
const doc = parseDocument(content);
|
|
61
|
+
|
|
62
|
+
expect(doc.operations).toHaveLength(1);
|
|
63
|
+
const op = doc.operations[0];
|
|
64
|
+
|
|
65
|
+
expect(op.name).toBe('EvaluateDocument');
|
|
66
|
+
expect(op.steps).toHaveLength(4);
|
|
67
|
+
expect(op.steps[0].stepNumber).toBe(1);
|
|
68
|
+
expect(op.steps[0].instruction).toBe('Parse frontmatter for metadata');
|
|
69
|
+
expect(op.checklist?.items).toHaveLength(4);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('Document with Triggers', () => {
|
|
74
|
+
it('should parse triggers from section', () => {
|
|
75
|
+
const content = loadFixture('automated-workflow.busy.md');
|
|
76
|
+
const doc = parseDocument(content);
|
|
77
|
+
|
|
78
|
+
expect(doc.triggers.length).toBeGreaterThan(0);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should parse alarm trigger with schedule', () => {
|
|
82
|
+
const content = loadFixture('automated-workflow.busy.md');
|
|
83
|
+
const doc = parseDocument(content);
|
|
84
|
+
|
|
85
|
+
const alarmTrigger = doc.triggers.find((t) => t.triggerType === 'alarm');
|
|
86
|
+
expect(alarmTrigger).toBeDefined();
|
|
87
|
+
expect(alarmTrigger?.operation).toBe('DailyReport');
|
|
88
|
+
expect(alarmTrigger?.schedule).toBe('0 6 * * *');
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should parse event trigger with filter', () => {
|
|
92
|
+
const content = loadFixture('automated-workflow.busy.md');
|
|
93
|
+
const doc = parseDocument(content);
|
|
94
|
+
|
|
95
|
+
const eventTrigger = doc.triggers.find(
|
|
96
|
+
(t) => t.triggerType === 'event' && t.eventType === 'data.received'
|
|
97
|
+
);
|
|
98
|
+
expect(eventTrigger).toBeDefined();
|
|
99
|
+
expect(eventTrigger?.filter).toEqual({ from: '*@trusted.com' });
|
|
100
|
+
expect(eventTrigger?.operation).toBe('ProcessIncoming');
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should parse event trigger without filter', () => {
|
|
104
|
+
const content = loadFixture('automated-workflow.busy.md');
|
|
105
|
+
const doc = parseDocument(content);
|
|
106
|
+
|
|
107
|
+
const webhookTrigger = doc.triggers.find(
|
|
108
|
+
(t) => t.eventType === 'webhook.triggered'
|
|
109
|
+
);
|
|
110
|
+
expect(webhookTrigger).toBeDefined();
|
|
111
|
+
expect(webhookTrigger?.filter).toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('Document with Operation References', () => {
|
|
116
|
+
it('should extract operation references from steps', () => {
|
|
117
|
+
const content = loadFixture('automated-workflow.busy.md');
|
|
118
|
+
const doc = parseDocument(content);
|
|
119
|
+
|
|
120
|
+
const processOp = doc.operations.find((o) => o.name === 'ProcessIncoming');
|
|
121
|
+
expect(processOp).toBeDefined();
|
|
122
|
+
|
|
123
|
+
// Step 1: "Run [ValidateInput] on the payload"
|
|
124
|
+
expect(processOp?.steps[0].operationReferences).toContain('ValidateInput');
|
|
125
|
+
|
|
126
|
+
// Step 2: "Apply processing rules from [ProcessingRule]"
|
|
127
|
+
expect(processOp?.steps[1].operationReferences).toContain('ProcessingRule');
|
|
128
|
+
|
|
129
|
+
// Step 3: "Execute [ProcessData] with validated input"
|
|
130
|
+
expect(processOp?.steps[2].operationReferences).toContain('ProcessData');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('Tool Document Parsing', () => {
|
|
135
|
+
it('should return ToolDocument for Type: [Tool]', () => {
|
|
136
|
+
const content = loadFixture('tool.busy.md');
|
|
137
|
+
const doc = parseDocument(content);
|
|
138
|
+
|
|
139
|
+
// Type guard to check if it's a ToolDocument
|
|
140
|
+
expect('tools' in doc).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should parse provider from metadata', () => {
|
|
144
|
+
const content = loadFixture('tool.busy.md');
|
|
145
|
+
const doc = parseDocument(content);
|
|
146
|
+
|
|
147
|
+
expect(doc.metadata.provider).toBe('composio');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should parse tools with providers', () => {
|
|
151
|
+
const content = loadFixture('tool.busy.md');
|
|
152
|
+
const doc = parseDocument(content) as ToolDocument;
|
|
153
|
+
|
|
154
|
+
expect(doc.tools).toHaveLength(2);
|
|
155
|
+
|
|
156
|
+
const sendEmail = doc.tools.find((t) => t.name === 'send_email');
|
|
157
|
+
expect(sendEmail).toBeDefined();
|
|
158
|
+
expect(sendEmail?.providers?.composio?.action).toBe('GMAIL_SEND_EMAIL');
|
|
159
|
+
expect(sendEmail?.providers?.composio?.parameters).toEqual({
|
|
160
|
+
to: 'to',
|
|
161
|
+
subject: 'subject',
|
|
162
|
+
body: 'body',
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should parse tool inputs and outputs', () => {
|
|
167
|
+
const content = loadFixture('tool.busy.md');
|
|
168
|
+
const doc = parseDocument(content) as ToolDocument;
|
|
169
|
+
|
|
170
|
+
const sendEmail = doc.tools.find((t) => t.name === 'send_email');
|
|
171
|
+
expect(sendEmail?.inputs).toHaveLength(3);
|
|
172
|
+
expect(sendEmail?.outputs).toHaveLength(2);
|
|
173
|
+
expect(sendEmail?.inputs[0]).toBe('to: Recipient email address');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe('Edge Cases', () => {
|
|
178
|
+
it('should handle document with no imports', () => {
|
|
179
|
+
const content = `
|
|
180
|
+
---
|
|
181
|
+
Name: NoImports
|
|
182
|
+
Type: [Document]
|
|
183
|
+
Description: Document without imports
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
# [Setup]
|
|
187
|
+
|
|
188
|
+
No imports needed.
|
|
189
|
+
|
|
190
|
+
# [Operations]
|
|
191
|
+
|
|
192
|
+
## SimpleOp
|
|
193
|
+
|
|
194
|
+
### [Steps]
|
|
195
|
+
1. Do something
|
|
196
|
+
`;
|
|
197
|
+
|
|
198
|
+
const doc = parseDocument(content);
|
|
199
|
+
expect(doc.imports).toHaveLength(0);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should handle document with no operations', () => {
|
|
203
|
+
const content = `
|
|
204
|
+
---
|
|
205
|
+
Name: NoOps
|
|
206
|
+
Type: [Document]
|
|
207
|
+
Description: Document without operations
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
# [Local Definitions]
|
|
211
|
+
|
|
212
|
+
## SomeDefinition
|
|
213
|
+
|
|
214
|
+
Just a definition.
|
|
215
|
+
`;
|
|
216
|
+
|
|
217
|
+
const doc = parseDocument(content);
|
|
218
|
+
expect(doc.operations).toHaveLength(0);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should handle document with no setup', () => {
|
|
222
|
+
const content = `
|
|
223
|
+
---
|
|
224
|
+
Name: NoSetup
|
|
225
|
+
Type: [Document]
|
|
226
|
+
Description: Document without setup
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
# [Operations]
|
|
230
|
+
|
|
231
|
+
## SomeOp
|
|
232
|
+
|
|
233
|
+
### [Steps]
|
|
234
|
+
1. Do it
|
|
235
|
+
`;
|
|
236
|
+
|
|
237
|
+
const doc = parseDocument(content);
|
|
238
|
+
expect(doc.setup).toBeUndefined();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it('should handle missing frontmatter gracefully', () => {
|
|
242
|
+
const content = `
|
|
243
|
+
# Just Markdown
|
|
244
|
+
|
|
245
|
+
No frontmatter here.
|
|
246
|
+
`;
|
|
247
|
+
|
|
248
|
+
expect(() => parseDocument(content)).toThrow();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should handle empty document', () => {
|
|
252
|
+
const content = `
|
|
253
|
+
---
|
|
254
|
+
Name: Empty
|
|
255
|
+
Type: [Document]
|
|
256
|
+
Description: Empty document
|
|
257
|
+
---
|
|
258
|
+
`;
|
|
259
|
+
|
|
260
|
+
const doc = parseDocument(content);
|
|
261
|
+
expect(doc.metadata.name).toBe('Empty');
|
|
262
|
+
expect(doc.imports).toHaveLength(0);
|
|
263
|
+
expect(doc.definitions).toHaveLength(0);
|
|
264
|
+
expect(doc.operations).toHaveLength(0);
|
|
265
|
+
expect(doc.triggers).toHaveLength(0);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('resolveImports', () => {
|
|
271
|
+
it('should resolve import paths to document IDs', () => {
|
|
272
|
+
const doc: BusyDocument = {
|
|
273
|
+
metadata: {
|
|
274
|
+
name: 'Test',
|
|
275
|
+
type: '[Document]',
|
|
276
|
+
description: 'Test',
|
|
277
|
+
},
|
|
278
|
+
imports: [
|
|
279
|
+
{ conceptName: 'Concept', path: './concept.busy.md' },
|
|
280
|
+
{ conceptName: 'Operation', path: './operation.busy.md' },
|
|
281
|
+
],
|
|
282
|
+
definitions: [],
|
|
283
|
+
operations: [],
|
|
284
|
+
triggers: [],
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
const basePath = FIXTURES_DIR;
|
|
288
|
+
// This would resolve imports against actual files
|
|
289
|
+
// For now, just verify the function signature
|
|
290
|
+
expect(typeof resolveImports).toBe('function');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('should detect circular imports', async () => {
|
|
294
|
+
// Circular import detection test
|
|
295
|
+
// doc-a imports doc-b, doc-b imports doc-a
|
|
296
|
+
const docA = `
|
|
297
|
+
---
|
|
298
|
+
Name: DocA
|
|
299
|
+
Type: [Document]
|
|
300
|
+
Description: A
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
[DocB]: ./doc-b.busy.md
|
|
304
|
+
`;
|
|
305
|
+
|
|
306
|
+
const docB = `
|
|
307
|
+
---
|
|
308
|
+
Name: DocB
|
|
309
|
+
Type: [Document]
|
|
310
|
+
Description: B
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
[DocA]: ./doc-a.busy.md
|
|
314
|
+
`;
|
|
315
|
+
|
|
316
|
+
// Would need actual file system or mock for this test
|
|
317
|
+
// The test documents the expected behavior
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('should validate anchor references exist in target', () => {
|
|
321
|
+
// Anchor validation test
|
|
322
|
+
const doc: BusyDocument = {
|
|
323
|
+
metadata: {
|
|
324
|
+
name: 'Test',
|
|
325
|
+
type: '[Document]',
|
|
326
|
+
description: 'Test',
|
|
327
|
+
},
|
|
328
|
+
imports: [
|
|
329
|
+
{ conceptName: 'NonExistent', path: './file.busy.md', anchor: 'nonexistent-anchor' },
|
|
330
|
+
],
|
|
331
|
+
definitions: [],
|
|
332
|
+
operations: [],
|
|
333
|
+
triggers: [],
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// Should throw BusyImportError when anchor doesn't exist
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
describe('Output Format Compatibility', () => {
|
|
341
|
+
it('should produce output matching busy-python structure', () => {
|
|
342
|
+
const content = loadFixture('document.busy.md');
|
|
343
|
+
const doc = parseDocument(content);
|
|
344
|
+
|
|
345
|
+
// Verify structure matches busy-python BusyDocument
|
|
346
|
+
expect(doc).toHaveProperty('metadata');
|
|
347
|
+
expect(doc).toHaveProperty('imports');
|
|
348
|
+
expect(doc).toHaveProperty('definitions');
|
|
349
|
+
expect(doc).toHaveProperty('setup');
|
|
350
|
+
expect(doc).toHaveProperty('operations');
|
|
351
|
+
expect(doc).toHaveProperty('triggers');
|
|
352
|
+
|
|
353
|
+
// Metadata structure
|
|
354
|
+
expect(doc.metadata).toHaveProperty('name');
|
|
355
|
+
expect(doc.metadata).toHaveProperty('type');
|
|
356
|
+
expect(doc.metadata).toHaveProperty('description');
|
|
357
|
+
|
|
358
|
+
// Import structure
|
|
359
|
+
if (doc.imports.length > 0) {
|
|
360
|
+
expect(doc.imports[0]).toHaveProperty('conceptName');
|
|
361
|
+
expect(doc.imports[0]).toHaveProperty('path');
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Operation structure
|
|
365
|
+
if (doc.operations.length > 0) {
|
|
366
|
+
expect(doc.operations[0]).toHaveProperty('name');
|
|
367
|
+
expect(doc.operations[0]).toHaveProperty('inputs');
|
|
368
|
+
expect(doc.operations[0]).toHaveProperty('outputs');
|
|
369
|
+
expect(doc.operations[0]).toHaveProperty('steps');
|
|
370
|
+
|
|
371
|
+
// Step structure
|
|
372
|
+
if (doc.operations[0].steps.length > 0) {
|
|
373
|
+
expect(doc.operations[0].steps[0]).toHaveProperty('stepNumber');
|
|
374
|
+
expect(doc.operations[0].steps[0]).toHaveProperty('instruction');
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('should serialize to JSON matching busy-python output', () => {
|
|
380
|
+
const content = loadFixture('document.busy.md');
|
|
381
|
+
const doc = parseDocument(content);
|
|
382
|
+
|
|
383
|
+
// Should be serializable to JSON
|
|
384
|
+
const json = JSON.stringify(doc);
|
|
385
|
+
const parsed = JSON.parse(json);
|
|
386
|
+
|
|
387
|
+
expect(parsed.metadata.name).toBe(doc.metadata.name);
|
|
388
|
+
expect(parsed.imports).toEqual(doc.imports);
|
|
389
|
+
expect(parsed.operations.length).toBe(doc.operations.length);
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
describe('Frontmatter Parsing', () => {
|
|
394
|
+
it('should handle Type as bracketed string', () => {
|
|
395
|
+
const content = `
|
|
396
|
+
---
|
|
397
|
+
Name: Test
|
|
398
|
+
Type: [Document]
|
|
399
|
+
Description: Test
|
|
400
|
+
---
|
|
401
|
+
`;
|
|
402
|
+
|
|
403
|
+
const doc = parseDocument(content);
|
|
404
|
+
expect(doc.metadata.type).toBe('[Document]');
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('should NOT include Extends or Tags (busy-python removed these)', () => {
|
|
408
|
+
const content = `
|
|
409
|
+
---
|
|
410
|
+
Name: Test
|
|
411
|
+
Type: [Document]
|
|
412
|
+
Description: Test
|
|
413
|
+
Extends:
|
|
414
|
+
- Parent
|
|
415
|
+
Tags:
|
|
416
|
+
- tag1
|
|
417
|
+
- tag2
|
|
418
|
+
---
|
|
419
|
+
`;
|
|
420
|
+
|
|
421
|
+
const doc = parseDocument(content);
|
|
422
|
+
expect(doc.metadata).not.toHaveProperty('extends');
|
|
423
|
+
expect(doc.metadata).not.toHaveProperty('tags');
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('should include Provider for tool documents', () => {
|
|
427
|
+
const content = loadFixture('tool.busy.md');
|
|
428
|
+
const doc = parseDocument(content);
|
|
429
|
+
|
|
430
|
+
expect(doc.metadata.provider).toBe('composio');
|
|
431
|
+
});
|
|
432
|
+
});
|