@@ -10,6 +10,66 @@ function getSubBlock(id: string) {
1010 return subBlock
1111}
1212
13+ const OPERATION_VISIBLE_INPUTS : Record < string , string [ ] > = {
14+ netsuite_list_records : [ 'recordType' , 'q' , 'limit' , 'offset' ] ,
15+ netsuite_get_record : [ 'recordType' , 'recordId' , 'fields' , 'expand' , 'expandSubResources' ] ,
16+ netsuite_create_record : [ 'recordType' , 'body' ] ,
17+ netsuite_update_record : [ 'recordType' , 'recordId' , 'body' , 'replace' ] ,
18+ netsuite_upsert_record : [ 'recordType' , 'externalId' , 'body' , 'replace' ] ,
19+ netsuite_delete_record : [ 'recordType' , 'recordId' ] ,
20+ netsuite_get_subresource : [ 'recordType' , 'recordId' , 'subresourcePath' ] ,
21+ netsuite_get_record_form : [
22+ 'recordType' ,
23+ 'recordId' ,
24+ 'fields' ,
25+ 'body' ,
26+ 'expand' ,
27+ 'expandSubResources' ,
28+ ] ,
29+ netsuite_get_select_options : [ 'recordType' , 'recordId' , 'q' , 'fields' , 'body' , 'limit' , 'offset' ] ,
30+ netsuite_attach_record : [
31+ 'recordType' ,
32+ 'recordId' ,
33+ 'relatedType' ,
34+ 'relatedId' ,
35+ 'roleId' ,
36+ 'roleExternalId' ,
37+ ] ,
38+ netsuite_detach_record : [ 'recordType' , 'recordId' , 'relatedType' , 'relatedId' ] ,
39+ netsuite_execute_action : [ 'recordType' , 'recordId' , 'body' , 'action' ] ,
40+ netsuite_transform_record : [ 'recordType' , 'recordId' , 'body' , 'targetRecordType' ] ,
41+ netsuite_batch_get_records : [
42+ 'recordType' ,
43+ 'fields' ,
44+ 'ids' ,
45+ 'expand' ,
46+ 'expandSubResources' ,
47+ 'idempotencyKey' ,
48+ ] ,
49+ netsuite_batch_create_records : [ 'recordType' , 'items' , 'idempotencyKey' ] ,
50+ netsuite_batch_update_records : [ 'recordType' , 'items' , 'idempotencyKey' ] ,
51+ netsuite_batch_upsert_records : [ 'recordType' , 'items' , 'idempotencyKey' ] ,
52+ netsuite_batch_delete_records : [ 'recordType' , 'ids' , 'idempotencyKey' ] ,
53+ netsuite_execute_suiteql : [ 'query' , 'limit' , 'offset' ] ,
54+ netsuite_list_datasets : [ 'limit' , 'offset' ] ,
55+ netsuite_execute_dataset : [ 'datasetId' , 'limit' , 'offset' ] ,
56+ netsuite_list_record_types : [ ] ,
57+ netsuite_get_record_metadata : [ 'recordType' , 'format' ] ,
58+ netsuite_get_async_status : [ 'jobId' , 'view' , 'statusTaskId' ] ,
59+ netsuite_get_async_result : [ 'jobId' , 'resultTaskId' ] ,
60+ netsuite_get_server_time : [ ] ,
61+ netsuite_get_governance_limits : [ ] ,
62+ }
63+
64+ function conditionIncludesOperation ( condition : unknown , operation : string ) : boolean {
65+ if ( ! condition || typeof condition !== 'object' ) return true
66+ const candidate = condition as { field ?: unknown ; value ?: unknown }
67+ if ( candidate . field !== 'operation' ) return false
68+ return Array . isArray ( candidate . value )
69+ ? candidate . value . includes ( operation )
70+ : candidate . value === operation
71+ }
72+
1373describe ( 'Oracle NetSuite block' , ( ) => {
1474 it ( 'exposes exactly the agreed 27 operations and no arbitrary request or RESTlet' , ( ) => {
1575 const operation = getSubBlock ( 'operation' )
@@ -31,6 +91,29 @@ describe('Oracle NetSuite block', () => {
3191 }
3292 } )
3393
94+ it ( 'shows exactly the intended operation-specific inputs for every tool' , ( ) => {
95+ const commonFields = new Set ( [
96+ 'operation' ,
97+ 'accountId' ,
98+ 'clientId' ,
99+ 'certificateId' ,
100+ 'privateKey' ,
101+ ] )
102+ expect ( Object . keys ( OPERATION_VISIBLE_INPUTS ) ) . toEqual ( [ ...NETSUITE_TOOL_IDS ] )
103+
104+ for ( const operation of NETSUITE_TOOL_IDS ) {
105+ const visible = NetSuiteBlock . subBlocks
106+ . filter (
107+ ( subBlock ) =>
108+ ! commonFields . has ( subBlock . id ) &&
109+ conditionIncludesOperation ( subBlock . condition , operation )
110+ )
111+ . map ( ( subBlock ) => subBlock . id )
112+ . sort ( )
113+ expect ( visible , operation ) . toEqual ( [ ...OPERATION_VISIBLE_INPUTS [ operation ] ] . sort ( ) )
114+ }
115+ } )
116+
34117 it ( 'covers every operation on canvas and preserves required action targets' , ( ) => {
35118 const byOperation = NetSuiteBlock . canvasPresentation ?. sentences ?. byOperation
36119 if ( ! byOperation ) throw new Error ( 'NetSuite block must define operation canvas sentences' )
@@ -84,6 +167,7 @@ describe('Oracle NetSuite block', () => {
84167 clientId : { type : 'string' } ,
85168 certificateId : { type : 'string' } ,
86169 privateKey : { type : 'string' } ,
170+ items : { type : 'array' } ,
87171 } )
88172 } )
89173
@@ -215,6 +299,39 @@ describe('Oracle NetSuite block', () => {
215299 expect (
216300 mapParams ( { operation : 'netsuite_get_async_result' , resultTaskId : 'task-8' } )
217301 ) . toMatchObject ( { taskId : 'task-8' } )
302+ expect (
303+ mapParams ( {
304+ operation : 'netsuite_get_async_result' ,
305+ taskId : 'legacy-task' ,
306+ statusTaskId : 'stale-status-task' ,
307+ resultTaskId : 'current-result-task' ,
308+ } )
309+ ) . toMatchObject ( { taskId : 'current-result-task' } )
310+ expect (
311+ mapParams ( {
312+ operation : 'netsuite_get_async_status' ,
313+ taskId : 'legacy-task' ,
314+ statusTaskId : 'current-status-task' ,
315+ resultTaskId : 'stale-result-task' ,
316+ } )
317+ ) . toMatchObject ( { taskId : 'current-status-task' } )
318+ expect (
319+ mapParams ( {
320+ operation : 'netsuite_get_async_result' ,
321+ taskId : 'legacy-task' ,
322+ resultTaskId : '' ,
323+ } )
324+ ) . toMatchObject ( { taskId : '' } )
325+ expect (
326+ mapParams ( {
327+ operation : 'netsuite_get_async_status' ,
328+ taskId : 'legacy-task' ,
329+ statusTaskId : null ,
330+ } )
331+ ) . toMatchObject ( { taskId : null } )
332+ expect (
333+ mapParams ( { operation : 'netsuite_get_async_status' , taskId : 'legacy-task' } )
334+ ) . toMatchObject ( { taskId : 'legacy-task' } )
218335 expect (
219336 mapParams ( {
220337 operation : 'netsuite_attach_record' ,
@@ -226,6 +343,15 @@ describe('Oracle NetSuite block', () => {
226343 expect ( ( ) => mapParams ( { operation : 'netsuite_create_record' , body : '{bad json' } ) ) . toThrow (
227344 'Record fields must be valid JSON'
228345 )
346+ for ( const value of [ true , false , [ 5 ] , { } , Number . NaN , Number . POSITIVE_INFINITY ] ) {
347+ expect (
348+ ( ) => mapParams ( { operation : 'netsuite_list_records' , limit : value } ) ,
349+ String ( value )
350+ ) . toThrow ( 'Invalid number for Limit' )
351+ }
352+ expect (
353+ mapParams ( { operation : 'netsuite_list_records' , limit : ' ' , offset : null } )
354+ ) . toMatchObject ( { limit : undefined , offset : undefined } )
229355 } )
230356
231357 it ( 'declares every tool parameter input and every common output once' , ( ) => {
0 commit comments