Files
ai-stack-deployer/tests/test-deployment-proof.ts
Oussama Douhou e617114310 refactor: enterprise-grade project structure
- Move test files to tests/
- Archive session notes to docs/archive/
- Remove temp/diagnostic files
- Clean src/ to only contain production code
2026-01-10 12:32:54 +01:00

178 lines
7.4 KiB
TypeScript
Executable File
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bun
/**
* Deployment Proof Test
*
* Executes a complete deployment and captures evidence at each phase.
* Does not fail on health check timeout (SSL provisioning can take time).
* Does not cleanup - leaves resources for manual verification.
*/
import { createProductionDokployClient } from './api/dokploy-production.js';
import { ProductionDeployer } from './orchestrator/production-deployer.js';
const TEST_STACK_NAME = `proof-${Date.now()}`;
const DOCKER_IMAGE = 'nginx:alpine'; // Fast to deploy
const DOMAIN_SUFFIX = process.env.STACK_DOMAIN_SUFFIX || 'ai.flexinit.nl';
console.log('═══════════════════════════════════════');
console.log(' DEPLOYMENT PROOF TEST');
console.log('═══════════════════════════════════════\n');
console.log(`Configuration:`);
console.log(` Stack Name: ${TEST_STACK_NAME}`);
console.log(` Docker Image: ${DOCKER_IMAGE}`);
console.log(` Domain: ${TEST_STACK_NAME}.${DOMAIN_SUFFIX}`);
console.log(` Expected URL: https://${TEST_STACK_NAME}.${DOMAIN_SUFFIX}`);
console.log();
async function main() {
const client = createProductionDokployClient();
const deployer = new ProductionDeployer(client);
console.log('🚀 Starting deployment...\n');
const result = await deployer.deploy({
stackName: TEST_STACK_NAME,
dockerImage: DOCKER_IMAGE,
domainSuffix: DOMAIN_SUFFIX,
port: 80,
healthCheckTimeout: 30000, // 30 seconds only (don't wait forever for SSL)
healthCheckInterval: 5000,
});
console.log('\n═══════════════════════════════════════');
console.log(' DEPLOYMENT RESULT');
console.log('═══════════════════════════════════════\n');
console.log(`Final Phase: ${result.state.phase}`);
console.log(`Status: ${result.state.status}`);
console.log(`Progress: ${result.state.progress}%`);
console.log(`Message: ${result.state.message}`);
if (result.state.url) {
console.log(`\n🌐 Application URL: ${result.state.url}`);
}
console.log(`\n📦 Resources Created:`);
console.log(` ✓ Project ID: ${result.state.resources.projectId || 'NONE'}`);
console.log(` ✓ Environment ID: ${result.state.resources.environmentId || 'NONE'}`);
console.log(` ✓ Application ID: ${result.state.resources.applicationId || 'NONE'}`);
console.log(` ✓ Domain ID: ${result.state.resources.domainId || 'NONE'}`);
console.log(`\n⏱ Timestamps:`);
console.log(` Started: ${result.state.timestamps.started}`);
console.log(` Completed: ${result.state.timestamps.completed || 'IN PROGRESS'}`);
if (result.state.timestamps.completed && result.state.timestamps.started) {
const start = new Date(result.state.timestamps.started).getTime();
const end = new Date(result.state.timestamps.completed).getTime();
const duration = ((end - start) / 1000).toFixed(2);
console.log(` Duration: ${duration}s`);
}
if (result.state.error) {
console.log(`\n⚠ Error Details:`);
console.log(` Phase: ${result.state.error.phase}`);
console.log(` Message: ${result.state.error.message}`);
}
console.log(`\n🔄 Circuit Breaker: ${client.getCircuitBreakerState()}`);
console.log(`\n═══════════════════════════════════════`);
console.log(' PHASE EXECUTION LOG');
console.log('═══════════════════════════════════════\n');
const logs = client.getLogs();
let phaseNum = 1;
let lastPhase = '';
logs.forEach(log => {
if (log.phase !== lastPhase) {
console.log(`\n[PHASE ${phaseNum}] ${log.phase.toUpperCase()}`);
phaseNum++;
lastPhase = log.phase;
}
const level = log.level === 'error' ? '❌' : log.level === 'warn' ? '⚠️ ' : '✅';
const duration = log.duration_ms ? ` (${log.duration_ms}ms)` : '';
console.log(` ${level} ${log.action}: ${log.message}${duration}`);
if (log.error) {
console.log(` ↳ Error: ${log.error.message}`);
}
});
// Check deployment success criteria
console.log(`\n═══════════════════════════════════════`);
console.log(' SUCCESS CRITERIA');
console.log('═══════════════════════════════════════\n');
const checks = {
'Project Created': !!result.state.resources.projectId,
'Environment Retrieved': !!result.state.resources.environmentId,
'Application Created': !!result.state.resources.applicationId,
'Domain Configured': !!result.state.resources.domainId,
'Deployment Triggered': result.state.phase !== 'creating_domain',
'URL Generated': !!result.state.url,
};
let passCount = 0;
Object.entries(checks).forEach(([name, passed]) => {
console.log(` ${passed ? '✅' : '❌'} ${name}`);
if (passed) passCount++;
});
const healthCheckNote = result.state.error?.phase === 'verifying_health'
? '\n Note: Health check timeout is expected for new SSL certificates'
: '';
console.log(`\n Score: ${passCount}/${Object.keys(checks).length} checks passed${healthCheckNote}`);
console.log(`\n═══════════════════════════════════════`);
console.log(' VERIFICATION INSTRUCTIONS');
console.log('═══════════════════════════════════════\n');
if (result.state.resources.projectId) {
console.log(`1. Verify in Dokploy UI:`);
console.log(` https://app.flexinit.nl/project/${result.state.resources.projectId}`);
console.log();
}
if (result.state.resources.applicationId) {
console.log(`2. Check application status:`);
console.log(` https://app.flexinit.nl/project/${result.state.resources.projectId}/services/application/${result.state.resources.applicationId}`);
console.log();
}
if (result.state.url) {
console.log(`3. Test application (may take 1-2 min for SSL):`);
console.log(` ${result.state.url}`);
console.log();
}
console.log(`4. Cleanup command:`);
console.log(` curl -X POST -H "x-api-key: \${DOKPLOY_API_TOKEN}" \\`);
console.log(` https://app.flexinit.nl/api/application.delete \\`);
console.log(` -d '{"applicationId":"${result.state.resources.applicationId}"}'`);
console.log();
// Determine overall success
const corePhases = passCount >= 5; // At least 5/6 core checks
const noBlockingErrors = !result.state.error || result.state.error.phase === 'verifying_health';
console.log(`\n═══════════════════════════════════════`);
if (corePhases && noBlockingErrors) {
console.log(' ✅ DEPLOYMENT WORKING - ALL CORE PHASES PASSED');
} else {
console.log(' ❌ DEPLOYMENT BLOCKED');
}
console.log('═══════════════════════════════════════\n');
process.exit(corePhases && noBlockingErrors ? 0 : 1);
}
main().catch(error => {
console.error('\n❌ Fatal error:', error);
process.exit(1);
});