CognxSafeTrack
feat: backlog P0βP3 β toast system, payments, tenant isolation, feedback handler, i18n parity
6dd9bad | import { describe, it, expect, vi, beforeEach } from 'vitest'; | |
| // Mock infrastructure before importing aiService | |
| vi.mock('../src/services/queue', () => ({ | |
| redis: { get: vi.fn(), set: vi.fn() }, | |
| whatsappQueue: { add: vi.fn() }, | |
| notificationQueue: { add: vi.fn() }, | |
| scheduleEmail: vi.fn(), | |
| scheduleBroadcast: vi.fn(), | |
| scheduleCampaign: vi.fn(), | |
| })); | |
| vi.mock('../src/services/prisma', () => ({ | |
| prisma: { organization: { findUnique: vi.fn() } } | |
| })); | |
| vi.mock('../src/services/organization', async () => { | |
| const actual = await vi.importActual('../src/services/organization') as object; | |
| return { ...actual, getTenantSecrets: vi.fn(), getOrganizationId: vi.fn() }; | |
| }); | |
| import { aiService } from '../src/services/ai'; | |
| import { getTenantSecrets } from '../src/services/organization'; | |
| import { ProviderCapability } from '@repo/ai-sdk'; | |
| describe('AIService - Multi-Tenant Isolation', () => { | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| }); | |
| it('calls getTenantSecrets with the org ID and builds a tenant registry', async () => { | |
| const orgId = 'org-premium-123'; | |
| const customKey = 'sk-custom-org-key'; | |
| (getTenantSecrets as ReturnType<typeof vi.fn>).mockResolvedValue({ | |
| openAiApiKey: customKey, | |
| googleAiApiKey: null | |
| }); | |
| const providers = await (aiService as any).getProvidersForTenant(ProviderCapability.TEXT, orgId); | |
| expect(getTenantSecrets).toHaveBeenCalledWith(orgId); | |
| // Tenant provider should be registered and sorted first (priority 500) | |
| const tenantProvider = providers.find((p: any) => p.name === 'OPENAI_TENANT'); | |
| expect(tenantProvider).toBeDefined(); | |
| }); | |
| it('returns global providers when org has no custom keys', async () => { | |
| const orgId = 'org-free-456'; | |
| (getTenantSecrets as ReturnType<typeof vi.fn>).mockResolvedValue({ | |
| openAiApiKey: null, | |
| googleAiApiKey: null | |
| }); | |
| const providers = await (aiService as any).getProvidersForTenant(ProviderCapability.TEXT, orgId); | |
| expect(getTenantSecrets).toHaveBeenCalledWith(orgId); | |
| // No tenant-specific providers β all names are global | |
| const tenantProvider = providers.find((p: any) => p.name.endsWith('_TENANT')); | |
| expect(tenantProvider).toBeUndefined(); | |
| }); | |
| it('skips getTenantSecrets when no organizationId is provided', async () => { | |
| const providers = await (aiService as any).getProvidersForTenant(ProviderCapability.TEXT, undefined); | |
| expect(getTenantSecrets).not.toHaveBeenCalled(); | |
| expect(Array.isArray(providers)).toBe(true); | |
| }); | |
| }); | |