Files
calendar/packages/features/calAIPhone/AIPhoneServiceRegistry.test.ts
T
98b6d63164 refactor: apply biome formatting to packages/features (#27844)
* refactor: apply biome formatting to packages/features (batch 1 - small subdirs)

Format small subdirectories in packages/features: di, flags, holidays, oauth,
settings, users, assignment-reason, selectedCalendar, hashedLink, host, form,
form-builder, availability, data-table, pbac, schedules, troubleshooter,
eventtypes, calendar-subscription, and root-level files.

Also includes straggler apps/web BookEventForm.tsx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 2 - medium subdirs)

Format medium subdirectories in packages/features: auth, credentials,
calendars, routing-forms, routing-trace, attributes, watchlist, calAIPhone,
tasker, and webhooks.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 3 - bookings + insights)

Format bookings and insights subdirectories in packages/features.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 4 - ee)

Format packages/features/ee subdirectory covering billing, workflows,
organizations, teams, managed-event-types, round-robin, dsync,
integration-attribute-sync, and payments.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 5 - booking-audit part 1)

Format booking-audit di, actions, common, dto, repository, and types
subdirectories in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* refactor: apply biome formatting to packages/features (batch 6 - booking-audit part 2)

Format booking-audit service subdirectory in packages/features/booking-audit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 15:47:14 +01:00

