@elevenlabs/convai-cli
Version:
CLI tool to manage ElevenLabs conversational AI agents
385 lines • 17.5 kB
JavaScript
;
/**
* Integration tests for sync functionality with mocked API calls
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const utils_1 = require("../utils");
const templates_1 = require("../templates");
const elevenLabsApi = __importStar(require("../elevenlabs-api"));
const config = __importStar(require("../config"));
// Mock the entire elevenlabs-api module
jest.mock('../elevenlabs-api');
const mockedElevenLabsApi = elevenLabsApi;
// Mock the config module
jest.mock('../config');
const mockedConfig = config;
// Mock os module for config path
jest.mock('os', () => ({
...jest.requireActual('os'),
homedir: jest.fn()
}));
const mockedOs = os;
describe('Sync Integration Tests', () => {
let tempDir;
let agentsConfigPath;
let lockFilePath;
beforeEach(async () => {
// Create a temporary directory
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'convai-sync-test-'));
agentsConfigPath = path.join(tempDir, 'agents.json');
lockFilePath = path.join(tempDir, 'convai.lock');
// Mock os.homedir for config
mockedOs.homedir.mockReturnValue(tempDir);
// Mock config to return a valid API key
mockedConfig.getApiKey.mockResolvedValue('test-api-key');
// Mock ElevenLabs client
const mockClient = {
conversationalAi: {
agents: {
update: jest.fn()
}
}
};
mockedElevenLabsApi.getElevenLabsClient.mockResolvedValue(mockClient);
mockedElevenLabsApi.updateAgentApi.mockResolvedValue('agent_123');
// Clear all mocks
jest.clearAllMocks();
});
afterEach(async () => {
// Clean up temp directory
await fs.remove(tempDir);
jest.clearAllMocks();
});
describe('Config change detection and API calls', () => {
it('should call updateAgentApi when agent config changes', async () => {
// Setup: Create initial agent configuration
const agentName = 'Test Agent';
const agentId = 'agent_123';
const environment = 'prod';
// Create agents.json
const agentsConfig = {
agents: [{
name: agentName,
environments: {
[environment]: {
config: `agent_configs/${environment}/test_agent.json`
}
}
}]
};
await (0, utils_1.writeAgentConfig)(agentsConfigPath, agentsConfig);
// Create initial agent config
const initialConfig = (0, templates_1.getDefaultAgentTemplate)(agentName);
const configPath = path.join(tempDir, agentsConfig.agents[0].environments[environment].config);
await fs.ensureDir(path.dirname(configPath));
await (0, utils_1.writeAgentConfig)(configPath, initialConfig);
// Create lock file with existing agent (using a different hash to ensure change detection)
const lockData = {
agents: {
[agentName]: {
[environment]: {
id: agentId,
hash: 'old_hash_that_will_not_match'
}
}
},
tools: {}
};
await (0, utils_1.saveLockFile)(lockFilePath, lockData);
// Modify the agent config to trigger a change
const modifiedConfig = {
...initialConfig,
conversation_config: {
...initialConfig.conversation_config,
agent: {
...initialConfig.conversation_config.agent,
prompt: {
...initialConfig.conversation_config.agent.prompt,
prompt: 'Modified prompt for testing',
temperature: 0.5 // Changed from 0.0
}
}
}
};
await (0, utils_1.writeAgentConfig)(configPath, modifiedConfig);
// Import and call the sync logic
const { syncAgentsWithMocks } = await createSyncFunction();
// Execute sync
await syncAgentsWithMocks(tempDir, agentName, false, environment);
// Verify that updateAgentApi was called
expect(mockedElevenLabsApi.updateAgentApi).toHaveBeenCalledTimes(1);
expect(mockedElevenLabsApi.updateAgentApi).toHaveBeenCalledWith(expect.any(Object), // client
agentId, agentName, modifiedConfig.conversation_config, modifiedConfig.platform_settings, modifiedConfig.tags);
});
it('should not call updateAgentApi when config has not changed', async () => {
// Setup: Create agent configuration
const agentName = 'Test Agent';
const agentId = 'agent_123';
const environment = 'prod';
// Create agents.json
const agentsConfig = {
agents: [{
name: agentName,
environments: {
[environment]: {
config: `agent_configs/${environment}/test_agent.json`
}
}
}]
};
await (0, utils_1.writeAgentConfig)(agentsConfigPath, agentsConfig);
// Create agent config
const agentConfig = (0, templates_1.getDefaultAgentTemplate)(agentName);
const configPath = path.join(tempDir, agentsConfig.agents[0].environments[environment].config);
await fs.ensureDir(path.dirname(configPath));
await (0, utils_1.writeAgentConfig)(configPath, agentConfig);
// Create lock file with matching hash (no changes)
const configHash = (0, utils_1.calculateConfigHash)(agentConfig);
const lockData = {
agents: {
[agentName]: {
[environment]: {
id: agentId,
hash: configHash
}
}
},
tools: {}
};
await (0, utils_1.saveLockFile)(lockFilePath, lockData);
// Import and call the sync logic
const { syncAgentsWithMocks } = await createSyncFunction();
// Execute sync
await syncAgentsWithMocks(tempDir, agentName, false, environment);
// Verify that updateAgentApi was NOT called
expect(mockedElevenLabsApi.updateAgentApi).not.toHaveBeenCalled();
});
it('should update lock file hash after successful API call', async () => {
// Setup: Create agent with changed config
const agentName = 'Test Agent';
const agentId = 'agent_123';
const environment = 'prod';
// Create agents.json
const agentsConfig = {
agents: [{
name: agentName,
environments: {
[environment]: {
config: `agent_configs/${environment}/test_agent.json`
}
}
}]
};
await (0, utils_1.writeAgentConfig)(agentsConfigPath, agentsConfig);
// Create modified agent config
const modifiedConfig = (0, templates_1.getDefaultAgentTemplate)(agentName);
modifiedConfig.conversation_config.agent.prompt.prompt = 'Modified prompt';
const configPath = path.join(tempDir, agentsConfig.agents[0].environments[environment].config);
await fs.ensureDir(path.dirname(configPath));
await (0, utils_1.writeAgentConfig)(configPath, modifiedConfig);
// Create lock file with old hash
const lockData = {
agents: {
[agentName]: {
[environment]: {
id: agentId,
hash: 'old_hash_value'
}
}
},
tools: {}
};
await (0, utils_1.saveLockFile)(lockFilePath, lockData);
// Import and call the sync logic
const { syncAgentsWithMocks } = await createSyncFunction();
// Execute sync
await syncAgentsWithMocks(tempDir, agentName, false, environment);
// Verify lock file was updated with new hash
const updatedLockData = await (0, utils_1.loadLockFile)(lockFilePath);
const newHash = (0, utils_1.calculateConfigHash)(modifiedConfig);
expect(updatedLockData.agents[agentName][environment].hash).toBe(newHash);
expect(updatedLockData.agents[agentName][environment].hash).not.toBe('old_hash_value');
});
it('should handle API errors gracefully', async () => {
// Setup: Create agent configuration
const agentName = 'Test Agent';
const agentId = 'agent_123';
const environment = 'prod';
// Mock API to throw an error
mockedElevenLabsApi.updateAgentApi.mockRejectedValue(new Error('API Error'));
// Create agents.json
const agentsConfig = {
agents: [{
name: agentName,
environments: {
[environment]: {
config: `agent_configs/${environment}/test_agent.json`
}
}
}]
};
await (0, utils_1.writeAgentConfig)(agentsConfigPath, agentsConfig);
// Create modified agent config
const modifiedConfig = (0, templates_1.getDefaultAgentTemplate)(agentName);
modifiedConfig.conversation_config.agent.prompt.prompt = 'Modified prompt';
const configPath = path.join(tempDir, agentsConfig.agents[0].environments[environment].config);
await fs.ensureDir(path.dirname(configPath));
await (0, utils_1.writeAgentConfig)(configPath, modifiedConfig);
// Create lock file
const lockData = {
agents: {
[agentName]: {
[environment]: {
id: agentId,
hash: 'old_hash'
}
}
},
tools: {}
};
await (0, utils_1.saveLockFile)(lockFilePath, lockData);
// Import and call the sync logic
const { syncAgentsWithMocks } = await createSyncFunction();
// Execute sync and expect it to handle the error
await syncAgentsWithMocks(tempDir, agentName, false, environment);
// Verify API was called but failed
expect(mockedElevenLabsApi.updateAgentApi).toHaveBeenCalledTimes(1);
// Verify lock file was NOT updated due to error
const lockDataAfter = await (0, utils_1.loadLockFile)(lockFilePath);
expect(lockDataAfter.agents[agentName][environment].hash).toBe('old_hash');
});
});
});
// Helper function to create a testable sync function
async function createSyncFunction() {
// This mimics the core sync logic from cli.ts but in a testable way
async function syncAgentsWithMocks(projectPath, agentName, dryRun = false, environment) {
const AGENTS_CONFIG_FILE = "agents.json";
const LOCK_FILE = "convai.lock";
// Load agents configuration
const agentsConfigPath = path.join(projectPath, AGENTS_CONFIG_FILE);
const agentsConfig = await fs.readJson(agentsConfigPath);
const lockFilePath = path.join(projectPath, LOCK_FILE);
const lockData = await (0, utils_1.loadLockFile)(lockFilePath);
// Initialize ElevenLabs client
let client;
if (!dryRun) {
client = await mockedElevenLabsApi.getElevenLabsClient();
}
// Filter agents if specific agent name provided
let agentsToProcess = agentsConfig.agents;
if (agentName) {
agentsToProcess = agentsConfig.agents.filter((agent) => agent.name === agentName);
}
// Determine environments to sync
let environmentsToSync = [];
if (environment) {
environmentsToSync = [environment];
}
else {
const envSet = new Set();
for (const agentDef of agentsToProcess) {
if (agentDef.environments) {
Object.keys(agentDef.environments).forEach(env => envSet.add(env));
}
}
environmentsToSync = Array.from(envSet);
}
let changesMade = false;
for (const currentEnv of environmentsToSync) {
for (const agentDef of agentsToProcess) {
const agentDefName = agentDef.name;
// Get config path for current environment
let configPath;
if (agentDef.environments && currentEnv in agentDef.environments) {
configPath = agentDef.environments[currentEnv].config;
}
else {
continue;
}
// Check if config file exists
if (!configPath) {
continue;
}
const fullConfigPath = path.join(projectPath, configPath);
if (!(await fs.pathExists(fullConfigPath))) {
continue;
}
// Load agent config
const agentConfig = await fs.readJson(fullConfigPath);
// Calculate config hash
const configHash = (0, utils_1.calculateConfigHash)(agentConfig);
// Get environment-specific agent data from lock file
const lockedAgent = lockData.agents?.[agentDefName]?.[currentEnv];
let needsUpdate = true;
if (lockedAgent && lockedAgent.hash === configHash) {
needsUpdate = false;
}
if (!needsUpdate || dryRun) {
continue;
}
// Perform API operation
try {
const agentId = lockedAgent?.id;
// Extract config components
const conversationConfig = agentConfig.conversation_config || {};
const platformSettings = agentConfig.platform_settings;
const tags = agentConfig.tags || [];
const agentDisplayName = agentConfig.name || agentDefName;
if (agentId) {
// Update existing agent
await mockedElevenLabsApi.updateAgentApi(client, agentId, agentDisplayName, conversationConfig, platformSettings, tags);
(0, utils_1.updateAgentInLock)(lockData, agentDefName, currentEnv, agentId, configHash);
changesMade = true;
}
}
catch (error) {
// Log error but continue (matches CLI behavior)
console.log(`Error processing ${agentDefName}: ${error}`);
}
}
}
// Save lock file if changes were made
if (changesMade && !dryRun) {
await (0, utils_1.saveLockFile)(lockFilePath, lockData);
}
}
return { syncAgentsWithMocks };
}
//# sourceMappingURL=sync.integration.test.js.map