Skip to content

Commit ea0437f

Browse files
committed
test(sim-cli): assert directory requests at the transport
1 parent ea1980b commit ea0437f

1 file changed

Lines changed: 67 additions & 31 deletions

File tree

‎packages/sim-cli/src/commands/protocol/resource-directory.test.ts‎

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,56 @@
11
import { Command } from 'commander'
22
import { beforeEach, describe, expect, it, vi } from 'vitest'
3+
import type { OutputFormat, ResolvedProfile } from '../../config/index'
34
import { buildGeneratedCommands } from '../../runtime/build'
45
import { 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

2455
function program(): Command {
2556
const root = new Command('sim').exitOverride()
@@ -34,41 +65,46 @@ function program(): Command {
3465
}
3566

3667
beforeEach(() => {
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+
4177
describe('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

Comments
 (0)