edtech / apps /api /test /multi-tenant.test.ts
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);
});
});