wulf-pulse/dev/test-entity-specific-sync.ts

335 lines
14 KiB
TypeScript
Raw Normal View History

/**
* 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);
});