/** * Test script for entity-specific sync with Autotask * Run with: npx tsx dev/test-entity-specific-sync.ts * * This script tests the entity-specific sync functionality by: * 1. Syncing individual entities * 2. Syncing multiple selected entities * 3. Verifying dependency ordering * 4. Testing different entity types */ import dotenv from 'dotenv'; import path from 'path'; // Load environment variables from .env.local FIRST dotenv.config({ path: path.resolve(__dirname, '../.env.local') }); import { AutotaskClient } from '../lib/services/autotask-client'; import { createSyncService } from '../lib/services/sync-service'; import { EntityType, SyncType } from '../lib/types/sync'; import postgresClient from '../lib/services/postgres-client'; async function testEntitySpecificSync() { console.log('šŸ” Testing Entity-Specific Sync with Autotask\n'); // Validate required environment variables if (!process.env.AUTOTASK_API_URL || !process.env.AUTOTASK_USERNAME || !process.env.AUTOTASK_SECRET || !process.env.AUTOTASK_API_INTEGRATION_CODE) { console.error('āŒ Missing required Autotask environment variables'); process.exit(1); } try { // Test 1: Database connection console.log('Test 1: Verify database connection'); const dbConnected = await postgresClient.testConnection(); if (!dbConnected) { console.error('āŒ Database connection failed'); process.exit(1); } console.log('āœ… Database connection successful\n'); // Test 2: Initialize clients console.log('Test 2: Initialize Autotask client and sync service'); const autotaskClient = new AutotaskClient({ apiUrl: process.env.AUTOTASK_API_URL!, username: process.env.AUTOTASK_USERNAME!, password: process.env.AUTOTASK_SECRET!, apiIntegrationCode: process.env.AUTOTASK_API_INTEGRATION_CODE!, }); const syncService = createSyncService(autotaskClient); console.log('āœ… Clients initialized\n'); // Test 3: Sync a single entity (Companies) console.log('Test 3: Sync single entity - Companies'); console.log('Testing entity-specific sync with one entity\n'); const singleEntityStart = Date.now(); const singleResult = await syncService.syncEntities( [EntityType.COMPANIES], SyncType.ENTITY_SPECIFIC, 'test-single-entity' ); const singleDuration = Date.now() - singleEntityStart; console.log('\n=== SINGLE ENTITY SYNC RESULTS ==='); console.log(`Sync ID: ${singleResult.syncId}`); console.log(`Sync Type: ${singleResult.syncType}`); console.log(`Status: ${singleResult.status}`); console.log(`Duration: ${singleDuration}ms`); console.log(`Entities synced: ${singleResult.entities.length}`); singleResult.entities.forEach(entity => { const status = entity.success ? 'āœ…' : 'āŒ'; console.log(`${status} ${entity.entityType}: +${entity.recordsAdded} ~${entity.recordsUpdated} -${entity.recordsDeleted}`); if (entity.error) { console.log(` Error: ${entity.error}`); } }); if (singleResult.errors.length > 0) { console.log('\nErrors:'); singleResult.errors.forEach(err => console.log(` - ${err}`)); } console.log(); // Test 4: Verify sync history for single entity console.log('Test 4: Verify sync history for single entity'); const companiesHistory = await syncService.getSyncHistory(1, EntityType.COMPANIES); if (companiesHistory.length > 0) { const latest = companiesHistory[0]; console.log(`āœ… Latest companies sync:`); console.log(` Sync Type: ${latest.sync_type}`); console.log(` Status: ${latest.status}`); if (latest.sync_type === 'entity-specific') { console.log(` āœ… Confirmed: Sync type is entity-specific`); } else { console.log(` āš ļø Expected entity-specific, got ${latest.sync_type}`); } } console.log(); // Test 5: Sync multiple entities console.log('Test 5: Sync multiple entities - Companies, Resources, Statuses'); console.log('Testing entity-specific sync with multiple entities\n'); const multiEntityStart = Date.now(); const multiResult = await syncService.syncEntities( [EntityType.COMPANIES, EntityType.RESOURCES, EntityType.STATUSES], SyncType.ENTITY_SPECIFIC, 'test-multi-entity' ); const multiDuration = Date.now() - multiEntityStart; console.log('\n=== MULTIPLE ENTITY SYNC RESULTS ==='); console.log(`Sync ID: ${multiResult.syncId}`); console.log(`Sync Type: ${multiResult.syncType}`); console.log(`Status: ${multiResult.status}`); console.log(`Duration: ${multiDuration}ms`); console.log(`Entities synced: ${multiResult.entities.length}`); console.log(`\nTotal Records:`); console.log(` Added: ${multiResult.totalRecordsAdded}`); console.log(` Updated: ${multiResult.totalRecordsUpdated}`); console.log(` Deleted: ${multiResult.totalRecordsDeleted}`); console.log('\n=== ENTITY DETAILS ==='); multiResult.entities.forEach(entity => { const status = entity.success ? 'āœ…' : 'āŒ'; console.log(`${status} ${entity.entityType}:`); console.log(` Added: ${entity.recordsAdded}, Updated: ${entity.recordsUpdated}, Deleted: ${entity.recordsDeleted}`); console.log(` Duration: ${entity.duration}ms`); if (entity.error) { console.log(` Error: ${entity.error}`); } }); if (multiResult.errors.length > 0) { console.log('\nErrors:'); multiResult.errors.forEach(err => console.log(` - ${err}`)); } console.log(); // Test 6: Verify entity ordering console.log('Test 6: Verify entity sync ordering'); console.log('Entities should sync in dependency order (companies first, then dependent entities)'); const entityOrder = multiResult.entities.map(e => e.entityType); console.log(`Actual sync order: ${entityOrder.join(' → ')}`); // Companies should come before resources and statuses const companiesIndex = entityOrder.indexOf(EntityType.COMPANIES); const resourcesIndex = entityOrder.indexOf(EntityType.RESOURCES); const statusesIndex = entityOrder.indexOf(EntityType.STATUSES); if (companiesIndex !== -1 && resourcesIndex !== -1 && companiesIndex < resourcesIndex) { console.log('āœ… Companies synced before Resources (correct dependency order)'); } else { console.log('āš ļø Dependency ordering may need verification'); } console.log(); // Test 7: Test with picklist entities console.log('Test 7: Sync picklist entities - Statuses, IssueTypes, WorkTypes'); console.log('Testing entity-specific sync with picklist/lookup entities\n'); const picklistStart = Date.now(); const picklistResult = await syncService.syncEntities( [EntityType.STATUSES, EntityType.ISSUE_TYPES, EntityType.WORK_TYPES], SyncType.ENTITY_SPECIFIC, 'test-picklist-entities' ); const picklistDuration = Date.now() - picklistStart; console.log('\n=== PICKLIST ENTITY SYNC RESULTS ==='); console.log(`Sync ID: ${picklistResult.syncId}`); console.log(`Status: ${picklistResult.status}`); console.log(`Duration: ${picklistDuration}ms`); console.log(`Entities synced: ${picklistResult.entities.length}`); picklistResult.entities.forEach(entity => { const status = entity.success ? 'āœ…' : 'āŒ'; console.log(`${status} ${entity.entityType}: +${entity.recordsAdded} ~${entity.recordsUpdated}`); if (entity.error) { console.log(` Error: ${entity.error}`); } }); console.log(); // Test 8: Verify sync history for multiple entities console.log('Test 8: Verify sync history for all tested entities'); const entities = [EntityType.COMPANIES, EntityType.RESOURCES, EntityType.STATUSES]; for (const entity of entities) { const history = await syncService.getSyncHistory(1, entity); if (history.length > 0) { const latest = history[0]; console.log(`āœ… ${entity}: ${latest.status} (${latest.sync_type})`); } else { console.log(`āš ļø ${entity}: No sync history found`); } } console.log(); // Test 9: Test entity-specific sync with full mode console.log('Test 9: Entity-specific sync with FULL sync type'); console.log('Testing that entity-specific can use full sync mode\n'); const fullModeResult = await syncService.syncEntities( [EntityType.STATUSES], SyncType.FULL, 'test-entity-full-mode' ); console.log(`āœ… Entity-specific full sync completed: ${fullModeResult.status}`); console.log(` Sync Type: ${fullModeResult.syncType}`); console.log(` Entities: ${fullModeResult.entities.map(e => e.entityType).join(', ')}`); console.log(); // Test 10: Test entity-specific sync with incremental mode console.log('Test 10: Entity-specific sync with INCREMENTAL sync type'); console.log('Testing that entity-specific can use incremental sync mode\n'); const incrementalModeResult = await syncService.syncEntities( [EntityType.COMPANIES], SyncType.INCREMENTAL, 'test-entity-incremental-mode' ); console.log(`āœ… Entity-specific incremental sync completed: ${incrementalModeResult.status}`); console.log(` Sync Type: ${incrementalModeResult.syncType}`); console.log(` Entities: ${incrementalModeResult.entities.map(e => e.entityType).join(', ')}`); console.log(); // Test 11: Count records in database for each entity console.log('Test 11: Verify data in database for synced entities'); const entityTables = [ { entity: EntityType.COMPANIES, table: 'companies' }, { entity: EntityType.RESOURCES, table: 'resources' }, { entity: EntityType.STATUSES, table: 'statuses' }, { entity: EntityType.ISSUE_TYPES, table: 'issue_types' }, { entity: EntityType.WORK_TYPES, table: 'work_types' }, ]; for (const { entity, table } of entityTables) { try { const count = await postgresClient.count(table, { is_deleted: false }); console.log(`āœ… ${entity}: ${count} records in database`); } catch (error) { console.log(`āš ļø ${entity}: Could not count records (${error instanceof Error ? error.message : 'unknown error'})`); } } console.log(); // Test 12: Test error handling for invalid entity console.log('Test 12: Test error handling with dependent entities'); console.log('Testing sync of entities that depend on others (e.g., Tickets depend on Companies)\n'); const dependentResult = await syncService.syncEntities( [EntityType.TICKETS, EntityType.COMPANIES], SyncType.ENTITY_SPECIFIC, 'test-dependent-entities' ); console.log(`āœ… Dependent entity sync completed: ${dependentResult.status}`); console.log(` Entities synced in order: ${dependentResult.entities.map(e => e.entityType).join(' → ')}`); // Verify companies came before tickets const syncedOrder = dependentResult.entities.map(e => e.entityType); const companiesIdx = syncedOrder.indexOf(EntityType.COMPANIES); const ticketsIdx = syncedOrder.indexOf(EntityType.TICKETS); if (companiesIdx !== -1 && ticketsIdx !== -1 && companiesIdx < ticketsIdx) { console.log('āœ… Dependency ordering respected: Companies synced before Tickets'); } else { console.log('āš ļø Dependency ordering may need attention'); } console.log(); // Summary console.log('šŸŽ‰ Entity-specific sync tests completed!'); console.log('\n=== SUMMARY ==='); console.log('āœ… Single entity sync working'); console.log('āœ… Multiple entity sync working'); console.log('āœ… Picklist entity sync working'); console.log('āœ… Entity-specific with FULL mode working'); console.log('āœ… Entity-specific with INCREMENTAL mode working'); console.log('āœ… Sync history tracking per entity'); console.log('āœ… Dependency ordering verified'); console.log('āœ… Database records verified'); // Test statistics console.log('\n=== TEST STATISTICS ==='); const allResults = [singleResult, multiResult, picklistResult, fullModeResult, incrementalModeResult, dependentResult]; const totalSyncs = allResults.length; const successfulSyncs = allResults.filter(r => r.status === 'completed').length; const failedSyncs = allResults.filter(r => r.status === 'failed').length; const totalEntitiesSynced = allResults.reduce((sum, r) => sum + r.entities.length, 0); const successfulEntities = allResults.reduce((sum, r) => sum + r.entities.filter(e => e.success).length, 0); const failedEntities = allResults.reduce((sum, r) => sum + r.entities.filter(e => !e.success).length, 0); console.log(`Total sync operations: ${totalSyncs}`); console.log(` Successful: ${successfulSyncs}`); console.log(` Failed: ${failedSyncs}`); console.log(`\nTotal entities processed: ${totalEntitiesSynced}`); console.log(` Successful: ${successfulEntities}`); console.log(` Failed: ${failedEntities}`); const successRate = ((successfulEntities / totalEntitiesSynced) * 100).toFixed(1); console.log(`\nEntity success rate: ${successRate}%`); } catch (error) { console.error('\nāŒ Test failed with error:', error); if (error instanceof Error) { console.error('Stack trace:', error.stack); } process.exit(1); } finally { // Close database connection await postgresClient.close(); console.log('\nšŸ”Œ Database connection closed'); } } // Run the tests testEntitySpecificSync() .then(() => { console.log('\nāœ… Test script completed successfully'); process.exit(0); }) .catch((error) => { console.error('\nāŒ Test script failed:', error); process.exit(1); });