Skip to content

Commit 0c0ff74

Browse files
committed
fix(ci): resolve default org by slug, not display name
getDefaultOrgSlug exported the org's display name as SOCKET_ORG_SLUG for the Coana CLI, but Coana resolves the org by its URL-safe slug. The display name can differ from the slug and may be null, producing a wrong or empty org identifier (and a hard failure for tokens that can only see one org). Use the slug field instead and add unit coverage.
1 parent e75b2d6 commit 0c0ff74

2 files changed

Lines changed: 94 additions & 1 deletion

File tree

src/commands/ci/fetch-default-org-slug.mts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ export async function getDefaultOrgSlug(
4545
}
4646
}
4747

48-
const slug = (organizations as any)[keys[0]!]?.name ?? undefined
48+
// Use the org's URL-safe `slug`, not its display `name`: this value is
49+
// exported as SOCKET_ORG_SLUG for the Coana CLI, which resolves the org by
50+
// slug. `name` is the human-readable display name (and may be null), so using
51+
// it here produced a wrong/empty org identifier.
52+
const slug = organizations[0]?.slug ?? undefined
4953
if (!slug) {
5054
return {
5155
ok: false,
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest'
2+
3+
import { getDefaultOrgSlug } from './fetch-default-org-slug.mts'
4+
import { fetchOrganization } from '../organization/fetch-organization-list.mts'
5+
import { getConfigValueOrUndef } from '../../utils/config.mts'
6+
7+
vi.mock('../organization/fetch-organization-list.mts', () => ({
8+
fetchOrganization: vi.fn(),
9+
}))
10+
vi.mock('../../utils/config.mts', () => ({
11+
getConfigValueOrUndef: vi.fn(() => undefined),
12+
}))
13+
// Keep SOCKET_CLI_ORG_SLUG unset so the resolver falls through to the API path.
14+
vi.mock('../../constants.mts', () => ({
15+
default: { ENV: {} },
16+
}))
17+
18+
describe('getDefaultOrgSlug', () => {
19+
beforeEach(() => {
20+
vi.clearAllMocks()
21+
vi.mocked(getConfigValueOrUndef).mockReturnValue(undefined)
22+
})
23+
24+
it('resolves the org slug (not the display name) from the API', async () => {
25+
vi.mocked(fetchOrganization).mockResolvedValue({
26+
ok: true,
27+
data: {
28+
organizations: [
29+
{
30+
id: 'org-id',
31+
name: 'Display Name',
32+
image: null,
33+
plan: 'free',
34+
slug: 'my-org-slug',
35+
},
36+
],
37+
},
38+
} as any)
39+
40+
const result = await getDefaultOrgSlug()
41+
42+
expect(result.ok).toBe(true)
43+
// Regression guard: must be the URL-safe slug, never the display name.
44+
expect(result.ok && result.data).toBe('my-org-slug')
45+
})
46+
47+
it('resolves the slug even when the display name is null', async () => {
48+
vi.mocked(fetchOrganization).mockResolvedValue({
49+
ok: true,
50+
data: {
51+
organizations: [
52+
{
53+
id: 'org-id',
54+
name: null,
55+
image: null,
56+
plan: 'free',
57+
slug: 'slug-only',
58+
},
59+
],
60+
},
61+
} as any)
62+
63+
const result = await getDefaultOrgSlug()
64+
65+
expect(result.ok).toBe(true)
66+
expect(result.ok && result.data).toBe('slug-only')
67+
})
68+
69+
it('prefers the defaultOrg config value without calling the API', async () => {
70+
vi.mocked(getConfigValueOrUndef).mockReturnValue('configured-org')
71+
72+
const result = await getDefaultOrgSlug()
73+
74+
expect(result.ok).toBe(true)
75+
expect(result.ok && result.data).toBe('configured-org')
76+
expect(fetchOrganization).not.toHaveBeenCalled()
77+
})
78+
79+
it('fails when the API returns no organizations', async () => {
80+
vi.mocked(fetchOrganization).mockResolvedValue({
81+
ok: true,
82+
data: { organizations: [] },
83+
} as any)
84+
85+
const result = await getDefaultOrgSlug()
86+
87+
expect(result.ok).toBe(false)
88+
})
89+
})

0 commit comments

Comments
 (0)