fix web ui smoke and brand assets

This commit is contained in:
hectorzhao
2026-06-30 11:13:30 +08:00
parent 0dfb2988b2
commit 9920575bba
103 changed files with 6650 additions and 135 deletions
+198
View File
@@ -0,0 +1,198 @@
import { mkdir, writeFile } from 'node:fs/promises';
import { request } from 'node:https';
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
const baseUrl = (process.env.LISGLOSIPS_BASE_URL || 'https://100.90.90.91').replace(/\/$/, '');
const timeoutMs = Number(process.env.LISGLOSIPS_SMOKE_TIMEOUT_MS || 30000);
const maxAttempts = Number(process.env.LISGLOSIPS_SMOKE_ATTEMPTS || 2);
const reportDir = resolve(dirname(fileURLToPath(import.meta.url)), '../reports');
function requestUrlOnce(path, attempt) {
return new Promise((resolveRequest) => {
const startedAt = Date.now();
const url = new URL(path, baseUrl);
const req = request(
url,
{
method: 'GET',
rejectUnauthorized: process.env.LISGLOSIPS_REJECT_UNAUTHORIZED === '1',
timeout: timeoutMs,
headers: {
Accept: 'application/json,text/html;q=0.9,*/*;q=0.8',
'User-Agent': 'lisglosips-remote-smoke/1.0',
},
},
(res) => {
const chunks = [];
res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => {
const body = Buffer.concat(chunks).toString('utf8');
resolveRequest({
path,
url: url.toString(),
attempt,
ok: true,
statusCode: res.statusCode || 0,
durationMs: Date.now() - startedAt,
contentType: String(res.headers['content-type'] || ''),
body,
});
});
}
);
req.on('timeout', () => {
req.destroy(new Error(`Request timed out after ${timeoutMs}ms`));
});
req.on('error', (error) => {
resolveRequest({
path,
url: url.toString(),
attempt,
ok: false,
statusCode: 0,
durationMs: Date.now() - startedAt,
contentType: '',
body: '',
error: error.message,
});
});
req.end();
});
}
async function requestUrl(path) {
let lastResult;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
lastResult = await requestUrlOnce(path, attempt);
if (lastResult.ok) {
return lastResult;
}
}
return lastResult;
}
function parseJson(result) {
try {
return JSON.parse(result.body);
} catch {
return null;
}
}
function expectStatus(result, expectedStatus) {
return {
name: `${result.path} returns ${expectedStatus}`,
pass: result.ok && result.statusCode === expectedStatus,
detail: result.error || `status=${result.statusCode}, duration=${result.durationMs}ms, attempt=${result.attempt}`,
};
}
function expectBody(result, name, predicate, detail) {
return {
name,
pass: result.ok && predicate(result),
detail: detail(result),
};
}
function reportLine(check) {
return `| ${check.pass ? 'PASS' : 'FAIL'} | ${check.name} | ${check.detail.replace(/\|/g, '\\|')} |`;
}
const checks = [];
const results = {
root: await requestUrl('/'),
live: await requestUrl('/api/v2/health/live'),
ready: await requestUrl('/api/v2/health/ready'),
captcha: await requestUrl('/api/v2/auth/captcha'),
};
checks.push(expectStatus(results.root, 200));
checks.push(
expectBody(
results.root,
'frontend HTML contains app root',
(result) => result.contentType.includes('text/html') && result.body.includes('<div id="root"></div>'),
(result) => `contentType=${result.contentType}, bytes=${Buffer.byteLength(result.body, 'utf8')}, attempt=${result.attempt}`
)
);
checks.push(expectStatus(results.live, 200));
checks.push(
expectBody(
results.live,
'live health reports ok',
(result) => {
const body = parseJson(result);
return body?.status === 'ok' && body?.service === 'api' && body?.checks?.process === 'ok';
},
(result) => `body=${JSON.stringify(parseJson(result))}`
)
);
checks.push(expectStatus(results.ready, 200));
checks.push(
expectBody(
results.ready,
'ready health reports database and redis ok',
(result) => {
const body = parseJson(result);
return body?.status === 'ok' && body?.checks?.database === 'ok' && body?.checks?.redis === 'ok';
},
(result) => `body=${JSON.stringify(parseJson(result))}`
)
);
checks.push(expectStatus(results.captcha, 200));
checks.push(
expectBody(
results.captcha,
'captcha endpoint returns id, image, and expiry',
(result) => {
const body = parseJson(result);
return typeof body?.captchaId === 'string' && body.imageDataUrl?.startsWith('data:image/svg+xml;base64,') && typeof body.expiresAt === 'string';
},
(result) => {
const body = parseJson(result);
return body ? `captchaId=${body.captchaId}, expiresAt=${body.expiresAt}, attempt=${result.attempt}` : 'body is not JSON';
}
)
);
const now = new Date();
const stamp = now.toISOString().replace(/[-:]/g, '').replace(/\..+$/, 'Z');
const reportPath = resolve(reportDir, `REMOTE_SMOKE_${stamp}.md`);
const failed = checks.filter((check) => !check.pass);
const report = [
'# Remote Smoke Test Report',
'',
`Date: ${now.toISOString()}`,
`Base URL: ${baseUrl}`,
`Timeout: ${timeoutMs}ms`,
`Attempts: ${maxAttempts}`,
'',
'| Result | Check | Detail |',
'| --- | --- | --- |',
...checks.map(reportLine),
'',
'## Raw Endpoints',
'',
...Object.values(results).map((result) => `- ${result.path}: status=${result.statusCode}, duration=${result.durationMs}ms, attempt=${result.attempt}, contentType=${result.contentType || 'n/a'}`),
'',
].join('\n');
await mkdir(reportDir, { recursive: true });
await writeFile(reportPath, report, 'utf8');
for (const check of checks) {
console.log(`${check.pass ? 'PASS' : 'FAIL'} ${check.name} - ${check.detail}`);
}
console.log(`Report: ${reportPath}`);
if (failed.length > 0) {
process.exitCode = 1;
}