test(qa): e2e 装配修正 + 集成/门禁测试与工具链重构
- e2e:修死选择器与蜜罐误命中、补断言、GA4/security-headers 用例诚实标注 skip 边界;新增 assert-helpers/fixtures/hydrated/primary-nav/touch-targets 等复用桩。 - 集成层:config/test/jest.integration.config.js + tests-integration/ 真库 一次性 SQLite 用例,teardown 守卫开发库指纹。 - 门禁工具链:jest.setup 归位到根、scripts/accessibility 全站 axe 节点计数、 check-brand-text-token/check-motion-constraints 机械守卫、stryker/lighthouse 配置收敛。
This commit is contained in:
@@ -58,13 +58,24 @@ function parseCookies(setCookieHeader: string | null): Array<{ name: string; fla
|
||||
}
|
||||
|
||||
function checkCookies(cookies: Array<{ name: string; flags: string[] }>): CookieCheck[] {
|
||||
return cookies.map((cookie) => ({
|
||||
name: cookie.name,
|
||||
hasHttpOnly: cookie.flags.some((f) => f === 'httponly'),
|
||||
hasSecure: cookie.flags.some((f) => f === 'secure'),
|
||||
hasSameSite: cookie.flags.some((f) => f.startsWith('samesite')),
|
||||
status: 'pass' as const,
|
||||
}));
|
||||
// N-22:此前 status 恒为字面量 'pass',上面三个 flag 全部白算、cookieFailed 永远为 0,
|
||||
// 而汇总的 hasFailures 只看 headerChecks ⇒ 任何 cookie 属性错误都无法让本门禁变红。
|
||||
return cookies.map((cookie) => {
|
||||
const hasHttpOnly = cookie.flags.some((f) => f === 'httponly');
|
||||
const hasSecure = cookie.flags.some((f) => f === 'secure');
|
||||
const hasSameSite = cookie.flags.some((f) => f.startsWith('samesite'));
|
||||
const missing = [!hasHttpOnly, !hasSameSite, !hasSecure];
|
||||
const isSessionLike = /token|session|auth|jwt/i.test(cookie.name);
|
||||
let status: CookieCheck['status'];
|
||||
if (isSessionLike && (!hasHttpOnly || !hasSameSite)) {
|
||||
status = 'fail';
|
||||
} else if (missing.some(Boolean)) {
|
||||
status = 'warn';
|
||||
} else {
|
||||
status = 'pass';
|
||||
}
|
||||
return { name: cookie.name, hasHttpOnly, hasSecure, hasSameSite, status };
|
||||
});
|
||||
}
|
||||
|
||||
async function checkSecurityHeaders(url: string): Promise<{
|
||||
@@ -85,7 +96,9 @@ async function checkSecurityHeaders(url: string): Promise<{
|
||||
header: 'Content-Security-Policy',
|
||||
expected: '存在(需包含合理策略)',
|
||||
actual: headers.get('content-security-policy'),
|
||||
status: headers.get('content-security-policy') ? 'pass' : 'warn',
|
||||
// N-22:缺 CSP 原本只 warn ⇒ 删掉整条 CSP 也无法让本门禁变红,而同族的
|
||||
// X-Content-Type-Options 同样缺失却判 fail。站点确实发 CSP(E2E 已在产物上断言),故判红不会误伤。
|
||||
status: headers.get('content-security-policy') ? ('pass' as const) : ('fail' as const),
|
||||
description: '防止 XSS 和数据注入攻击的核心策略',
|
||||
},
|
||||
{
|
||||
@@ -110,7 +123,13 @@ async function checkSecurityHeaders(url: string): Promise<{
|
||||
header: 'Strict-Transport-Security',
|
||||
expected: '存在(需包含 max-age)',
|
||||
actual: headers.get('strict-transport-security'),
|
||||
status: headers.get('strict-transport-security') ? 'pass' : 'fail',
|
||||
// N-22:HSTS 由边缘(Nginx/CDN)注入,本地 standalone 产物上没有。无条件判 fail 会让本门禁
|
||||
// 永远无法在分支产物上通过(只能打线上),故仅在 https 目标上缺失才判 fail。
|
||||
status: headers.get('strict-transport-security')
|
||||
? ('pass' as const)
|
||||
: url.startsWith('https:')
|
||||
? ('fail' as const)
|
||||
: ('warn' as const),
|
||||
description: '强制 HTTPS 连接,防止 SSL Strip 攻击',
|
||||
},
|
||||
{
|
||||
@@ -134,7 +153,7 @@ async function checkSecurityHeaders(url: string): Promise<{
|
||||
status: (() => {
|
||||
const val = headers.get('x-xss-protection');
|
||||
if (val === '0' || val === '1; mode=block') return 'pass' as const;
|
||||
return val ? 'warn' as const : 'warn' as const;
|
||||
return val ? ('warn' as const) : ('pass' as const);
|
||||
})(),
|
||||
description: '已废弃的 XSS 过滤器(现代浏览器不再需要)',
|
||||
},
|
||||
@@ -281,7 +300,8 @@ async function main() {
|
||||
printSummary(headerChecks, cookieChecks);
|
||||
|
||||
// Determine exit code
|
||||
const hasFailures = headerChecks.some((c) => c.status === 'fail');
|
||||
const hasFailures =
|
||||
headerChecks.some((c) => c.status === 'fail') || cookieChecks.some((c) => c.status === 'fail');
|
||||
if (hasFailures) {
|
||||
console.log('\n❌ 存在失败的安全头检查项!');
|
||||
process.exit(1);
|
||||
|
||||
Reference in New Issue
Block a user