ci: decouple heavy smoke tests into dedicated qa pipeline and add api checks

This commit is contained in:
2026-02-27 14:04:45 +01:00
parent 08425a3a42
commit 7f106b1fa7
4 changed files with 137 additions and 47 deletions

119
scripts/check-apis.ts Normal file
View File

@@ -0,0 +1,119 @@
import axios from 'axios';
import dns from 'dns';
import { promisify } from 'util';
import url from 'url';
const resolve4 = promisify(dns.resolve4);
// This script verifies that external logging and analytics APIs are reachable
// from the deployment environment (which could be behind corporate firewalls or VPNs).
const umamiEndpoint = process.env.UMAMI_API_ENDPOINT || 'https://analytics.infra.mintel.me';
const sentryDsn = process.env.SENTRY_DSN || '';
async function checkUmami() {
console.log(`\n🔍 Checking Umami Analytics API Availability...`);
console.log(` Endpoint: ${umamiEndpoint}`);
try {
// Umami usually exposes a /api/heartbeat or /api/health if we know the route.
// Trying root or /api/auth/verify (which will give 401 but proves routing works).
// A simple GET to the configured endpoint should return a 200 or 401, not a 5xx/timeout.
const response = await axios.get(`${umamiEndpoint.replace(/\/$/, '')}/api/health`, {
timeout: 5000,
validateStatus: () => true, // Accept any status, we just want to know it's reachable and not 5xx
});
// As long as it's not a 502/503/504 Bad Gateway/Timeout, the service is "up" from our perspective
if (response.status >= 500) {
throw new Error(`Umami API responded with server error HTTP ${response.status}`);
}
console.log(` ✅ Umami Analytics is reachable (HTTP ${response.status})`);
return true;
} catch (err: any) {
// If /api/health fails completely, maybe try a DNS check as a fallback
try {
console.warn(` ⚠️ HTTP check failed, falling back to DNS resolution...`);
const umamiHost = new url.URL(umamiEndpoint).hostname;
await resolve4(umamiHost);
console.log(` ✅ Umami Analytics DNS resolved successfully (${umamiHost})`);
return true;
} catch (dnsErr: any) {
console.error(
` ❌ CRITICAL: Umami Analytics is completely unreachable! ${err.message} | DNS: ${dnsErr.message}`,
);
return false;
}
}
}
async function checkSentry() {
console.log(`\n🔍 Checking Glitchtip/Sentry Error Tracking Availability...`);
if (!sentryDsn) {
console.log(` No SENTRY_DSN provided in environment. Skipping.`);
return true;
}
try {
const parsedDsn = new url.URL(sentryDsn);
const host = parsedDsn.hostname;
console.log(` Host: ${host}`);
// We do a DNS lookup to ensure the runner can actually resolve the tracking server
const addresses = await resolve4(host);
if (addresses && addresses.length > 0) {
console.log(` ✅ Glitchtip/Sentry domain resolved: ${addresses[0]}`);
// Optional: Quick TCP/HTTP check to the host root (Glitchtip usually runs on 80/443 root)
try {
const proto = parsedDsn.protocol || 'https:';
await axios.get(`${proto}//${host}/api/0/`, {
timeout: 5000,
validateStatus: () => true,
});
console.log(` ✅ Glitchtip/Sentry API root responds to HTTP.`);
} catch (ignore) {
console.log(
` ⚠️ Glitchtip/Sentry HTTP ping failed or timed out, but DNS is valid. Proceeding.`,
);
}
return true;
}
throw new Error('No IP addresses found for DSN host');
} catch (err: any) {
console.error(
` ❌ CRITICAL: Glitchtip/Sentry DSN is invalid or hostname is unresolvable! ${err.message}`,
);
return false;
}
}
async function main() {
console.log('🚀 Starting External API Connectivity Smoke Test...');
let hasErrors = false;
const umamiOk = await checkUmami();
if (!umamiOk) hasErrors = true;
const sentryOk = await checkSentry();
if (!sentryOk) hasErrors = true;
if (hasErrors) {
console.error(
`\n🚨 POST-DEPLOY CHECK FAILED: One or more critical external APIs are unreachable.`,
);
console.error(` This might mean the deployment environment lacks outbound internet access, `);
console.error(` DNS is misconfigured, or the upstream services are down.`);
process.exit(1);
}
console.log(`\n🎉 SUCCESS: All required external APIs are reachable!`);
process.exit(0);
}
main();