chore: directory migration - gateway→server, web→client/electron

This commit is contained in:
2026-09-08 19:16:35 +08:00
parent fd9f99a3f9
commit f9d757b5e5
243 changed files with 5095 additions and 228 deletions

View File

@ -0,0 +1,98 @@
/**
* 邮件详情页地址行的真实渲染验收。
*
* 锁的是那次事故:发件行显示成 `jianf.<会话别名>` —— 别名拼给了发件人,
* 而且 path 为空时拼出了 ParseAddress 会整串当成名字的非法形态。
*/
import { openApp, WIDE } from './narrow-probe-helper.mjs';
const { browser, page } = await openApp(WIDE);
let fail = 0;
const check = (name, ok, detail = '') => {
if (ok) console.log(` 通过 ${name}`);
else { fail++; console.log(` 失败 ${name}${detail ? ' — ' + detail : ''}`); }
};
// 走**发件**箱:报这个 bug 的那封信是人发出去的(jianf → pi,抄送 pi@….new),
// 收件箱里只有 Agent 的回信 —— 而回信没有抄送、也不带 `.new`,
// 探不到要验的那三处。
await page.waitForSelector('button', { timeout: 20000 });
await page.click('button:has-text("发件")');
await page.waitForTimeout(2000);
// 收件箱/发件箱按会话分组:先点组头展开,再点里面的邮件行。
// 两者 className 都含 `w-full text-left`,靠「点完有没有出现含『发件』的 dl」区分。
let opened = false;
for (let round = 0; round < 3 && !opened; round++) {
const btns = await page.$$('button.w-full.text-left');
for (const b of btns) {
const t = await b.innerText().catch(() => '');
if (!t) continue;
await b.click().catch(() => {});
await page.waitForTimeout(350);
const hasMeta = await page.evaluate(() =>
[...document.querySelectorAll('dl')].some(dl => dl.textContent.includes('发件')));
if (hasMeta) { opened = true; break; }
}
}
check('打开了一封邮件', opened);
await page.waitForTimeout(800);
// 读元信息 dl
const meta = await page.evaluate(() => {
const dls = [...document.querySelectorAll('dl')];
for (const dl of dls) {
const pairs = [];
const kids = [...dl.children];
for (const div of kids) {
const dt = div.querySelector('dt'), dd = div.querySelector('dd');
if (dt && dd) pairs.push([dt.textContent.trim(), dd.textContent.trim()]);
}
if (pairs.some(([k]) => k === '发件')) return Object.fromEntries(pairs);
}
return null;
});
if (!meta) {
check('找到元信息区', false, '没有含「发件」的 dl');
} else {
console.log(' 元信息:', JSON.stringify(meta));
const from = meta['发件'] || '';
const to = meta['收件'] || '';
const cc = meta['抄送'] || '';
// 判「哪一方是人」:人的地址里既无 @ 也无 .
const isHuman = a => a && !a.includes('@') && !a.includes('.');
const human = isHuman(from) ? from : (isHuman(to) ? to : '');
const agent = isHuman(from) ? to : from;
check('有一方是人(裸名字,无 @ 无 .)', !!human, `发件=${from} 收件=${to}`);
check('人的地址不含会话位', !human || !/[@.]/.test(human), `人=${human}`);
// Agent 必须三段齐全:name@path.session
const threeSeg = /^[^@]+@\/[^@]*\.[^.@]+$/.test(agent);
check('Agent 带完整三段 name@path.session', threeSeg, `agent=${agent}`);
// path 不能是 Agent 名(`dsh@dsh` 那个 bug)
if (agent.includes('@')) {
const [n, rest] = [agent.slice(0, agent.indexOf('@')), agent.slice(agent.indexOf('@') + 1)];
check('Agent 的 path 是真路径而不是 Agent 名',
rest.startsWith('/'), `${n}@${rest}`);
}
// 别名不能挂在人身上
check('会话别名跟着 Agent 而不是人',
!human || !agent || agent.includes('.'), `人=${human} agent=${agent}`);
// 抄送里不能残留 .new
check('抄送里没有 .new',
!cc.split('、').some(a => a.trim().endsWith('.new')), `抄送=${cc}`);
if (cc) console.log(` 抄送实际值: ${cc}`);
// 不该再有独立的「会话」行(别名已在 Agent 地址里)
check('没有多余的「会话」行', !('会话' in meta), `会话=${meta['会话']}`);
}
await browser.close();
console.log(fail ? `\n!! ${fail} 项不达标` : '\n全部达标');
process.exit(fail ? 1 : 0);