Files
insights/services/mcp/tests/unit/organizations-get.test.ts
zeekay 40d120dbf7 refactor(api): say /v1/ in the Python callers
Every first-party Python caller and test now names the mount the API actually
answers on. Mechanical: URL literals only, and only where the /api/ was ours --
a preceding word character means it was a module path like insights/api/ or a
third-party host like slack.com/api/, and those are untouched.

Left saying /api/, each for a reason rather than for safety:
- products/warehouse_sources/.../sources/** is a catalogue of vendor endpoints
- services/llm-gateway deploys separately and is under a code freeze, so moving
  its two calls would break it during the skew
- the mcp_store and Slack/Teams redirect_uris are registered with third parties
  and must byte-match what was registered
- the Wizard and Raycast client-metadata paths are OAuth client_ids
- /api/surveys and /api/feature_flag/local_evaluation are SDK aliases the
  ingress routes to the Rust fleets, held by released SDK versions

Test URLconfs moved with the requests that call them, and the Datastore
user-selection fixtures now carry the leading slash that request.path has.
2026-08-08 18:38:44 -07:00

79 lines
2.8 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
import getOrganizationsTool from '@/tools/organizations/getOrganizations'
import type { Context } from '@/tools/types'
function createMockContext(list: ReturnType<typeof vi.fn>): Context {
return {
api: { organizations: () => ({ list }) },
cache: {},
stateManager: {},
env: {},
sessionManager: {},
getDistinctId: async () => 'test-distinct-id',
trackEvent: async () => {},
} as unknown as Context
}
describe('organizations-get', () => {
const tool = getOrganizationsTool()
it('is registered under the expected tool name', () => {
expect(tool.name).toBe('organizations-get')
})
it('returns accessible organizations filtered to the safe id/name/slug/membership fields', async () => {
// The `/v1/organizations/` rows carry the full serializer, so the raw
// data includes billing/security fields the tool must not expose.
const orgs = [
{
id: 'org-a',
name: 'Org A',
slug: 'org-a',
membership_level: 8,
customer_id: 'cus_secret',
available_product_features: [{ key: 'sso' }],
},
{
id: 'org-b',
name: 'Org B',
slug: 'org-b',
membership_level: 1,
enforce_2fa: true,
is_hipaa: true,
},
]
const list = vi.fn().mockResolvedValue({ success: true, data: orgs })
const result = await tool.handler(createMockContext(list), {})
expect(result).toEqual([
{ id: 'org-a', name: 'Org A', slug: 'org-a', membership_level: 8 },
{ id: 'org-b', name: 'Org B', slug: 'org-b', membership_level: 1 },
])
for (const org of result) {
expect(org).not.toHaveProperty('customer_id')
expect(org).not.toHaveProperty('available_product_features')
expect(org).not.toHaveProperty('enforce_2fa')
expect(org).not.toHaveProperty('is_hipaa')
}
})
it('throws a descriptive error and preserves the API error cause when the list request fails', async () => {
const apiError = new Error('boom')
const list = vi.fn().mockResolvedValue({ success: false, error: apiError })
let caught: (Error & { cause?: unknown }) | undefined
try {
await tool.handler(createMockContext(list), {})
} catch (e) {
caught = e as Error & { cause?: unknown }
}
expect(caught?.message).toMatch(/Failed to get organizations: boom/)
// Preserving the cause lets handleToolError classify the recoverable 4xx
// and keep it out of exception tracking.
expect(caught?.cause).toBe(apiError)
})
})