Skip to content

Commit f516449

Browse files
committed
test(cli): add unit tests for terminal utilities
Add comprehensive test coverage for: - simple-output.mts: Generic output formatter with table and pagination support - ascii-header-banner.mts: ASCII art header banner generation
1 parent 97e1587 commit f516449

File tree

2 files changed

+637
-0
lines changed

2 files changed

+637
-0
lines changed
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
/**
2+
* Unit tests for ASCII header banner utility.
3+
*
4+
* Purpose:
5+
* Tests the ASCII header banner generation for CLI commands.
6+
*
7+
* Test Coverage:
8+
* - getAsciiHeader function
9+
* - Compact mode output
10+
* - Token display
11+
* - Org display
12+
* - Version display
13+
*
14+
* Related Files:
15+
* - src/utils/terminal/ascii-header-banner.mts (implementation)
16+
*/
17+
18+
import { beforeEach, describe, expect, it, vi } from 'vitest'
19+
20+
// Mock dependencies.
21+
vi.mock('yoctocolors-cjs', () => ({
22+
default: {
23+
cyan: (s: string) => s,
24+
green: (s: string) => s,
25+
yellow: (s: string) => s,
26+
red: (s: string) => s,
27+
},
28+
}))
29+
30+
const mockGetCI = vi.hoisted(() => vi.fn(() => false))
31+
vi.mock('@socketsecurity/lib/env/ci', () => ({
32+
getCI: mockGetCI,
33+
}))
34+
35+
const mockGetSocketCliApiToken = vi.hoisted(() => vi.fn(() => ''))
36+
const mockGetSocketCliNoApiToken = vi.hoisted(() => vi.fn(() => false))
37+
vi.mock('@socketsecurity/lib/env/socket-cli', () => ({
38+
getSocketCliApiToken: mockGetSocketCliApiToken,
39+
getSocketCliNoApiToken: mockGetSocketCliNoApiToken,
40+
}))
41+
42+
vi.mock('@socketsecurity/lib/paths/normalize', () => ({
43+
normalizePath: (p: string) => p,
44+
}))
45+
46+
vi.mock('../../../../src/constants/cli.mts', () => ({
47+
FLAG_ORG: '--org',
48+
REDACTED: '[REDACTED]',
49+
}))
50+
51+
vi.mock('../../../../src/constants/config.mts', () => ({
52+
CONFIG_KEY_API_TOKEN: 'apiToken',
53+
CONFIG_KEY_DEFAULT_ORG: 'defaultOrg',
54+
}))
55+
56+
const mockGetCliVersion = vi.hoisted(() => vi.fn(() => '1.0.0'))
57+
vi.mock('../../../../src/env/cli-version.mts', () => ({
58+
getCliVersion: mockGetCliVersion,
59+
}))
60+
61+
const mockGetCliVersionHash = vi.hoisted(() => vi.fn(() => 'abc123'))
62+
vi.mock('../../../../src/env/cli-version-hash.mts', () => ({
63+
getCliVersionHash: mockGetCliVersionHash,
64+
}))
65+
66+
// Mock VITEST as false to test non-test mode.
67+
vi.mock('../../../../src/env/vitest.mts', () => ({
68+
VITEST: false,
69+
}))
70+
71+
const mockGetConfigValueOrUndef = vi.hoisted(() => vi.fn(() => undefined))
72+
const mockIsConfigFromFlag = vi.hoisted(() => vi.fn(() => false))
73+
vi.mock('../../../../src/utils/config.mts', () => ({
74+
getConfigValueOrUndef: mockGetConfigValueOrUndef,
75+
isConfigFromFlag: mockIsConfigFromFlag,
76+
}))
77+
78+
const mockIsDebug = vi.hoisted(() => vi.fn(() => false))
79+
vi.mock('../../../../src/utils/debug.mts', () => ({
80+
isDebug: mockIsDebug,
81+
}))
82+
83+
vi.mock('../../../../src/utils/terminal/ascii-header.mts', () => ({
84+
renderLogoWithFallback: () => 'LOGO',
85+
supportsFullColor: () => false,
86+
}))
87+
88+
vi.mock('../../../../src/utils/fs/home-path.mts', () => ({
89+
tildify: (p: string) => p,
90+
}))
91+
92+
const mockGetVisibleTokenPrefix = vi.hoisted(() => vi.fn(() => ''))
93+
vi.mock('../../../../src/utils/socket/sdk.mjs', () => ({
94+
getVisibleTokenPrefix: mockGetVisibleTokenPrefix,
95+
}))
96+
97+
import { getAsciiHeader } from '../../../../src/utils/terminal/ascii-header-banner.mts'
98+
99+
describe('ascii-header-banner', () => {
100+
beforeEach(() => {
101+
vi.clearAllMocks()
102+
mockGetCI.mockReturnValue(false)
103+
mockGetSocketCliApiToken.mockReturnValue('')
104+
mockGetSocketCliNoApiToken.mockReturnValue(false)
105+
mockGetConfigValueOrUndef.mockReturnValue(undefined)
106+
mockGetVisibleTokenPrefix.mockReturnValue('')
107+
mockIsDebug.mockReturnValue(false)
108+
})
109+
110+
describe('getAsciiHeader', () => {
111+
describe('compact mode', () => {
112+
it('returns compact header in compact mode', () => {
113+
mockGetCI.mockReturnValue(true)
114+
mockGetConfigValueOrUndef.mockImplementation((key: string) =>
115+
key === 'defaultOrg' ? 'my-org' : undefined,
116+
)
117+
118+
const header = getAsciiHeader('scan create', undefined, true)
119+
120+
expect(header).toContain('CLI:')
121+
expect(header).toContain('cmd: scan create')
122+
expect(header).toContain('org: my-org')
123+
expect(header).toContain('token: (not set)')
124+
})
125+
126+
it('shows org flag in compact mode', () => {
127+
mockGetCI.mockReturnValue(true)
128+
129+
const header = getAsciiHeader('scan create', 'flag-org', true)
130+
131+
expect(header).toContain('org: flag-org')
132+
})
133+
134+
it('shows token prefix in compact mode', () => {
135+
mockGetCI.mockReturnValue(true)
136+
mockGetVisibleTokenPrefix.mockReturnValue('sk_live_')
137+
mockGetSocketCliApiToken.mockReturnValue('sk_live_xxxxx')
138+
139+
const header = getAsciiHeader('scan create', undefined, true)
140+
141+
expect(header).toContain('sk_live_***')
142+
expect(header).toContain('(env)')
143+
})
144+
145+
it('shows disabled token in compact mode', () => {
146+
mockGetCI.mockReturnValue(true)
147+
mockGetSocketCliNoApiToken.mockReturnValue(true)
148+
149+
const header = getAsciiHeader('scan create', undefined, true)
150+
151+
expect(header).toContain('token: (disabled)')
152+
})
153+
})
154+
155+
describe('full mode', () => {
156+
it('includes logo and info lines', () => {
157+
mockGetCI.mockReturnValue(true) // Use CI mode for plain logo
158+
159+
const header = getAsciiHeader('fix', undefined, false)
160+
161+
// The plain logo contains "Socket" (in ASCII art style).
162+
expect(header).toContain('_____')
163+
expect(header).toContain('CLI:')
164+
expect(header).toContain('Command: `fix`')
165+
})
166+
167+
it('shows org from flag', () => {
168+
mockGetCI.mockReturnValue(true)
169+
170+
const header = getAsciiHeader('scan', 'my-org', false)
171+
172+
expect(header).toContain('org: my-org (--org flag)')
173+
})
174+
175+
it('shows org from config', () => {
176+
mockGetCI.mockReturnValue(true)
177+
mockGetConfigValueOrUndef.mockImplementation((key: string) =>
178+
key === 'defaultOrg' ? 'config-org' : undefined,
179+
)
180+
181+
const header = getAsciiHeader('scan', undefined, false)
182+
183+
expect(header).toContain('org: config-org (config)')
184+
})
185+
186+
it('shows not set when no org', () => {
187+
mockGetCI.mockReturnValue(true)
188+
189+
const header = getAsciiHeader('scan', undefined, false)
190+
191+
expect(header).toContain('org: (not set)')
192+
})
193+
194+
it('shows token with env origin', () => {
195+
mockGetCI.mockReturnValue(true)
196+
mockGetSocketCliApiToken.mockReturnValue('sk_live_test')
197+
mockGetVisibleTokenPrefix.mockReturnValue('sk_live_')
198+
199+
const header = getAsciiHeader('scan', undefined, false)
200+
201+
expect(header).toContain('sk_live_***')
202+
expect(header).toContain('(env)')
203+
})
204+
205+
it('shows token with config origin', () => {
206+
mockGetCI.mockReturnValue(true)
207+
mockGetVisibleTokenPrefix.mockReturnValue('sk_live_')
208+
mockGetConfigValueOrUndef.mockImplementation((key: string) =>
209+
key === 'apiToken' ? 'sk_live_config' : undefined,
210+
)
211+
212+
const header = getAsciiHeader('scan', undefined, false)
213+
214+
expect(header).toContain('sk_live_***')
215+
expect(header).toContain('(config)')
216+
})
217+
218+
it('shows disabled token', () => {
219+
mockGetCI.mockReturnValue(true)
220+
mockGetSocketCliNoApiToken.mockReturnValue(true)
221+
222+
const header = getAsciiHeader('scan', undefined, false)
223+
224+
expect(header).toContain('(disabled)')
225+
})
226+
227+
it('shows node version in debug mode', () => {
228+
mockGetCI.mockReturnValue(true)
229+
mockIsDebug.mockReturnValue(true)
230+
231+
const header = getAsciiHeader('scan', undefined, false)
232+
233+
expect(header).toContain('Node:')
234+
})
235+
236+
it('shows version hash in debug mode', () => {
237+
mockGetCI.mockReturnValue(true)
238+
mockIsDebug.mockReturnValue(true)
239+
240+
const header = getAsciiHeader('scan', undefined, false)
241+
242+
expect(header).toContain('abc123')
243+
})
244+
})
245+
246+
describe('theme support', () => {
247+
it('accepts valid theme flag', () => {
248+
mockGetCI.mockReturnValue(true)
249+
250+
// Valid theme should not throw.
251+
const header = getAsciiHeader('scan', undefined, false, {
252+
headerTheme: 'cyberpunk',
253+
})
254+
255+
expect(header).toBeDefined()
256+
})
257+
258+
it('falls back to default for invalid theme', () => {
259+
mockGetCI.mockReturnValue(true)
260+
261+
// Invalid theme should fall back to default.
262+
const header = getAsciiHeader('scan', undefined, false, {
263+
headerTheme: 'invalid',
264+
})
265+
266+
expect(header).toBeDefined()
267+
})
268+
})
269+
})
270+
})

0 commit comments

Comments
 (0)