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).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).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); }); });