Skip to content

Commit aab2970

Browse files
committed
test(cli): add unit tests for organization and scan helpers
Add test coverage for: - output-organization-list.mts: Organization list output formatting - generate-report-test-helpers.mts: Scan test data helper functions
1 parent f516449 commit aab2970

File tree

2 files changed

+316
-0
lines changed

2 files changed

+316
-0
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/**
2+
* Unit tests for organization list output formatting.
3+
*
4+
* Purpose:
5+
* Tests the output formatting for organization list results.
6+
*
7+
* Test Coverage:
8+
* - outputOrganizationList function
9+
* - JSON output format
10+
* - Text output format
11+
* - Markdown output format
12+
* - Error handling
13+
*
14+
* Related Files:
15+
* - src/commands/organization/output-organization-list.mts (implementation)
16+
*/
17+
18+
import { beforeEach, describe, expect, it, vi } from 'vitest'
19+
20+
// Mock yoctocolors.
21+
vi.mock('yoctocolors-cjs', () => ({
22+
default: {
23+
bold: (s: string) => s,
24+
italic: (s: string) => s,
25+
},
26+
}))
27+
28+
// Mock logger.
29+
const mockLogger = vi.hoisted(() => ({
30+
log: vi.fn(),
31+
error: vi.fn(),
32+
warn: vi.fn(),
33+
fail: vi.fn(),
34+
success: vi.fn(),
35+
info: vi.fn(),
36+
}))
37+
vi.mock('@socketsecurity/lib/logger', () => ({
38+
getDefaultLogger: () => mockLogger,
39+
}))
40+
41+
// Mock utilities.
42+
vi.mock('../../../../src/utils/error/fail-msg-with-badge.mts', () => ({
43+
failMsgWithBadge: (msg: string, cause?: string) =>
44+
cause ? `${msg}: ${cause}` : msg,
45+
}))
46+
47+
vi.mock('../../../../src/utils/output/markdown.mts', () => ({
48+
mdHeader: (text: string) => `# ${text}`,
49+
}))
50+
51+
vi.mock('../../../../src/utils/output/result-json.mjs', () => ({
52+
serializeResultJson: (result: unknown) => JSON.stringify(result, null, 2),
53+
}))
54+
55+
const mockGetVisibleTokenPrefix = vi.hoisted(() => vi.fn(() => 'sk_live_'))
56+
vi.mock('../../../../src/utils/socket/sdk.mjs', () => ({
57+
getVisibleTokenPrefix: mockGetVisibleTokenPrefix,
58+
}))
59+
60+
import { outputOrganizationList } from '../../../../src/commands/organization/output-organization-list.mts'
61+
62+
import type { OrganizationsCResult } from '../../../../src/commands/organization/fetch-organization-list.mts'
63+
64+
describe('output-organization-list', () => {
65+
beforeEach(() => {
66+
vi.clearAllMocks()
67+
process.exitCode = undefined
68+
})
69+
70+
describe('outputOrganizationList', () => {
71+
const mockOrganizations = [
72+
{ id: 'org-1', name: 'My Org', slug: 'my-org', plan: 'pro' },
73+
{ id: 'org-2', name: 'Other Org', slug: 'other-org', plan: 'free' },
74+
]
75+
76+
describe('JSON output', () => {
77+
it('outputs success result as JSON', async () => {
78+
const result: OrganizationsCResult = {
79+
ok: true,
80+
data: { organizations: mockOrganizations },
81+
}
82+
83+
await outputOrganizationList(result, 'json')
84+
85+
expect(mockLogger.log).toHaveBeenCalledWith(
86+
expect.stringContaining('"ok": true'),
87+
)
88+
})
89+
90+
it('outputs error result as JSON', async () => {
91+
const result: OrganizationsCResult = {
92+
ok: false,
93+
message: 'Failed to fetch',
94+
}
95+
96+
await outputOrganizationList(result, 'json')
97+
98+
expect(mockLogger.log).toHaveBeenCalledWith(
99+
expect.stringContaining('"ok": false'),
100+
)
101+
expect(process.exitCode).toBe(1)
102+
})
103+
})
104+
105+
describe('Text output', () => {
106+
it('outputs organization list in text format', async () => {
107+
const result: OrganizationsCResult = {
108+
ok: true,
109+
data: { organizations: mockOrganizations },
110+
}
111+
112+
await outputOrganizationList(result, 'text')
113+
114+
const logs = mockLogger.log.mock.calls.map(c => c[0]).join('\n')
115+
expect(logs).toContain('sk_live_')
116+
expect(logs).toContain('My Org')
117+
expect(logs).toContain('Other Org')
118+
})
119+
120+
it('outputs error with fail message', async () => {
121+
const result: OrganizationsCResult = {
122+
ok: false,
123+
message: 'Authentication failed',
124+
cause: 'Invalid token',
125+
}
126+
127+
await outputOrganizationList(result, 'text')
128+
129+
expect(mockLogger.fail).toHaveBeenCalledWith(
130+
expect.stringContaining('Authentication failed'),
131+
)
132+
expect(process.exitCode).toBe(1)
133+
})
134+
135+
it('uses custom exit code when provided', async () => {
136+
const result: OrganizationsCResult = {
137+
ok: false,
138+
message: 'Rate limited',
139+
code: 429,
140+
}
141+
142+
await outputOrganizationList(result, 'text')
143+
144+
expect(process.exitCode).toBe(429)
145+
})
146+
})
147+
148+
describe('Markdown output', () => {
149+
it('outputs organization list as markdown table', async () => {
150+
const result: OrganizationsCResult = {
151+
ok: true,
152+
data: { organizations: mockOrganizations },
153+
}
154+
155+
await outputOrganizationList(result, 'markdown')
156+
157+
const logs = mockLogger.log.mock.calls.map(c => c[0]).join('\n')
158+
expect(logs).toContain('# Organizations')
159+
expect(logs).toContain('Name')
160+
expect(logs).toContain('ID')
161+
expect(logs).toContain('Plan')
162+
expect(logs).toContain('My Org')
163+
expect(logs).toContain('org-1')
164+
})
165+
166+
it('handles organizations with missing name', async () => {
167+
const result: OrganizationsCResult = {
168+
ok: true,
169+
data: {
170+
organizations: [
171+
{ id: 'org-1', name: undefined as any, slug: 'my-org', plan: 'pro' },
172+
],
173+
},
174+
}
175+
176+
await outputOrganizationList(result, 'markdown')
177+
178+
// Should not throw.
179+
expect(mockLogger.log).toHaveBeenCalled()
180+
})
181+
})
182+
183+
describe('Default output', () => {
184+
it('defaults to text output', async () => {
185+
const result: OrganizationsCResult = {
186+
ok: true,
187+
data: { organizations: mockOrganizations },
188+
}
189+
190+
await outputOrganizationList(result)
191+
192+
const logs = mockLogger.log.mock.calls.map(c => c[0]).join('\n')
193+
expect(logs).toContain('Name:')
194+
expect(logs).toContain('ID:')
195+
expect(logs).toContain('Plan:')
196+
})
197+
})
198+
})
199+
})
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* Unit tests for scan report generation test helpers.
3+
*
4+
* Purpose:
5+
* Tests the helper functions for creating test scan data.
6+
*
7+
* Test Coverage:
8+
* - getSimpleCleanScan function
9+
* - getScanWithEnvVars function
10+
* - getScanWithMultiplePackages function
11+
*
12+
* Related Files:
13+
* - src/commands/scan/generate-report-test-helpers.mts (implementation)
14+
*/
15+
16+
import { describe, expect, it } from 'vitest'
17+
18+
import {
19+
getSimpleCleanScan,
20+
getScanWithEnvVars,
21+
getScanWithMultiplePackages,
22+
} from '../../../../src/commands/scan/generate-report-test-helpers.mts'
23+
24+
describe('generate-report-test-helpers', () => {
25+
describe('getSimpleCleanScan', () => {
26+
it('returns an array with one artifact', () => {
27+
const scan = getSimpleCleanScan()
28+
29+
expect(Array.isArray(scan)).toBe(true)
30+
expect(scan.length).toBe(1)
31+
})
32+
33+
it('returns artifact with no alerts', () => {
34+
const scan = getSimpleCleanScan()
35+
36+
expect(scan[0]!.alerts).toEqual([])
37+
})
38+
39+
it('returns artifact with expected structure', () => {
40+
const scan = getSimpleCleanScan()
41+
const artifact = scan[0]!
42+
43+
expect(artifact.type).toBe('npm')
44+
expect(artifact.name).toBe('tslib')
45+
expect(artifact.version).toBe('1.14.1')
46+
expect(artifact.score).toBeDefined()
47+
expect(artifact.manifestFiles).toBeDefined()
48+
})
49+
})
50+
51+
describe('getScanWithEnvVars', () => {
52+
it('returns an array with one artifact', () => {
53+
const scan = getScanWithEnvVars()
54+
55+
expect(Array.isArray(scan)).toBe(true)
56+
expect(scan.length).toBe(1)
57+
})
58+
59+
it('returns artifact with envVars alerts', () => {
60+
const scan = getScanWithEnvVars()
61+
const artifact = scan[0]!
62+
63+
expect(artifact.alerts!.length).toBe(2)
64+
expect(artifact.alerts![0]!.type).toBe('envVars')
65+
expect(artifact.alerts![1]!.type).toBe('envVars')
66+
})
67+
68+
it('returns alerts with start/end positions', () => {
69+
const scan = getScanWithEnvVars()
70+
const alert = scan[0]!.alerts![0]!
71+
72+
expect(alert.start).toBeDefined()
73+
expect(alert.end).toBeDefined()
74+
expect(typeof alert.start).toBe('number')
75+
expect(typeof alert.end).toBe('number')
76+
})
77+
})
78+
79+
describe('getScanWithMultiplePackages', () => {
80+
it('returns an array with multiple artifacts', () => {
81+
const scan = getScanWithMultiplePackages()
82+
83+
expect(Array.isArray(scan)).toBe(true)
84+
expect(scan.length).toBe(2)
85+
})
86+
87+
it('returns different packages', () => {
88+
const scan = getScanWithMultiplePackages()
89+
90+
expect(scan[0]!.name).toBe('tslib')
91+
expect(scan[1]!.name).toBe('lodash')
92+
})
93+
94+
it('returns artifacts with alerts', () => {
95+
const scan = getScanWithMultiplePackages()
96+
97+
expect(scan[0]!.alerts!.length).toBe(2)
98+
expect(scan[1]!.alerts!.length).toBe(1)
99+
})
100+
101+
it('returns artifacts with different versions', () => {
102+
const scan = getScanWithMultiplePackages()
103+
104+
expect(scan[0]!.version).toBe('1.14.1')
105+
expect(scan[1]!.version).toBe('4.17.21')
106+
})
107+
108+
it('returns artifacts with manifest files', () => {
109+
const scan = getScanWithMultiplePackages()
110+
111+
expect(scan[0]!.manifestFiles).toBeDefined()
112+
expect(scan[0]!.manifestFiles!.length).toBe(1)
113+
expect(scan[1]!.manifestFiles).toBeDefined()
114+
expect(scan[1]!.manifestFiles!.length).toBe(1)
115+
})
116+
})
117+
})

0 commit comments

Comments
 (0)