Skip to content

Commit 448ea99

Browse files
committed
test(cli): add tests for utils modules with low coverage
Add comprehensive test coverage for: - utils/cli/completion.mts: bash completion script generation - utils/python/standalone.mts: Python re-exports verification - utils/telemetry/service.mts: TelemetryService singleton pattern, event tracking, batching, and flushing
1 parent 50254b9 commit 448ea99

File tree

3 files changed

+396
-127
lines changed

3 files changed

+396
-127
lines changed

packages/cli/test/unit/utils/cli/completion.test.mts

Lines changed: 96 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,173 +1,166 @@
11
/**
2-
* Unit tests for CLI shell completion.
2+
* Unit tests for CLI completion utilities.
33
*
44
* Purpose:
5-
* Tests shell completion generation for CLI commands. Validates bash, zsh, and fish completion scripts.
5+
* Tests the bash completion script generation and configuration.
66
*
77
* Test Coverage:
8-
* - Completion script generation
9-
* - Command suggestion
10-
* - Flag completion
11-
* - Multi-shell support (bash, zsh, fish)
12-
* - Dynamic completion data
13-
*
14-
* Testing Approach:
15-
* Tests completion script output and validation logic.
8+
* - COMPLETION_CMD_PREFIX constant
9+
* - getCompletionSourcingCommand function
10+
* - getBashrcDetails function
1611
*
1712
* Related Files:
1813
* - utils/cli/completion.mts (implementation)
1914
*/
2015

21-
import fs from 'node:fs'
22-
import path from 'node:path'
23-
2416
import { beforeEach, describe, expect, it, vi } from 'vitest'
2517

18+
// Mock fs.existsSync.
19+
const mockExistsSync = vi.hoisted(() => vi.fn())
20+
vi.mock('node:fs', async importOriginal => {
21+
const actual = (await importOriginal()) as typeof import('node:fs')
22+
return {
23+
...actual,
24+
default: {
25+
...actual,
26+
existsSync: mockExistsSync,
27+
},
28+
existsSync: mockExistsSync,
29+
}
30+
})
31+
32+
// Mock getSocketAppDataPath.
33+
const mockGetSocketAppDataPath = vi.hoisted(() => vi.fn())
34+
vi.mock('../../../../src/constants/paths.mts', async importOriginal => {
35+
const actual =
36+
(await importOriginal()) as typeof import('../../../../src/constants/paths.mts')
37+
return {
38+
...actual,
39+
getSocketAppDataPath: mockGetSocketAppDataPath,
40+
}
41+
})
42+
2643
import {
2744
COMPLETION_CMD_PREFIX,
2845
getBashrcDetails,
2946
getCompletionSourcingCommand,
3047
} from '../../../../src/utils/cli/completion.mts'
3148

