11import { Command } from 'commander'
22import { beforeEach , describe , expect , it , vi } from 'vitest'
3+ import type { OutputFormat , ResolvedProfile } from '../../config/index'
34import { buildGeneratedCommands } from '../../runtime/build'
45import { attachProtocolCommands } from './index'
56
6- const { mockRequest, output } = vi . hoisted ( ( ) => ( {
7- mockRequest : vi . fn ( ) ,
7+ interface SentRequest {
8+ method : string
9+ url : URL
10+ body : unknown
11+ }
12+
13+ interface Wire {
14+ output : { format : OutputFormat }
15+ sent : SentRequest [ ]
16+ respond : ( url : URL ) => unknown
17+ }
18+
19+ const wire : Wire = vi . hoisted ( ( ) => ( {
820 output : { format : 'json' } ,
21+ sent : [ ] ,
22+ respond : ( ) => ( { data : [ ] , nextCursor : null } ) ,
923} ) )
1024
11- vi . mock ( '../../context' , ( ) => ( {
12- clientFrom : ( ) => ( {
13- client : { request : mockRequest , requireWorkspace : ( ) => 'ws_local' } ,
14- profile : {
15- workspaceId : 'ws_local' ,
16- output : output . format ,
17- name : 'default' ,
18- apiKey : 'k' ,
19- endpoint : 'https://sim.example' ,
25+ vi . mock ( '../../context' , async ( ) => {
26+ const { SimClient } = await import ( '../../http/client' )
27+ return {
28+ clientFrom : ( ) => {
29+ const profile : ResolvedProfile = {
30+ name : 'default' ,
31+ authProfile : 'default' ,
32+ endpoint : 'https://sim.example' ,
33+ apiKey : 'k' ,
34+ oauth : null ,
35+ workspaceId : 'ws_local' ,
36+ output : wire . output . format ,
37+ sources : { endpoint : 'flag' , credential : 'flag' , workspaceId : 'flag' , output : 'flag' } ,
38+ transport : async ( input : string | URL | Request , init ?: RequestInit ) => {
39+ const url = new URL ( String ( input ) )
40+ wire . sent . push ( {
41+ method : init ?. method ?? 'GET' ,
42+ url,
43+ body : init ?. body ? JSON . parse ( String ( init . body ) ) : undefined ,
44+ } )
45+ return new Response ( JSON . stringify ( wire . respond ( url ) ) , {
46+ headers : { 'content-type' : 'application/json' } ,
47+ } )
48+ } ,
49+ }
50+ return { client : new SimClient ( profile ) , profile }
2051 } ,
21- } ) ,
22- } ) )
52+ }
53+ } )
2354
2455function program ( ) : Command {
2556 const root = new Command ( 'sim' ) . exitOverride ( )
@@ -34,41 +65,46 @@ function program(): Command {
3465}
3566
3667beforeEach ( ( ) => {
37- mockRequest . mockReset ( )
38- output . format = 'json'
68+ wire . sent . length = 0
69+ wire . respond = ( ) => ( { data : [ ] , nextCursor : null } )
70+ wire . output . format = 'json'
3971} )
4072
73+ function sentTo ( path : string ) : SentRequest [ ] {
74+ return wire . sent . filter ( ( request ) => request . url . pathname === path )
75+ }
76+
4177describe ( 'resource directory' , ( ) => {
4278 it ( 'encodes the path both commands take, as every contract-driven flag does' , async ( ) => {
4379 // These two build their own request, so `buildRequest`'s encoding never ran
4480 // for them: `--folder '/Folder 1'` worked while `ls '/Folder 1'` was
4581 // rejected as non-canonical, and `mkdir` disagreed with the `folders
4682 // create` the README calls its long form.
47- mockRequest . mockResolvedValue ( { data : [ ] , nextCursor : null } )
4883 vi . spyOn ( console , 'log' ) . mockImplementation ( ( ) => { } )
4984
5085 await program ( ) . parseAsync ( [ 'node' , 'sim' , 'table' , 'ls' , '/Q1 (draft)' ] )
51- expect ( mockRequest ) . toHaveBeenCalledWith ( '/api/v2/tables/folders' , {
52- method : 'GET' ,
53- query : expect . objectContaining ( { parentPath : '/Q1%20%28draft%29' } ) ,
54- } )
86+ const [ listed ] = sentTo ( '/api/v2/tables/folders' )
87+ expect ( listed . method ) . toBe ( 'GET' )
88+ expect ( listed . url . searchParams . get ( 'parentPath' ) ) . toBe ( '/Q1%20%28draft%29' )
5589
56- mockRequest . mockReset ( )
57- mockRequest . mockResolvedValue ( { data : { folder : { } } } )
90+ wire . sent . length = 0
91+ wire . respond = ( ) => ( { data : { folder : { } } } )
5892 await program ( ) . parseAsync ( [ 'node' , 'sim' , 'table' , 'mkdir' , '/Q1 (draft)' ] )
59- expect ( mockRequest ) . toHaveBeenCalledWith ( '/api/v2/tables/folders' , {
60- method : 'POST' ,
61- body : { workspaceId : 'ws_local' , path : '/Q1%20%28draft%29' } ,
62- } )
93+ expect ( sentTo ( '/api/v2/tables/folders' ) ) . toEqual ( [
94+ expect . objectContaining ( {
95+ method : 'POST' ,
96+ body : { workspaceId : 'ws_local' , path : '/Q1%20%28draft%29' } ,
97+ } ) ,
98+ ] )
6399 } )
64100
65101 it ( 'decodes folder paths for the human formats but leaves json on the wire form' , async ( ) => {
66102 // `ls` builds its own columns, so the contract's `folder-path` display
67103 // format never reached it: the sibling `folders list` printed `/Folder 2`
68104 // while `ls` printed `/Folder%202` for the same folder, one column away
69105 // from the decoded `name` it prints beside it.
70- mockRequest . mockImplementation ( async ( path : string ) => {
71- if ( path === '/api/v2/tables/folders' ) {
106+ wire . respond = ( url ) => {
107+ if ( url . pathname === '/api/v2/tables/folders' ) {
72108 return {
73109 data : [
74110 {
@@ -92,18 +128,18 @@ describe('resource directory', () => {
92128 ] ,
93129 nextCursor : null ,
94130 }
95- } )
131+ }
96132
97133 const logged : string [ ] = [ ]
98134 vi . spyOn ( console , 'log' ) . mockImplementation ( ( line : string ) => logged . push ( line ) )
99135
100- output . format = 'text'
136+ wire . output . format = 'text'
101137 await program ( ) . parseAsync ( [ 'node' , 'sim' , 'table' , 'ls' , '/Folder 2' ] )
102138 expect ( logged . join ( '\n' ) ) . toContain ( '/Folder 2/New folder' )
103139 expect ( logged . join ( '\n' ) ) . not . toContain ( '%20' )
104140
105141 logged . length = 0
106- output . format = 'json'
142+ wire . output . format = 'json'
107143 await program ( ) . parseAsync ( [ 'node' , 'sim' , 'table' , 'ls' , '/Folder 2' ] )
108144 const entries = JSON . parse ( logged [ 0 ] ) as Array < { kind : string ; ref : string } >
109145 expect ( entries . find ( ( entry ) => entry . kind === 'folder' ) ?. ref ) . toBe ( '/Folder%202/New%20folder' )
0 commit comments