77 registerMetadataTools ,
88 METADATA_TOOL_DEFINITIONS ,
99} from '../tools/metadata-tools.js' ;
10+ import {
11+ registerDataTools ,
12+ } from '../tools/data-tools.js' ;
1013import type { MetadataToolContext } from '../tools/metadata-tools.js' ;
1114
1215// ── Helpers ────────────────────────────────────────────────────────
@@ -52,8 +55,8 @@ describe('Metadata Tool Definitions', () => {
5255 'add_field' ,
5356 'modify_field' ,
5457 'delete_field' ,
55- 'list_objects ' ,
56- 'describe_object ' ,
58+ 'list_metadata_objects ' ,
59+ 'describe_metadata_object ' ,
5760 ] ) ;
5861 } ) ;
5962
@@ -85,8 +88,45 @@ describe('registerMetadataTools', () => {
8588 expect ( registry . has ( 'add_field' ) ) . toBe ( true ) ;
8689 expect ( registry . has ( 'modify_field' ) ) . toBe ( true ) ;
8790 expect ( registry . has ( 'delete_field' ) ) . toBe ( true ) ;
91+ expect ( registry . has ( 'list_metadata_objects' ) ) . toBe ( true ) ;
92+ expect ( registry . has ( 'describe_metadata_object' ) ) . toBe ( true ) ;
93+ } ) ;
94+ } ) ;
95+
96+ // ═══════════════════════════════════════════════════════════════════
97+ // Dual registration (data tools + metadata tools)
98+ // ═══════════════════════════════════════════════════════════════════
99+
100+ describe ( 'registerDataTools + registerMetadataTools — no collision' , ( ) => {
101+ it ( 'should register both tool sets on the same registry without overwriting' , ( ) => {
102+ const registry = new ToolRegistry ( ) ;
103+ const metadataService = createMockMetadataService ( ) ;
104+ const dataEngine = {
105+ find : vi . fn ( ) ,
106+ findOne : vi . fn ( ) ,
107+ aggregate : vi . fn ( ) ,
108+ } as any ;
109+
110+ registerDataTools ( registry , { dataEngine, metadataService } ) ;
111+ const sizeAfterData = registry . size ;
112+
113+ registerMetadataTools ( registry , { metadataService } ) ;
114+ const sizeAfterBoth = registry . size ;
115+
116+ // Data tools define: list_objects, describe_object, query_records, get_record, aggregate_data
117+ // Metadata tools define: create_object, add_field, modify_field, delete_field, list_metadata_objects, describe_metadata_object
118+ // No overlap — total should be sum of both
119+ expect ( sizeAfterBoth ) . toBe ( sizeAfterData + 6 ) ;
120+
121+ // Data tools should still be present
88122 expect ( registry . has ( 'list_objects' ) ) . toBe ( true ) ;
89123 expect ( registry . has ( 'describe_object' ) ) . toBe ( true ) ;
124+ expect ( registry . has ( 'query_records' ) ) . toBe ( true ) ;
125+
126+ // Metadata tools should also be present with distinct names
127+ expect ( registry . has ( 'list_metadata_objects' ) ) . toBe ( true ) ;
128+ expect ( registry . has ( 'describe_metadata_object' ) ) . toBe ( true ) ;
129+ expect ( registry . has ( 'create_object' ) ) . toBe ( true ) ;
90130 } ) ;
91131} ) ;
92132
@@ -217,6 +257,64 @@ describe('create_object handler', () => {
217257 const parsed = JSON . parse ( ( result . output as any ) . value ) ;
218258 expect ( parsed . error ) . toContain ( 'required' ) ;
219259 } ) ;
260+
261+ it ( 'should reject fields with invalid snake_case names' , async ( ) => {
262+ const result = await registry . execute ( {
263+ type : 'tool-call' as const ,
264+ toolCallId : 'c7' ,
265+ toolName : 'create_object' ,
266+ input : {
267+ name : 'project' ,
268+ label : 'Project' ,
269+ fields : [
270+ { name : 'ValidField' , type : 'text' } ,
271+ ] ,
272+ } ,
273+ } ) ;
274+
275+ const parsed = JSON . parse ( ( result . output as any ) . value ) ;
276+ expect ( parsed . error ) . toContain ( 'snake_case' ) ;
277+ expect ( metadataService . register ) . not . toHaveBeenCalled ( ) ;
278+ } ) ;
279+
280+ it ( 'should reject fields with duplicate names' , async ( ) => {
281+ const result = await registry . execute ( {
282+ type : 'tool-call' as const ,
283+ toolCallId : 'c8' ,
284+ toolName : 'create_object' ,
285+ input : {
286+ name : 'project' ,
287+ label : 'Project' ,
288+ fields : [
289+ { name : 'status' , type : 'text' } ,
290+ { name : 'status' , type : 'select' } ,
291+ ] ,
292+ } ,
293+ } ) ;
294+
295+ const parsed = JSON . parse ( ( result . output as any ) . value ) ;
296+ expect ( parsed . error ) . toContain ( 'Duplicate' ) ;
297+ expect ( metadataService . register ) . not . toHaveBeenCalled ( ) ;
298+ } ) ;
299+
300+ it ( 'should reject fields with missing name' , async ( ) => {
301+ const result = await registry . execute ( {
302+ type : 'tool-call' as const ,
303+ toolCallId : 'c9' ,
304+ toolName : 'create_object' ,
305+ input : {
306+ name : 'project' ,
307+ label : 'Project' ,
308+ fields : [
309+ { type : 'text' } ,
310+ ] ,
311+ } ,
312+ } ) ;
313+
314+ const parsed = JSON . parse ( ( result . output as any ) . value ) ;
315+ expect ( parsed . error ) . toBeTruthy ( ) ;
316+ expect ( metadataService . register ) . not . toHaveBeenCalled ( ) ;
317+ } ) ;
220318} ) ;
221319
222320// ═══════════════════════════════════════════════════════════════════
@@ -332,6 +430,70 @@ describe('add_field handler', () => {
332430 const parsed = JSON . parse ( ( result . output as any ) . value ) ;
333431 expect ( parsed . error ) . toContain ( 'snake_case' ) ;
334432 } ) ;
433+
434+ it ( 'should reject invalid objectName (not snake_case)' , async ( ) => {
435+ const result = await registry . execute ( {
436+ type : 'tool-call' as const ,
437+ toolCallId : 'c6' ,
438+ toolName : 'add_field' ,
439+ input : { objectName : 'MyProject' , name : 'status' , type : 'text' } ,
440+ } ) ;
441+
442+ const parsed = JSON . parse ( ( result . output as any ) . value ) ;
443+ expect ( parsed . error ) . toContain ( 'snake_case' ) ;
444+ } ) ;
445+
446+ it ( 'should accept reference as a string (not object)' , async ( ) => {
447+ const result = await registry . execute ( {
448+ type : 'tool-call' as const ,
449+ toolCallId : 'c7' ,
450+ toolName : 'add_field' ,
451+ input : { objectName : 'project' , name : 'account_id' , type : 'lookup' , reference : 'account' } ,
452+ } ) ;
453+
454+ const parsed = JSON . parse ( ( result . output as any ) . value ) ;
455+ expect ( parsed . fieldName ) . toBe ( 'account_id' ) ;
456+ expect ( metadataService . register ) . toHaveBeenCalledWith (
457+ 'object' ,
458+ 'project' ,
459+ expect . objectContaining ( {
460+ fields : expect . objectContaining ( {
461+ account_id : expect . objectContaining ( { type : 'lookup' , reference : 'account' } ) ,
462+ } ) ,
463+ } ) ,
464+ ) ;
465+ } ) ;
466+
467+ it ( 'should reject invalid reference (not snake_case)' , async ( ) => {
468+ const result = await registry . execute ( {
469+ type : 'tool-call' as const ,
470+ toolCallId : 'c8' ,
471+ toolName : 'add_field' ,
472+ input : { objectName : 'project' , name : 'account_id' , type : 'lookup' , reference : 'MyAccount' } ,
473+ } ) ;
474+
475+ const parsed = JSON . parse ( ( result . output as any ) . value ) ;
476+ expect ( parsed . error ) . toContain ( 'snake_case' ) ;
477+ } ) ;
478+
479+ it ( 'should reject invalid select option values (not snake_case)' , async ( ) => {
480+ const result = await registry . execute ( {
481+ type : 'tool-call' as const ,
482+ toolCallId : 'c9' ,
483+ toolName : 'add_field' ,
484+ input : {
485+ objectName : 'project' ,
486+ name : 'priority' ,
487+ type : 'select' ,
488+ options : [
489+ { label : 'High Priority' , value : 'HighPriority' } ,
490+ ] ,
491+ } ,
492+ } ) ;
493+
494+ const parsed = JSON . parse ( ( result . output as any ) . value ) ;
495+ expect ( parsed . error ) . toContain ( 'snake_case' ) ;
496+ } ) ;
335497} ) ;
336498
337499// ═══════════════════════════════════════════════════════════════════
@@ -496,10 +658,10 @@ describe('delete_field handler', () => {
496658} ) ;
497659
498660// ═══════════════════════════════════════════════════════════════════
499- // list_objects handler
661+ // list_metadata_objects handler
500662// ═══════════════════════════════════════════════════════════════════
501663
502- describe ( 'list_objects handler' , ( ) => {
664+ describe ( 'list_metadata_objects handler' , ( ) => {
503665 let registry : ToolRegistry ;
504666 let metadataService : IMetadataService ;
505667
@@ -516,7 +678,7 @@ describe('list_objects handler', () => {
516678 const result = await registry . execute ( {
517679 type : 'tool-call' as const ,
518680 toolCallId : 'c1' ,
519- toolName : 'list_objects ' ,
681+ toolName : 'list_metadata_objects ' ,
520682 input : { } ,
521683 } ) ;
522684
@@ -531,7 +693,7 @@ describe('list_objects handler', () => {
531693 const result = await registry . execute ( {
532694 type : 'tool-call' as const ,
533695 toolCallId : 'c2' ,
534- toolName : 'list_objects ' ,
696+ toolName : 'list_metadata_objects ' ,
535697 input : { filter : 'account' } ,
536698 } ) ;
537699
@@ -544,7 +706,7 @@ describe('list_objects handler', () => {
544706 const result = await registry . execute ( {
545707 type : 'tool-call' as const ,
546708 toolCallId : 'c3' ,
547- toolName : 'list_objects ' ,
709+ toolName : 'list_metadata_objects ' ,
548710 input : { includeFields : true } ,
549711 } ) ;
550712
@@ -561,7 +723,7 @@ describe('list_objects handler', () => {
561723 const result = await registry . execute ( {
562724 type : 'tool-call' as const ,
563725 toolCallId : 'c4' ,
564- toolName : 'list_objects ' ,
726+ toolName : 'list_metadata_objects ' ,
565727 input : { } ,
566728 } ) ;
567729
@@ -572,10 +734,10 @@ describe('list_objects handler', () => {
572734} ) ;
573735
574736// ═══════════════════════════════════════════════════════════════════
575- // describe_object handler
737+ // describe_metadata_object handler
576738// ═══════════════════════════════════════════════════════════════════
577739
578- describe ( 'describe_object handler' , ( ) => {
740+ describe ( 'describe_metadata_object handler' , ( ) => {
579741 let registry : ToolRegistry ;
580742 let metadataService : IMetadataService ;
581743
@@ -600,7 +762,7 @@ describe('describe_object handler', () => {
600762 const result = await registry . execute ( {
601763 type : 'tool-call' as const ,
602764 toolCallId : 'c1' ,
603- toolName : 'describe_object ' ,
765+ toolName : 'describe_metadata_object ' ,
604766 input : { objectName : 'account' } ,
605767 } ) ;
606768
@@ -622,7 +784,7 @@ describe('describe_object handler', () => {
622784 const result = await registry . execute ( {
623785 type : 'tool-call' as const ,
624786 toolCallId : 'c2' ,
625- toolName : 'describe_object ' ,
787+ toolName : 'describe_metadata_object ' ,
626788 input : { objectName : 'nonexistent' } ,
627789 } ) ;
628790
@@ -673,7 +835,7 @@ describe('Metadata Tools — full lifecycle', () => {
673835 const descResult = await registry . execute ( {
674836 type : 'tool-call' as const ,
675837 toolCallId : 's4' ,
676- toolName : 'describe_object ' ,
838+ toolName : 'describe_metadata_object ' ,
677839 input : { objectName : 'invoice' } ,
678840 } ) ;
679841 const desc = JSON . parse ( ( descResult . output as any ) . value ) ;
@@ -705,7 +867,7 @@ describe('Metadata Tools — full lifecycle', () => {
705867 const descResult2 = await registry . execute ( {
706868 type : 'tool-call' as const ,
707869 toolCallId : 's7' ,
708- toolName : 'describe_object ' ,
870+ toolName : 'describe_metadata_object ' ,
709871 input : { objectName : 'invoice' } ,
710872 } ) ;
711873 const desc2 = JSON . parse ( ( descResult2 . output as any ) . value ) ;
@@ -718,7 +880,7 @@ describe('Metadata Tools — full lifecycle', () => {
718880 const listResult = await registry . execute ( {
719881 type : 'tool-call' as const ,
720882 toolCallId : 's8' ,
721- toolName : 'list_objects ' ,
883+ toolName : 'list_metadata_objects ' ,
722884 input : { } ,
723885 } ) ;
724886 const list = JSON . parse ( ( listResult . output as any ) . value ) ;
0 commit comments