fix: lates
Some checks are pending
CI - Production Readiness / Verify (push) Waiting to run

This commit is contained in:
2026-04-21 20:37:59 +07:00
parent f48c87e36d
commit 137edc12b7
15 changed files with 846 additions and 23 deletions

View File

@ -0,0 +1,189 @@
#!/usr/bin/env node
const BASE_URL = [
process.env.OPS_BASE_URL,
process.env.APP_URL,
process.env.NEXT_PUBLIC_APP_URL
].map((value) => value?.trim()).find(Boolean);
const TEST_EMAIL = process.env.OPS_SESSION_CHECK_EMAIL?.trim();
const TEST_PASSWORD = process.env.OPS_SESSION_CHECK_PASSWORD?.trim();
const EXPECTED_TTL_SECONDS = resolveSessionTtl(process.env.SESSION_TTL_SECONDS);
const SESSION_COOKIE_NAME = "wa_inbox_session";
if (!BASE_URL) {
console.error("[ops-session-check] Missing OPS_BASE_URL / APP_URL / NEXT_PUBLIC_APP_URL");
process.exit(1);
}
if (!TEST_EMAIL || !TEST_PASSWORD) {
console.error("[ops-session-check] Missing OPS_SESSION_CHECK_EMAIL / OPS_SESSION_CHECK_PASSWORD. Test skipped.");
process.exit(1);
}
function resolveSessionTtl(value) {
if (!value) {
return 60 * 60 * 24 * 7;
}
const parsed = Number(value.trim());
if (!Number.isFinite(parsed) || parsed <= 0) {
return 60 * 60 * 24 * 7;
}
return Math.floor(parsed);
}
function readCookieLines(headers) {
const direct = [];
if (typeof headers.getSetCookie === "function") {
const lines = headers.getSetCookie();
if (Array.isArray(lines)) {
return lines;
}
}
headers.forEach((value, key) => {
if (key.toLowerCase() === "set-cookie") {
direct.push(value);
}
});
return direct;
}
function getCookieValue(cookieHeader, name) {
const prefix = `${name}=`;
const found = cookieHeader.find((entry) => entry.startsWith(prefix));
if (!found) {
return null;
}
const [value] = found.split(";")[0].split("=", 2).slice(1);
if (typeof value !== "string") {
return null;
}
return `${found.split(";")[0].slice(prefix.length)}`;
}
function getCookieTtlSeconds(cookieHeader, name) {
const prefix = `${name}=`;
const found = cookieHeader.find((entry) => entry.startsWith(prefix));
if (!found) {
return null;
}
const parts = found.split(";").map((part) => part.trim());
const attrs = new Map();
for (const part of parts.slice(1)) {
const [rawKey, rawValue] = part.split("=");
attrs.set(rawKey.toLowerCase(), rawValue ?? "");
}
const maxAgeRaw = attrs.get("max-age");
if (maxAgeRaw) {
const parsed = Number(maxAgeRaw);
if (Number.isFinite(parsed)) {
return Math.floor(parsed);
}
}
const expiresRaw = attrs.get("expires");
if (expiresRaw) {
const parsedDate = new Date(expiresRaw);
if (!Number.isNaN(parsedDate.getTime())) {
return Math.floor((parsedDate.getTime() - Date.now()) / 1000);
}
}
return null;
}
async function requestJson(url, options = {}) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 12_000);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
clearTimeout(timeout);
return response;
} catch (error) {
clearTimeout(timeout);
throw error;
}
}
async function main() {
const loginHeaders = {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "ops-session-check"
};
const loginBody = new URLSearchParams({
email: TEST_EMAIL,
password: TEST_PASSWORD,
next: "/super-admin"
}).toString();
const loginResponse = await requestJson(`${BASE_URL}/auth/login`, {
method: "POST",
redirect: "manual",
headers: loginHeaders,
body: loginBody
});
if (loginResponse.status !== 307 && loginResponse.status !== 302) {
console.error(`[ops-session-check] login status unexpected: ${loginResponse.status}`);
process.exit(1);
}
const setCookieLines = readCookieLines(loginResponse.headers);
const sessionCookieValue = getCookieValue(setCookieLines, SESSION_COOKIE_NAME);
if (!sessionCookieValue) {
console.error("[ops-session-check] session cookie not issued by /auth/login");
process.exit(1);
}
const ttl = getCookieTtlSeconds(setCookieLines, SESSION_COOKIE_NAME);
if (ttl === null) {
console.error("[ops-session-check] session cookie ttl missing");
process.exit(1);
}
if (Math.abs(ttl - EXPECTED_TTL_SECONDS) > 600) {
console.warn(
`[ops-session-check] session ttl unexpected: ${ttl}s (expected ${EXPECTED_TTL_SECONDS}s ±600s)`
);
} else {
console.log(`[ops-session-check] session ttl check OK: ${ttl}s`);
}
const protected = await requestJson(`${BASE_URL}/super-admin`, {
method: "GET",
redirect: "manual",
headers: {
Cookie: `${SESSION_COOKIE_NAME}=${sessionCookieValue}`
}
});
if (protected.status === 200) {
console.log("[ops-session-check] protected path access OK (HTTP 200).");
process.exit(0);
}
if (protected.status >= 300 && protected.status < 400) {
const location = protected.headers.get("location") || "";
if (location.includes("/login")) {
console.error("[ops-session-check] protected path redirected to login; session not accepted.");
process.exit(1);
}
}
console.error(`[ops-session-check] protected path check failed with status ${protected.status}`);
process.exit(1);
}
main().catch((error) => {
console.error("[ops-session-check] failed:", error instanceof Error ? error.message : String(error));
process.exit(1);
});