File size: 2,673 Bytes
34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 6dd9bad 34c79d1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | 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);
});
});
|