32-
// Mock node:fs.
33-
const mockGetSocketAppDataPath = vi.hoisted(() => vi.fn(() => '/mock/app/data'))
34-
35-
vi.mock('node:fs', () => ({
36-
default: {
37-
existsSync: vi.fn(),
38-
},
39-
}))
40-
41-
// Mock constants/paths.
42-
vi.mock('../../../../src/constants/paths.mts', () => ({
43-
rootPath: '/mock/dist/path',
44-
getSocketAppDataPath: mockGetSocketAppDataPath,
45-
}))
46-
47-
describe('completion utilities', () => {
49+
describe('cli/completion', () => {
4850
beforeEach(() => {
4951
vi.clearAllMocks()
5052
})
5153

5254
describe('COMPLETION_CMD_PREFIX', () => {
53-
it('has the expected value', () => {
55+
it('has the correct prefix', () => {
5456
expect(COMPLETION_CMD_PREFIX).toBe('complete -F _socket_completion')
5557
})
5658
})
5759

5860
describe('getCompletionSourcingCommand', () => {
59-
it('returns sourcing command when completion script exists', () => {
60-
vi.mocked(fs.existsSync).mockReturnValue(true)
61+
it('returns error when completion script does not exist', () => {
62+
mockExistsSync.mockReturnValue(false)
6163

6264
const result = getCompletionSourcingCommand()
6365

64-
expect(result).toEqual({
65-
ok: true,
66-
data: 'source /mock/dist/path/data/socket-completion.bash',
67-
})
68-
69-
// Note: On Windows, path.join returns backslashes, which are then checked by existsSync.
70-
// The implementation normalizes the path display but checks existence with the actual path.
71-
const expectedPath =
72-
path.sep === '\\'
73-
? '\\mock\\dist\\path\\data\\socket-completion.bash'
74-
: '/mock/dist/path/data/socket-completion.bash'
75-
expect(fs.existsSync).toHaveBeenCalledWith(expectedPath)
66+
expect(result.ok).toBe(false)
67+
if (!result.ok) {
68+
expect(result.message).toBe('Tab Completion script not found')
69+
expect(result.cause).toContain('Expected to find completion script')
70+
}
7671
})
7772

78-
it('returns error when completion script does not exist', () => {
79-
vi.mocked(fs.existsSync).mockReturnValue(false)
73+
it('returns sourcing command when completion script exists', () => {
74+
mockExistsSync.mockReturnValue(true)
8075

8176
const result = getCompletionSourcingCommand()
8277

83-
expect(result).toEqual({
84-
ok: false,
85-
message: 'Tab Completion script not found',
86-
cause:
87-
'Expected to find completion script at `/mock/dist/path/data/socket-completion.bash` but it was not there',
88-
})
78+
expect(result.ok).toBe(true)
79+
if (result.ok) {
80+
expect(result.data).toContain('source')
81+
expect(result.data).toContain('socket-completion.bash')
82+
}
8983
})
90-
})
9184

92-
describe('getBashrcDetails', () => {
93-
it('returns bashrc details when everything is configured', () => {
94-
vi.mocked(fs.existsSync).mockReturnValue(true)
85+
it('uses forward slashes in sourcing command', () => {
86+
mockExistsSync.mockReturnValue(true)
9587

96-
const result = getBashrcDetails('socket')
88+
const result = getCompletionSourcingCommand()
9789

9890
expect(result.ok).toBe(true)
9991
if (result.ok) {
100-
expect(result.data.completionCommand).toBe(
101-
'complete -F _socket_completion socket',
102-
)
103-
expect(result.data.sourcingCommand).toBe(
104-
'source /mock/dist/path/data/socket-completion.bash',
105-
)
106-
expect(result.data.targetName).toBe('socket')
107-
expect(result.data.targetPath).toBe(
108-
'/mock/app/completion/socket-completion.bash',
109-
)
110-
expect(result.data.toAddToBashrc).toContain(
111-
'# Socket CLI completion for "socket"',
112-
)
113-
expect(result.data.toAddToBashrc).toContain(
114-
'source "/mock/app/completion/socket-completion.bash"',
115-
)
116-
expect(result.data.toAddToBashrc).toContain(
117-
'complete -F _socket_completion socket',
118-
)
92+
expect(result.data).not.toContain('\\')
11993
}
12094
})
95+
})
12196

122-
it('returns error when completion script is missing', () => {
123-
vi.mocked(fs.existsSync).mockReturnValue(false)
97+
describe('getBashrcDetails', () => {
98+
it('returns error when completion script does not exist', () => {
99+
mockExistsSync.mockReturnValue(false)
124100

125101
const result = getBashrcDetails('socket')
126102

127-
expect(result).toEqual({
128-
ok: false,
129-
message: 'Tab Completion script not found',
130-
cause:
131-
'Expected to find completion script at `/mock/dist/path/data/socket-completion.bash` but it was not there',
132-
})
103+
expect(result.ok).toBe(false)
104+
if (!result.ok) {
105+
expect(result.message).toBe('Tab Completion script not found')
106+
}
133107
})
134108

135-
it('returns error when socketAppDataPath is not available', () => {
136-
// This test is tricky because we need to re-mock the constants module.
137-
// Since getBashrcDetails imports constants at the top level,
138-
// we can't easily change it after import. Let's skip this test
139-
// or mark it as todo since the logic is tested in other ways.
109+
it('returns error when config directory cannot be determined', () => {
110+
mockExistsSync.mockReturnValue(true)
111+
mockGetSocketAppDataPath.mockReturnValue(null)
112+
113+
const result = getBashrcDetails('socket')
114+
115+
expect(result.ok).toBe(false)
116+
if (!result.ok) {
117+
expect(result.message).toBe('Could not determine config directory')
118+
}
140119
})
141120

142-
it('handles different command names', () => {
143-
vi.mocked(fs.existsSync).mockReturnValue(true)
121+
it('returns bashrc details when everything is available', () => {
122+
mockExistsSync.mockReturnValue(true)
123+
mockGetSocketAppDataPath.mockReturnValue('/home/user/.socket/config.json')
144124

145-
const result = getBashrcDetails('my-custom-socket')
125+
const result = getBashrcDetails('socket')
146126

147127
expect(result.ok).toBe(true)
148128
if (result.ok) {
129+
expect(result.data.targetName).toBe('socket')
149130
expect(result.data.completionCommand).toBe(
150-
'complete -F _socket_completion my-custom-socket',
151-
)
152-
expect(result.data.targetName).toBe('my-custom-socket')
153-
expect(result.data.toAddToBashrc).toContain('my-custom-socket')
154-
expect(result.data.toAddToBashrc).toContain(
155-
'# Socket CLI completion for "my-custom-socket"',
131+
`${COMPLETION_CMD_PREFIX} socket`,
156132
)
133+
expect(result.data.toAddToBashrc).toContain('Socket CLI completion')
134+
expect(result.data.toAddToBashrc).toContain('socket')
135+
expect(result.data.sourcingCommand).toContain('source')
157136
}
158137
})
159138

160-
it('constructs correct paths using path.join', () => {
161-
vi.mocked(fs.existsSync).mockReturnValue(true)
139+
it('uses forward slashes in target path', () => {
140+
mockExistsSync.mockReturnValue(true)
141+
mockGetSocketAppDataPath.mockReturnValue('/home/user/.socket/config.json')
162142

163143
const result = getBashrcDetails('socket')
164144

165145
expect(result.ok).toBe(true)
166146
if (result.ok) {
167-
// Note: The implementation normalizes paths to forward slashes for bash compatibility.
168-
expect(result.data.targetPath).toBe(
169-
'/mock/app/completion/socket-completion.bash',
147+
expect(result.data.targetPath).not.toContain('\\')
148+
}
149+
})
150+
151+
it('handles custom command names', () => {
152+
mockExistsSync.mockReturnValue(true)
153+
mockGetSocketAppDataPath.mockReturnValue('/home/user/.socket/config.json')
154+
155+
const result = getBashrcDetails('socket-npm')
156+
157+
expect(result.ok).toBe(true)
158+
if (result.ok) {
159+
expect(result.data.targetName).toBe('socket-npm')
160+
expect(result.data.completionCommand).toBe(
161+
`${COMPLETION_CMD_PREFIX} socket-npm`,
170162
)
163+
expect(result.data.toAddToBashrc).toContain('socket-npm')
171164
}
172165
})
173166
})
Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,43 @@
11
/**
2-
* Unit tests for standalone Python utilities.
2+
* Unit tests for Python standalone utilities.
33
*
44
* Purpose:
5-
* Tests standalone Python utilities. Validates Python binary detection.
5+
* Tests the re-exports from the DLX spawn utilities.
66
*
77
* Test Coverage:
8-
* - ensurePython function
9-
* - Python binary availability
10-
*
11-
* Testing Approach:
12-
* Tests Python ecosystem utilities.
8+
* - Re-export verification
9+
* - Type export verification
1310
*
1411
* Related Files:
1512
* - utils/python/standalone.mts (implementation)
16-
* - utils/dlx/spawn.mts (actual implementation)
13+
* - utils/dlx/spawn.mts (source module)
1714
*/
1815

1916
import { describe, expect, it } from 'vitest'
2017

21-
import { ensurePython } from '../../../../src/utils/python/standalone.mts'
18+
import {
19+
ensurePython,
20+
ensurePythonDlx,
21+
ensureSocketPyCli,
22+
spawnSocketPyCli,
23+
} from '../../../../src/utils/python/standalone.mts'
24+
25+
describe('python/standalone exports', () => {
26+
describe('re-exported functions', () => {
27+
it('exports ensurePython function', () => {
28+
expect(typeof ensurePython).toBe('function')
29+
})
30+
31+
it('exports ensurePythonDlx function', () => {
32+
expect(typeof ensurePythonDlx).toBe('function')
33+
})
34+
35+
it('exports ensureSocketPyCli function', () => {
36+
expect(typeof ensureSocketPyCli).toBe('function')
37+
})
2238

23-
describe('python-standalone', () => {
24-
describe('ensurePython', () => {
25-
it('should ensure Python is available or throw error', async () => {
26-
try {
27-
const pythonBin = await ensurePython()
28-
expect(typeof pythonBin).toBe('string')
29-
expect(pythonBin.length).toBeGreaterThan(0)
30-
expect(pythonBin).toContain('python')
31-
} catch (e) {
32-
// In test environment without proper constants, download might fail.
33-
// This is expected and not a test failure.
34-
expect(e).toBeDefined()
35-
}
36-
// Give it 60 seconds for potential download.
37-
}, 60_000)
39+
it('exports spawnSocketPyCli function', () => {
40+
expect(typeof spawnSocketPyCli).toBe('function')
41+
})
3842
})
3943
})

0 commit comments

Comments
 (0)