Repository navigation
Expand file tree
/
Copy pathresult.test.ts
More file actions
237 lines (207 loc) · 7.99 KB
/
Copy pathresult.test.ts
File metadata and controls
237 lines (207 loc) · 7.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import { load } from 'js-yaml'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { CLI_CONTRACT } from '../contract/commands'
import type { CommandSpec } from '../contract/types'
import { encodeFolderPath } from './request'
import { decodeFolderPath, foldPageEnvelope, renderPage, renderResult } from './result'
let logged: string[]
beforeEach(() => {
logged = []
vi.spyOn(console, 'log').mockImplementation((line: string) => {
logged.push(line)
})
})
/** The table arrives as one string; its first line is the header. */
function tableLines(): string[] {
return logged.join('\n').split('\n')
}
describe('single-record output is only clamped for the human table', () => {
const url = `https://sim-storage.example.com/exports/probe.csv?X-Amz-Signature=${'a'.repeat(300)}`
it('clamps the value in table mode', () => {
renderResult('tableExportDownload', 'table', { url }, {})
expect(logged[0]).toMatch(/…$/)
expect(logged[0].length).toBeLessThan(url.length)
})
})
describe('inferred cells pick a format from the key shape', () => {
const _row = {
createdAt: '2026-08-17T20:35:38.478Z',
durationMs: 9.145596999907866,
size: 3000000,
isActive: true,
deletedAt: null,
rowCount: 0,
displayName: 'probe',
}
it('infers nothing when the value type disagrees with the key', () => {
renderResult('getTable', 'text', { size: 'small', createdAt: 'whenever', isActive: 'yes' }, {})
expect(logged).toEqual(['size\tsmall', 'created at\twhenever', 'is active\tyes'])
})
})
describe('cells the user named, not the API', () => {
// `tables rows list` and `tables rows query` expand `data`, whose keys are
// whatever the caller called their columns. A key shape is a promise about
// the value, and only the API's own field names carry one.
const rows = [{ id: 'row_1', data: { score: 3, size: 5, duration: 30, isBillable: true } }]
const spec: CommandSpec = { expand: 'data' }
it('heads each one with the name the user has to type back into --filter', () => {
renderPage('table', { data: rows, nextCursor: null }, spec)
expect(tableLines()[0].split(/\s{2,}/)).toEqual([
'ID',
'SCORE',
'SIZE',
'DURATION',
'ISBILLABLE',
])
})
})
describe('a folder path the operation declared no column for', () => {
it('stays in wire form in json, which is what gets fed back', () => {
renderResult('createTableFolder', 'json', { path: '/Reports/Q1%202026' }, {})
expect(JSON.parse(logged[0])).toEqual({ path: '/Reports/Q1%202026' })
})
})
describe('a declared field that the API stops returning', () => {
const spec: CommandSpec = {
fields: [
{ header: 'plan' },
{ header: 'credits used', path: 'credits.used' },
{ header: 'credits limit', path: 'credits.limit' },
],
}
it('is reported as absent rather than dropped', () => {
renderResult('getBillingStatus', 'table', { plan: 'team' }, spec)
expect(logged).toHaveLength(3)
expect(logged[1]).toContain('credits used')
expect(logged[2]).toContain('credits limit')
})
it('stays an empty field in text, so cut -f2 still lines up', () => {
renderResult('getBillingStatus', 'text', { plan: 'team' }, spec)
expect(logged).toEqual(['plan\tteam', 'credits used\t', 'credits limit\t'])
})
})
describe('folder paths are shown by name, but piped in wire form', () => {
const _folders = [
{
path: '/cli-test-a/nested%20one',
name: 'nested one',
parentPath: '/cli-test-a',
updatedAt: '2026-08-17T20:35:38.478Z',
},
]
const spec = CLI_CONTRACT.listTableFolders as CommandSpec
/**
* `%2F` is the one escape that must survive display: decoding it prints a
* root folder named `a/enc` exactly like a folder `enc` nested under `a`, and
* the path people paste back then resolves to the other folder.
*/
describe('a folder whose own name contains the separator', () => {
const slashNamed = [
{
path: '/cli-test-a%2Fenc',
name: 'cli-test-a/enc',
parentPath: '/',
updatedAt: '2026-08-17T20:35:38.478Z',
},
]
const nested = [
{
path: '/cli-test-a/enc',
name: 'enc',
parentPath: '/cli-test-a',
updatedAt: '2026-08-17T20:35:38.478Z',
},
]
it('keeps it distinguishable from a genuinely nested folder in the table', () => {
renderPage('table', { data: slashNamed, nextCursor: null }, spec)
const [, slashRow] = tableLines()
logged = []
renderPage('table', { data: nested, nextCursor: null }, spec)
const [, nestedRow] = tableLines()
expect(slashRow).toContain('%2F')
expect(slashRow.split(/\s{2,}/)[0]).not.toBe(nestedRow.split(/\s{2,}/)[0])
})
it('survives a round trip back through the encoder', () => {
expect(encodeFolderPath(decodeFolderPath('/cli-test-a%2Fenc'))).toBe('/cli-test-a%2Fenc')
})
})
it('shows an undecodable path as it arrived rather than dropping it', () => {
renderPage(
'text',
{
data: [{ path: '/100%zz', name: 'x', parentPath: '/', updatedAt: null }],
nextCursor: null,
},
spec
)
expect(logged[0].split('\t')[0]).toBe('/100%zz')
})
})
describe('paginated JSON output', () => {
it.each([
{ format: 'json', truncated: true },
{ format: 'json', truncated: false },
{ format: 'yaml', truncated: true },
{ format: 'yaml', truncated: false },
] as const)(
'preserves truncated=$truncated in $format without carrying stale page data',
({ format, truncated }) => {
vi.spyOn(process.stderr, 'write').mockReturnValue(true)
const page = { data: [{ id: 'a' }, { id: 'b' }], nextCursor: null }
renderPage(format, page, {}, { data: [{ id: 'a' }], nextCursor: 'stale', truncated })
const result = format === 'json' ? JSON.parse(logged.join('\n')) : load(logged.join('\n'))
expect(result).toEqual({ ...page, truncated })
}
)
it.each([
{ first: false, last: true },
{ first: true, last: false },
{ first: undefined, last: false },
])('preserves truncation across pages with first=$first and last=$last', ({ first, last }) => {
vi.spyOn(process.stderr, 'write').mockReturnValue(true)
const envelope = foldPageEnvelope(
{
data: [{ id: 'a' }],
nextCursor: 'next',
...(first === undefined ? {} : { toolNamesTruncated: first }),
},
{ data: [{ id: 'b' }], nextCursor: null, toolNamesTruncated: last }
)
const page = { data: [{ id: 'a' }, { id: 'b' }], nextCursor: null }
renderPage('json', page, {}, envelope)
expect(JSON.parse(logged.join('\n'))).toEqual({
...page,
toolNamesTruncated: first === true || last,
})
})
})
describe('a truncation the response states inside its payload', () => {
/** Every note goes to stderr; stdout is asserted to be untouched by it. */
function captureStderr(): () => string {
const stderr = vi.spyOn(process.stderr, 'write').mockReturnValue(true)
return () => stderr.mock.calls.map(([chunk]) => String(chunk)).join('')
}
it.each(['readFileText', 'readFileVersionText'] as const)(
'preserves complete %s content in table output',
(operation) => {
const read = captureStderr()
const payload = {
fileId: 'file_probe',
name: 'probe.txt',
text: `${'Long line '.repeat(30)}\nsecond\tline\n\u001b[2Jlast line`,
truncated: false,
}
renderResult(operation, 'table', payload, CLI_CONTRACT[operation]!, {}, { data: payload })
expect(JSON.parse(logged.join('\n'))).toEqual(payload)
expect(logged.join('\n')).not.toContain('\u001b')
expect(read()).toBe('')
}
)
it('reports a clipped file body, which the envelope says nothing about', () => {
const read = captureStderr()
const payload = { fileId: 'wf_probe', name: 'a.txt', text: 'abc', truncated: true }
renderResult('readFileText', 'json', payload, {}, {}, { data: payload })
expect(read()).toContain('the server clipped this result')
expect(JSON.parse(logged.join('\n'))).toEqual(payload)
})
})