460 lines
16 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from "vitest";
import {
AIPhoneServiceRegistry,
createAIPhoneServiceProvider,
createDefaultAIPhoneServiceProvider,
} from "./AIPhoneServiceRegistry";
import type {
AIPhoneServiceProvider,
AIPhoneServiceProviderFactory,
AIPhoneServiceProviderConfig,
} from "./interfaces/AIPhoneService.interface";
import { AIPhoneServiceProviderType } from "./interfaces/AIPhoneService.interface";
// Mock environment variables
vi.stubEnv("RETELL_AI_KEY", "test-api-key");
vi.mock("./providers/retellAI", () => ({
RetellAIPhoneServiceProviderFactory: vi.fn().mockImplementation(function () {
return {
create: vi.fn().mockReturnValue({
setupConfiguration: vi.fn(),
createPhoneCall: vi.fn(),
createPhoneNumber: vi.fn(),
}),
};
}),
}));
describe("AIPhoneServiceRegistry", () => {
let mockFactory: AIPhoneServiceProviderFactory;
let mockProvider: AIPhoneServiceProvider;
beforeEach(() => {
// Clear all factories before each test
AIPhoneServiceRegistry.clearProviders();
// Create mock factory and provider
mockProvider = {
setupConfiguration: vi.fn().mockResolvedValue({ llmId: "test-llm", agentId: "test-agent" }),
deleteConfiguration: vi
.fn()
.mockResolvedValue({ success: true, errors: [], deleted: { llm: true, agent: true } }),
updateModelConfiguration: vi.fn().mockResolvedValue({ llm_id: "test-llm" }),
getModelDetails: vi.fn().mockResolvedValue({ llm_id: "test-llm" }),
getAgent: vi.fn().mockResolvedValue({ agent_id: "test-agent" }),
updateAgent: vi.fn().mockResolvedValue({ agent_id: "test-agent" }),
createPhoneCall: vi.fn().mockResolvedValue({ call_id: "test-call" }),
createPhoneNumber: vi.fn().mockResolvedValue({ phone_number: "+1234567890" }),
deletePhoneNumber: vi.fn().mockResolvedValue(undefined),
getPhoneNumber: vi.fn().mockResolvedValue({ phone_number: "+1234567890" }),
updatePhoneNumber: vi.fn().mockResolvedValue({ phone_number: "+1234567890" }),
importPhoneNumber: vi.fn().mockResolvedValue({ phone_number: "+1234567890" }),
generatePhoneNumberCheckoutSession: vi
.fn()
.mockResolvedValue({ url: "test-url", message: "test-message" }),
cancelPhoneNumberSubscription: vi.fn().mockResolvedValue({ success: true, message: "test-message" }),
updatePhoneNumberWithAgents: vi.fn().mockResolvedValue({ message: "test-message" }),
listAgents: vi.fn().mockResolvedValue({ totalCount: 1, filtered: [] }),
getAgentWithDetails: vi.fn().mockResolvedValue({ agent_id: "test-agent" }),
createOutboundAgent: vi
.fn()
.mockResolvedValue({ id: "test-id", providerAgentId: "test-provider-id", message: "test-message" }),
updateAgentConfiguration: vi.fn().mockResolvedValue({ message: "test-message" }),
deleteAgent: vi.fn().mockResolvedValue({ message: "test-message" }),
createTestCall: vi
.fn()
.mockResolvedValue({ callId: "test-call-id", status: "test-status", message: "test-message" }),
};
mockFactory = {
create: vi.fn().mockReturnValue(mockProvider),
};
});
describe("initialize", () => {
it("should initialize with provided configuration", () => {
AIPhoneServiceRegistry.initialize({
defaultProvider: "test-provider",
providers: [
{ type: "test-provider", factory: mockFactory },
{ type: "another-provider", factory: mockFactory },
],
});
expect(AIPhoneServiceRegistry.isInitialized()).toBe(true);
expect(AIPhoneServiceRegistry.getDefaultProvider()).toBe("test-provider");
expect(AIPhoneServiceRegistry.getAvailableProviders()).toContain("test-provider");
expect(AIPhoneServiceRegistry.getAvailableProviders()).toContain("another-provider");
});
it("should set first provider as default if defaultProvider not specified", () => {
AIPhoneServiceRegistry.initialize({
providers: [
{ type: "first-provider", factory: mockFactory },
{ type: "second-provider", factory: mockFactory },
],
});
expect(AIPhoneServiceRegistry.getDefaultProvider()).toBe("first-provider");
});
it("should allow initialization without providers", () => {
AIPhoneServiceRegistry.initialize();
expect(AIPhoneServiceRegistry.isInitialized()).toBe(true);
expect(AIPhoneServiceRegistry.getDefaultProvider()).toBe(AIPhoneServiceProviderType.RETELL_AI);
});
});
describe("registerProvider", () => {
it("should register a provider factory", () => {
AIPhoneServiceRegistry.registerProvider("test-provider", mockFactory);
expect(AIPhoneServiceRegistry.isProviderRegistered("test-provider")).toBe(true);
expect(AIPhoneServiceRegistry.getAvailableProviders()).toContain("test-provider");
});
it("should keep default as retellAI even after registering provider", () => {
AIPhoneServiceRegistry.registerProvider("test-provider", mockFactory);
expect(AIPhoneServiceRegistry.getDefaultProvider()).toBe(AIPhoneServiceProviderType.RETELL_AI);
});
it("should allow overriding existing provider", () => {
const newMockFactory: AIPhoneServiceProviderFactory = {
create: vi.fn().mockReturnValue(mockProvider),
};
AIPhoneServiceRegistry.registerProvider("test-provider", mockFactory);
AIPhoneServiceRegistry.registerProvider("test-provider", newMockFactory);
const retrievedFactory = AIPhoneServiceRegistry.getProviderFactory("test-provider");
expect(retrievedFactory).toBe(newMockFactory);
});
});
describe("getProviderFactory", () => {
it("should return registered factory", () => {
AIPhoneServiceRegistry.registerProvider("test-provider", mockFactory);
const retrievedFactory = AIPhoneServiceRegistry.getProviderFactory("test-provider");
expect(retrievedFactory).toBe(mockFactory);
});
it("should return undefined for unregistered provider", () => {
const retrievedFactory = AIPhoneServiceRegistry.getProviderFactory("nonexistent");
expect(retrievedFactory).toBeUndefined();
});
});
describe("createProvider", () => {
it("should create provider instance using registered factory", () => {
const config: AIPhoneServiceProviderConfig = { apiKey: "test-key" };
AIPhoneServiceRegistry.registerProvider("test-provider", mockFactory);
const provider = AIPhoneServiceRegistry.createProvider("test-provider", config);
expect(mockFactory.create).toHaveBeenCalledWith(config);
expect(provider).toBe(mockProvider);
});
it("should throw error for unregistered provider", () => {
const config: AIPhoneServiceProviderConfig = { apiKey: "test-key" };
expect(() => {
AIPhoneServiceRegistry.createProvider("nonexistent", config);
}).toThrow("AI phone service provider 'nonexistent' not found. Available providers: ");
});
it("should include available providers in error message when factory not found", () => {
AIPhoneServiceRegistry.registerProvider("provider1", mockFactory);
AIPhoneServiceRegistry.registerProvider("provider2", mockFactory);
expect(() => {
AIPhoneServiceRegistry.createProvider("nonexistent", {});
}).toThrow("Available providers: provider1, provider2");
});
});
describe("createDefaultProvider", () => {
it("should create provider using default provider type", () => {
const config: AIPhoneServiceProviderConfig = { apiKey: "test-key" };
AIPhoneServiceRegistry.initialize({
defaultProvider: "test-provider",
providers: [{ type: "test-provider", factory: mockFactory }],
});
const provider = AIPhoneServiceRegistry.createDefaultProvider(config);
expect(mockFactory.create).toHaveBeenCalledWith(config);
expect(provider).toBe(mockProvider);
});
it("should throw error when default provider is not registered", () => {
const config: AIPhoneServiceProviderConfig = { apiKey: "test-key" };
expect(() => {
AIPhoneServiceRegistry.createDefaultProvider(config);
}).toThrow("AI phone service provider 'retellAI' not found");
});
});
describe("setDefaultProvider", () => {
it("should set default provider when provider is registered", () => {
AIPhoneServiceRegistry.registerProvider("new-default", mockFactory);
AIPhoneServiceRegistry.setDefaultProvider("new-default");
expect(AIPhoneServiceRegistry.getDefaultProvider()).toBe("new-default");
});
it("should throw error when setting unregistered provider as default", () => {
expect(() => {
AIPhoneServiceRegistry.setDefaultProvider("nonexistent");
}).toThrow("Cannot set default provider to 'nonexistent' - provider not registered");
});
});
describe("getDefaultProvider", () => {
it("should return current default provider", () => {
AIPhoneServiceRegistry.initialize({
defaultProvider: "test-provider",
providers: [{ type: "test-provider", factory: mockFactory }],
});
expect(AIPhoneServiceRegistry.getDefaultProvider()).toBe("test-provider");
});
it("should return retellAI as default provider", () => {
expect(AIPhoneServiceRegistry.getDefaultProvider()).toBe(AIPhoneServiceProviderType.RETELL_AI);
});
});
describe("getAvailableProviders", () => {
it("should return empty array when no providers registered", () => {
expect(AIPhoneServiceRegistry.getAvailableProviders()).toEqual([]);
});
it("should return array of registered provider names", () => {
AIPhoneServiceRegistry.registerProvider("provider1", mockFactory);
AIPhoneServiceRegistry.registerProvider("provider2", mockFactory);
const providers = AIPhoneServiceRegistry.getAvailableProviders();
expect(providers).toContain("provider1");
expect(providers).toContain("provider2");
expect(providers).toHaveLength(2);
});
});
describe("isProviderRegistered", () => {
it("should return true for registered provider", () => {
AIPhoneServiceRegistry.registerProvider("test-provider", mockFactory);
expect(AIPhoneServiceRegistry.isProviderRegistered("test-provider")).toBe(true);
});
it("should return false for unregistered provider", () => {
expect(AIPhoneServiceRegistry.isProviderRegistered("nonexistent")).toBe(false);
});
});
});
describe("createAIPhoneServiceProvider", () => {
let mockFactory: AIPhoneServiceProviderFactory;
let mockProvider: AIPhoneServiceProvider;
beforeEach(() => {
// Clear all factories before each test
AIPhoneServiceRegistry.clearProviders();
mockProvider = {
setupConfiguration: vi.fn(),
deleteConfiguration: vi.fn(),
updateModelConfiguration: vi.fn(),
getModelDetails: vi.fn(),
getAgent: vi.fn(),
updateAgent: vi.fn(),
createPhoneCall: vi.fn(),
createPhoneNumber: vi.fn(),
deletePhoneNumber: vi.fn(),
getPhoneNumber: vi.fn(),
updatePhoneNumber: vi.fn(),
importPhoneNumber: vi.fn(),
generatePhoneNumberCheckoutSession: vi.fn(),
cancelPhoneNumberSubscription: vi.fn(),
updatePhoneNumberWithAgents: vi.fn(),
listAgents: vi.fn(),
getAgentWithDetails: vi.fn(),
createOutboundAgent: vi.fn(),
updateAgentConfiguration: vi.fn(),
deleteAgent: vi.fn(),
createTestCall: vi.fn(),
};
mockFactory = {
create: vi.fn().mockReturnValue(mockProvider),
};
});
it("should automatically initialize registry when not initialized", () => {
// Registry starts uninitialized
expect(AIPhoneServiceRegistry.isInitialized()).toBe(false);
// Should automatically initialize the registry and create provider
expect(() => {
createAIPhoneServiceProvider({
config: { apiKey: "test-key" },
});
}).not.toThrow();
// Registry should now be initialized
expect(AIPhoneServiceRegistry.isInitialized()).toBe(true);
});
it("should create provider with specified type and config", () => {
AIPhoneServiceRegistry.initialize({
providers: [{ type: "custom-provider", factory: mockFactory }],
});
// For custom providers, we must provide API key in config since there's no env var mapping
const provider = createAIPhoneServiceProvider({
providerType: "custom-provider",
config: { apiKey: "custom-key", enableLogging: false },
});
expect(mockFactory.create).toHaveBeenCalledWith({
apiKey: "custom-key",
enableLogging: false,
});
expect(provider).toBe(mockProvider);
});
it("should use default provider when type not specified", () => {
AIPhoneServiceRegistry.initialize({
defaultProvider: AIPhoneServiceProviderType.RETELL_AI, // Use RETELL_AI as default since we have its API key
providers: [{ type: AIPhoneServiceProviderType.RETELL_AI, factory: mockFactory }],
});
const provider = createAIPhoneServiceProvider();
expect(mockFactory.create).toHaveBeenCalledWith({
apiKey: "test-api-key",
enableLogging: true, // Should be true when NODE_ENV is not production
});
expect(provider).toBe(mockProvider);
});
it("should merge provided config with defaults", () => {
AIPhoneServiceRegistry.initialize({
providers: [{ type: AIPhoneServiceProviderType.RETELL_AI, factory: mockFactory }],
});
createAIPhoneServiceProvider({
providerType: AIPhoneServiceProviderType.RETELL_AI,
config: { enableLogging: false },
});
expect(mockFactory.create).toHaveBeenCalledWith({
apiKey: "test-api-key",
enableLogging: false,
});
});
it("should override default apiKey with provided config", () => {
AIPhoneServiceRegistry.initialize({
providers: [{ type: AIPhoneServiceProviderType.RETELL_AI, factory: mockFactory }],
});
createAIPhoneServiceProvider({
providerType: AIPhoneServiceProviderType.RETELL_AI,
config: { apiKey: "override-key" },
});
expect(mockFactory.create).toHaveBeenCalledWith({
apiKey: "override-key",
enableLogging: true,
});
});
it("should throw error when default provider is not registered", () => {
AIPhoneServiceRegistry.initialize(); // Initialize without providers
expect(() => {
createAIPhoneServiceProvider();
}).toThrow("AI phone service provider 'retellAI' not found");
});
});
describe("createDefaultAIPhoneServiceProvider", () => {
let mockFactory: AIPhoneServiceProviderFactory;
let mockProvider: AIPhoneServiceProvider;
beforeEach(() => {
AIPhoneServiceRegistry.clearProviders();
mockProvider = {
setupConfiguration: vi.fn(),
deleteConfiguration: vi.fn(),
updateModelConfiguration: vi.fn(),
getModelDetails: vi.fn(),
getAgent: vi.fn(),
updateAgent: vi.fn(),
createPhoneCall: vi.fn(),
createPhoneNumber: vi.fn(),
deletePhoneNumber: vi.fn(),
getPhoneNumber: vi.fn(),
updatePhoneNumber: vi.fn(),
importPhoneNumber: vi.fn(),
generatePhoneNumberCheckoutSession: vi.fn(),
cancelPhoneNumberSubscription: vi.fn(),
updatePhoneNumberWithAgents: vi.fn(),
listAgents: vi.fn(),
getAgentWithDetails: vi.fn(),
createOutboundAgent: vi.fn(),
updateAgentConfiguration: vi.fn(),
deleteAgent: vi.fn(),
createTestCall: vi.fn(),
};
mockFactory = {
create: vi.fn().mockReturnValue(mockProvider),
};
AIPhoneServiceRegistry.initialize({
defaultProvider: AIPhoneServiceProviderType.RETELL_AI,
providers: [{ type: AIPhoneServiceProviderType.RETELL_AI, factory: mockFactory }],
});
});
it("should create provider using default configuration", () => {
const provider = createDefaultAIPhoneServiceProvider();
expect(mockFactory.create).toHaveBeenCalledWith({
apiKey: "test-api-key",
enableLogging: true,
});
expect(provider).toBe(mockProvider);
});
it("should merge provided config with defaults", () => {
const customConfig = { enableLogging: false, apiKey: "custom-key" };
const provider = createDefaultAIPhoneServiceProvider(customConfig);
expect(mockFactory.create).toHaveBeenCalledWith({
apiKey: "custom-key",
enableLogging: false,
});
expect(provider).toBe(mockProvider);
});
it("should use default provider type internally", () => {
// This test ensures createDefaultAIPhoneServiceProvider calls createAIPhoneServiceProvider correctly
const provider = createDefaultAIPhoneServiceProvider();
expect(provider).toBe(mockProvider);
expect(mockFactory.create).toHaveBeenCalledTimes(1);
});
});