Add comprehensive admin features and multi-system integration
- Add admin dashboard with sync controls and data browser - Implement RMM, Auvik, and Addigy organization mappings - Add chunked ticket sync with progress tracking - Implement entity sync service with rate limiting - Add analytics engine and performance optimizer - Create data browser for all PSA entities - Add navigation components and UI improvements - Implement background processing and sync services - Add comprehensive documentation and migration scripts - Update configuration items with multi-system support - Enhance contact management and purchase history - Add issue type assignment and LLM analyzer - Improve error handling and logging utilities
This commit is contained in:
parent
e8462ef301
commit
6eee14f8af
171 changed files with 32671 additions and 621 deletions
102
scripts/apply-migrations.sh
Executable file
102
scripts/apply-migrations.sh
Executable file
|
|
@ -0,0 +1,102 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Script to apply database migrations for Pulse application
|
||||
|
||||
# Colors for output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}=== Pulse Database Migration Tool ===${NC}"
|
||||
echo ""
|
||||
|
||||
# Check if we're using Docker
|
||||
if docker ps | grep -q pulse-postgres; then
|
||||
echo -e "${YELLOW}Using Docker PostgreSQL container${NC}"
|
||||
DOCKER_MODE=true
|
||||
DB_USER="${POSTGRES_USER:-pulse_user}"
|
||||
DB_NAME="${POSTGRES_DB:-pulse_autotask}"
|
||||
else
|
||||
echo -e "${YELLOW}Running in local environment${NC}"
|
||||
DOCKER_MODE=false
|
||||
# Check if psql is available
|
||||
if ! command -v psql &> /dev/null; then
|
||||
echo -e "${RED}Error: psql command not found${NC}"
|
||||
echo -e "${YELLOW}Hint: If using Docker, make sure pulse-postgres container is running${NC}"
|
||||
exit 1
|
||||
fi
|
||||
DB_CONNECTION="psql -h ${POSTGRES_HOST:-localhost} -p ${POSTGRES_PORT:-5432} -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-pulse}"
|
||||
fi
|
||||
|
||||
# Get the migrations directory
|
||||
MIGRATIONS_DIR="/opt/stacks/pulse/migrations"
|
||||
|
||||
# Check if migrations directory exists
|
||||
if [ ! -d "$MIGRATIONS_DIR" ]; then
|
||||
echo -e "${RED}Error: Migrations directory not found at $MIGRATIONS_DIR${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# List available migrations
|
||||
echo -e "${GREEN}Available migrations:${NC}"
|
||||
ls -la $MIGRATIONS_DIR/*.sql | awk '{print $9}' | xargs -I {} basename {}
|
||||
|
||||
echo ""
|
||||
|
||||
# Check if a specific migration was requested
|
||||
if [ ! -z "$1" ]; then
|
||||
MIGRATION_FILE="$MIGRATIONS_DIR/$1"
|
||||
if [ ! -f "$MIGRATION_FILE" ]; then
|
||||
echo -e "${RED}Error: Migration file $1 not found${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -e "${YELLOW}Applying single migration: $1${NC}"
|
||||
|
||||
if [ "$DOCKER_MODE" = true ]; then
|
||||
docker exec -i pulse-postgres psql -U "$DB_USER" -d "$DB_NAME" < "$MIGRATION_FILE"
|
||||
else
|
||||
$DB_CONNECTION -f "$MIGRATION_FILE"
|
||||
fi
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ Migration $1 applied successfully${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Failed to apply migration $1${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Apply all migrations in order
|
||||
echo -e "${YELLOW}Apply all migrations? (y/n)${NC}"
|
||||
read -r response
|
||||
|
||||
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
|
||||
for migration in $MIGRATIONS_DIR/*.sql; do
|
||||
filename=$(basename "$migration")
|
||||
echo -e "${YELLOW}Applying: $filename${NC}"
|
||||
|
||||
if [ "$DOCKER_MODE" = true ]; then
|
||||
docker exec -i pulse-postgres psql -U "$DB_USER" -d "$DB_NAME" < "$migration"
|
||||
else
|
||||
$DB_CONNECTION -f "$migration"
|
||||
fi
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo -e "${GREEN}✓ $filename applied${NC}"
|
||||
else
|
||||
echo -e "${RED}✗ Failed to apply $filename${NC}"
|
||||
echo -e "${YELLOW}Continue with remaining migrations? (y/n)${NC}"
|
||||
read -r continue_response
|
||||
if [[ ! "$continue_response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo -e "${GREEN}=== Migration process complete ===${NC}"
|
||||
else
|
||||
echo "Migration cancelled"
|
||||
fi
|
||||
fi
|
||||
21
scripts/run-migration-008.sh
Normal file
21
scripts/run-migration-008.sh
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/bash
|
||||
# Run migration 008 to relax tickets resource constraints
|
||||
|
||||
set -e
|
||||
|
||||
echo "Running migration 008: Relax tickets resource constraints..."
|
||||
|
||||
# Check if we're in Docker or local
|
||||
if [ -f /.dockerenv ]; then
|
||||
# Running inside Docker
|
||||
psql "$DATABASE_URL" -f /app/migrations/008_relax_tickets_resource_constraints.sql
|
||||
else
|
||||
# Running locally
|
||||
if [ -z "$DATABASE_URL" ]; then
|
||||
echo "Error: DATABASE_URL environment variable not set"
|
||||
exit 1
|
||||
fi
|
||||
psql "$DATABASE_URL" -f ./migrations/008_relax_tickets_resource_constraints.sql
|
||||
fi
|
||||
|
||||
echo "Migration 008 completed successfully!"
|
||||
172
scripts/test-auvik-config.ts
Normal file
172
scripts/test-auvik-config.ts
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/**
|
||||
* Test script to fetch device configuration from Auvik API
|
||||
* Usage: npx tsx scripts/test-auvik-config.ts YNGHYNSWP19
|
||||
*/
|
||||
|
||||
import { AuvikClient } from '../lib/services/auvik-client';
|
||||
|
||||
interface AuvikConfigurationResponse {
|
||||
data: Array<{
|
||||
type: string;
|
||||
id: string;
|
||||
attributes: {
|
||||
deviceId: string;
|
||||
backupDate: string;
|
||||
configType: string;
|
||||
configText?: string;
|
||||
configSize?: number;
|
||||
};
|
||||
}>;
|
||||
links?: {
|
||||
next?: string;
|
||||
};
|
||||
}
|
||||
|
||||
async function testAuvikConfiguration(hostname: string) {
|
||||
console.log(`\n=== Testing Auvik Configuration API for: ${hostname} ===\n`);
|
||||
|
||||
// Initialize Auvik client
|
||||
const config = {
|
||||
apiUrl: process.env.AUVIK_API_URL || 'https://auvikapi.us1.my.auvik.com',
|
||||
apiUser: process.env.AUVIK_API_USER || '',
|
||||
apiKey: process.env.AUVIK_API_KEY || '',
|
||||
};
|
||||
|
||||
if (!config.apiUser || !config.apiKey) {
|
||||
console.error('Error: AUVIK_API_USER and AUVIK_API_KEY environment variables must be set');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const client = new AuvikClient(config);
|
||||
|
||||
try {
|
||||
// Step 1: Find all devices and locate the one with matching hostname
|
||||
console.log('Step 1: Fetching all devices to find matching hostname...');
|
||||
const devices = await client.getAllDevices();
|
||||
console.log(`Found ${devices.length} total devices`);
|
||||
|
||||
const matchingDevice = devices.find(
|
||||
(d) => d.deviceName.toLowerCase() === hostname.toLowerCase()
|
||||
);
|
||||
|
||||
if (!matchingDevice) {
|
||||
console.error(`\nDevice not found with hostname: ${hostname}`);
|
||||
console.log('\nAvailable devices:');
|
||||
devices.forEach((d) => {
|
||||
console.log(` - ${d.deviceName} (${d.deviceType}) - ${d.id}`);
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\n✓ Found device: ${matchingDevice.deviceName}`);
|
||||
console.log(` Device ID: ${matchingDevice.id}`);
|
||||
console.log(` Type: ${matchingDevice.deviceType}`);
|
||||
console.log(` Tenant: ${matchingDevice.tenantName || matchingDevice.tenantId}`);
|
||||
console.log(` Status: ${matchingDevice.onlineStatus}`);
|
||||
console.log(` IP Addresses: ${matchingDevice.ipAddresses.join(', ')}`);
|
||||
|
||||
// Step 2: Try to fetch device configuration
|
||||
console.log('\nStep 2: Attempting to fetch device configuration...');
|
||||
|
||||
// Auvik API endpoint for device configuration
|
||||
const configUrl = `${config.apiUrl}/v1/inventory/device/configuration?filter[deviceId]=${matchingDevice.id}`;
|
||||
console.log(`Config URL: ${configUrl}`);
|
||||
|
||||
const credentials = Buffer.from(`${config.apiUser}:${config.apiKey}`).toString('base64');
|
||||
|
||||
const response = await fetch(configUrl, {
|
||||
headers: {
|
||||
Authorization: `Basic ${credentials}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error(`\nAPI Error: ${response.status} ${response.statusText}`);
|
||||
console.error(`Response: ${errorText}`);
|
||||
|
||||
// Try alternative endpoint - device detail
|
||||
console.log('\nStep 3: Trying device detail endpoint...');
|
||||
const detailUrl = `${config.apiUrl}/v1/inventory/device/detail/${matchingDevice.id}`;
|
||||
console.log(`Detail URL: ${detailUrl}`);
|
||||
|
||||
const detailResponse = await fetch(detailUrl, {
|
||||
headers: {
|
||||
Authorization: `Basic ${credentials}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!detailResponse.ok) {
|
||||
const detailErrorText = await detailResponse.text();
|
||||
console.error(`\nDetail API Error: ${detailResponse.status} ${detailResponse.statusText}`);
|
||||
console.error(`Response: ${detailErrorText}`);
|
||||
} else {
|
||||
const detailData = await detailResponse.json();
|
||||
console.log('\n✓ Device Detail Response:');
|
||||
console.log(JSON.stringify(detailData, null, 2));
|
||||
}
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const configData: AuvikConfigurationResponse = await response.json();
|
||||
|
||||
console.log('\n✓ Configuration Response:');
|
||||
console.log(`Found ${configData.data.length} configuration(s)`);
|
||||
|
||||
configData.data.forEach((config, index) => {
|
||||
console.log(`\n--- Configuration ${index + 1} ---`);
|
||||
console.log(` Type: ${config.attributes.configType}`);
|
||||
console.log(` Backup Date: ${config.attributes.backupDate}`);
|
||||
console.log(` Size: ${config.attributes.configSize || 'N/A'} bytes`);
|
||||
|
||||
if (config.attributes.configText) {
|
||||
console.log(`\n Configuration Text (first 500 chars):`);
|
||||
console.log(` ${config.attributes.configText.substring(0, 500)}...`);
|
||||
} else {
|
||||
console.log(` Configuration text not available in response`);
|
||||
}
|
||||
});
|
||||
|
||||
// Step 4: Try to get the latest configuration backup
|
||||
console.log('\n\nStep 4: Fetching latest configuration backup...');
|
||||
const backupUrl = `${config.apiUrl}/v1/inventory/device/configuration?filter[deviceId]=${matchingDevice.id}&page[first]=1`;
|
||||
console.log(`Backup URL: ${backupUrl}`);
|
||||
|
||||
const backupResponse = await fetch(backupUrl, {
|
||||
headers: {
|
||||
Authorization: `Basic ${credentials}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (backupResponse.ok) {
|
||||
const backupData: AuvikConfigurationResponse = await backupResponse.json();
|
||||
console.log('\n✓ Latest Configuration Backup:');
|
||||
console.log(JSON.stringify(backupData, null, 2));
|
||||
} else {
|
||||
const backupError = await backupResponse.text();
|
||||
console.error(`\nBackup API Error: ${backupResponse.status} ${backupResponse.statusText}`);
|
||||
console.error(`Response: ${backupError}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('\nError:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Get hostname from command line argument
|
||||
const hostname = process.argv[2];
|
||||
if (!hostname) {
|
||||
console.error('Usage: npx tsx scripts/test-auvik-config.ts <hostname>');
|
||||
console.error('Example: npx tsx scripts/test-auvik-config.ts YNGHYNSWP19');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
testAuvikConfiguration(hostname);
|
||||
282
scripts/test-postgres.ts
Normal file
282
scripts/test-postgres.ts
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
/**
|
||||
* Test script for PostgreSQL connection and CRUD operations
|
||||
* Run with: npx tsx scripts/test-postgres.ts
|
||||
*/
|
||||
|
||||
// Load environment variables from .env.local
|
||||
import { config } from 'dotenv';
|
||||
import { resolve } from 'path';
|
||||
|
||||
config({ path: resolve(__dirname, '../.env.local') });
|
||||
|
||||
import { postgresClient } from '../lib/services/postgres-client';
|
||||
|
||||
interface TestCompany {
|
||||
id: number;
|
||||
name: string;
|
||||
phone?: string;
|
||||
address?: string;
|
||||
city?: string;
|
||||
state?: string;
|
||||
zip_code?: string;
|
||||
country?: string;
|
||||
website?: string;
|
||||
is_active: boolean;
|
||||
created_at?: Date;
|
||||
updated_at?: Date;
|
||||
synced_at?: Date;
|
||||
is_deleted: boolean;
|
||||
deleted_at?: Date;
|
||||
}
|
||||
|
||||
async function runTests() {
|
||||
console.log('🧪 Starting PostgreSQL Connection and CRUD Tests\n');
|
||||
|
||||
let testsPassed = 0;
|
||||
let testsFailed = 0;
|
||||
|
||||
try {
|
||||
// Test 1: Connection Test
|
||||
console.log('Test 1: Testing database connection...');
|
||||
const isConnected = await postgresClient.testConnection();
|
||||
if (isConnected) {
|
||||
console.log('✅ Database connection successful\n');
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ Database connection failed\n');
|
||||
testsFailed++;
|
||||
return;
|
||||
}
|
||||
|
||||
// Test 2: Insert Operation
|
||||
console.log('Test 2: Testing INSERT operation...');
|
||||
const testCompany = {
|
||||
id: 999999,
|
||||
name: 'Test Company Inc',
|
||||
phone: '555-0123',
|
||||
address: '123 Test Street',
|
||||
city: 'Test City',
|
||||
state: 'TS',
|
||||
zip_code: '12345',
|
||||
country: 'USA',
|
||||
website: 'https://testcompany.com',
|
||||
is_active: true,
|
||||
is_deleted: false,
|
||||
synced_at: new Date(),
|
||||
};
|
||||
|
||||
const inserted = await postgresClient.insert<TestCompany>('companies', testCompany);
|
||||
if (inserted && inserted.id === testCompany.id) {
|
||||
console.log('✅ INSERT successful:', inserted.name);
|
||||
console.log(` ID: ${inserted.id}, Name: ${inserted.name}\n`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ INSERT failed\n');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 3: Find by ID
|
||||
console.log('Test 3: Testing findById operation...');
|
||||
const found = await postgresClient.findById<TestCompany>('companies', 999999);
|
||||
if (found && found.name === 'Test Company Inc') {
|
||||
console.log('✅ findById successful:', found.name);
|
||||
console.log(` Phone: ${found.phone}, City: ${found.city}\n`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ findById failed\n');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 4: Update Operation
|
||||
console.log('Test 4: Testing UPDATE operation...');
|
||||
const updated = await postgresClient.update<TestCompany>('companies', 999999, {
|
||||
name: 'Updated Test Company Inc',
|
||||
phone: '555-9999',
|
||||
});
|
||||
if (updated && updated.name === 'Updated Test Company Inc') {
|
||||
console.log('✅ UPDATE successful:', updated.name);
|
||||
console.log(` New phone: ${updated.phone}\n`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ UPDATE failed\n');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 5: Upsert Operation (Update existing)
|
||||
console.log('Test 5: Testing UPSERT operation (update existing)...');
|
||||
const upserted1 = await postgresClient.upsert<TestCompany>('companies', {
|
||||
id: 999999,
|
||||
name: 'Upserted Test Company',
|
||||
phone: '555-8888',
|
||||
is_active: true,
|
||||
is_deleted: false,
|
||||
synced_at: new Date(),
|
||||
});
|
||||
if (upserted1 && upserted1.name === 'Upserted Test Company') {
|
||||
console.log('✅ UPSERT (update) successful:', upserted1.name);
|
||||
console.log(` Phone: ${upserted1.phone}\n`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ UPSERT (update) failed\n');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 6: Upsert Operation (Insert new)
|
||||
console.log('Test 6: Testing UPSERT operation (insert new)...');
|
||||
const upserted2 = await postgresClient.upsert<TestCompany>('companies', {
|
||||
id: 999998,
|
||||
name: 'Another Test Company',
|
||||
phone: '555-7777',
|
||||
is_active: true,
|
||||
is_deleted: false,
|
||||
synced_at: new Date(),
|
||||
});
|
||||
if (upserted2 && upserted2.id === 999998) {
|
||||
console.log('✅ UPSERT (insert) successful:', upserted2.name);
|
||||
console.log(` ID: ${upserted2.id}\n`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ UPSERT (insert) failed\n');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 7: Find with criteria
|
||||
console.log('Test 7: Testing find with criteria...');
|
||||
const foundCompanies = await postgresClient.find<TestCompany>('companies', {
|
||||
is_active: true,
|
||||
}, {
|
||||
limit: 5,
|
||||
orderBy: 'name ASC',
|
||||
});
|
||||
if (foundCompanies && foundCompanies.length > 0) {
|
||||
console.log(`✅ find successful: Found ${foundCompanies.length} active companies`);
|
||||
console.log(` First company: ${foundCompanies[0].name}\n`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ find failed\n');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 8: Count operation
|
||||
console.log('Test 8: Testing count operation...');
|
||||
const count = await postgresClient.count('companies', { is_active: true });
|
||||
if (count >= 2) {
|
||||
console.log(`✅ count successful: ${count} active companies\n`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ count failed\n');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 9: Bulk insert
|
||||
console.log('Test 9: Testing bulk insert...');
|
||||
const bulkCompanies = [
|
||||
{
|
||||
id: 999997,
|
||||
name: 'Bulk Test Company 1',
|
||||
is_active: true,
|
||||
is_deleted: false,
|
||||
synced_at: new Date(),
|
||||
},
|
||||
{
|
||||
id: 999996,
|
||||
name: 'Bulk Test Company 2',
|
||||
is_active: true,
|
||||
is_deleted: false,
|
||||
synced_at: new Date(),
|
||||
},
|
||||
];
|
||||
const bulkInserted = await postgresClient.bulkInsert('companies', bulkCompanies);
|
||||
if (bulkInserted === 2) {
|
||||
console.log(`✅ bulk insert successful: ${bulkInserted} records inserted\n`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log(`❌ bulk insert failed: Expected 2, got ${bulkInserted}\n`);
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 10: Soft delete
|
||||
console.log('Test 10: Testing soft delete...');
|
||||
await postgresClient.softDelete('companies', 999999);
|
||||
const deletedCompany = await postgresClient.findById<TestCompany>('companies', 999999, false);
|
||||
const deletedCompanyWithDeleted = await postgresClient.findById<TestCompany>('companies', 999999, true);
|
||||
if (!deletedCompany && deletedCompanyWithDeleted && deletedCompanyWithDeleted.is_deleted) {
|
||||
console.log('✅ soft delete successful: Record marked as deleted');
|
||||
console.log(` is_deleted: ${deletedCompanyWithDeleted.is_deleted}\n`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ soft delete failed\n');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Test 11: Transaction test
|
||||
console.log('Test 11: Testing transaction (rollback)...');
|
||||
try {
|
||||
await postgresClient.transaction(async (client) => {
|
||||
await client.query('INSERT INTO companies (id, name, is_active, is_deleted, synced_at) VALUES ($1, $2, $3, $4, $5)',
|
||||
[999995, 'Transaction Test', true, false, new Date()]);
|
||||
// Force an error to test rollback
|
||||
throw new Error('Intentional error for rollback test');
|
||||
});
|
||||
console.log('❌ transaction rollback failed: Should have thrown error\n');
|
||||
testsFailed++;
|
||||
} catch (error) {
|
||||
// Check if the record was NOT inserted (rollback worked)
|
||||
const notInserted = await postgresClient.findById<TestCompany>('companies', 999995, true);
|
||||
if (!notInserted) {
|
||||
console.log('✅ transaction rollback successful: Record not inserted after error\n');
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ transaction rollback failed: Record was inserted\n');
|
||||
testsFailed++;
|
||||
}
|
||||
}
|
||||
|
||||
// Test 12: Transaction test (commit)
|
||||
console.log('Test 12: Testing transaction (commit)...');
|
||||
await postgresClient.transaction(async (client) => {
|
||||
await client.query('INSERT INTO companies (id, name, is_active, is_deleted, synced_at) VALUES ($1, $2, $3, $4, $5)',
|
||||
[999994, 'Transaction Commit Test', true, false, new Date()]);
|
||||
});
|
||||
const committed = await postgresClient.findById<TestCompany>('companies', 999994);
|
||||
if (committed && committed.name === 'Transaction Commit Test') {
|
||||
console.log('✅ transaction commit successful:', committed.name);
|
||||
console.log(` ID: ${committed.id}\n`);
|
||||
testsPassed++;
|
||||
} else {
|
||||
console.log('❌ transaction commit failed\n');
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Cleanup: Delete all test records
|
||||
console.log('Cleanup: Removing test records...');
|
||||
await postgresClient.query('DELETE FROM companies WHERE id >= 999994 AND id <= 999999');
|
||||
console.log('✅ Cleanup complete\n');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Test suite error:', error);
|
||||
testsFailed++;
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log('═══════════════════════════════════════');
|
||||
console.log('Test Summary:');
|
||||
console.log(`✅ Passed: ${testsPassed}`);
|
||||
console.log(`❌ Failed: ${testsFailed}`);
|
||||
console.log(`📊 Total: ${testsPassed + testsFailed}`);
|
||||
console.log('═══════════════════════════════════════\n');
|
||||
|
||||
if (testsFailed === 0) {
|
||||
console.log('🎉 All tests passed!\n');
|
||||
process.exit(0);
|
||||
} else {
|
||||
console.log('⚠️ Some tests failed. Please review the output above.\n');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Run tests
|
||||
runTests().catch((error) => {
|
||||
console.error('Fatal error running tests:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue