Files
digital-me-history/digital-me-backend/test/gongzou.test.js
T
admin 0593ff7e62
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
数字我 Digital Me:全量入库(大文件走 Git LFS)
2026-10-10 16:24:43 +08:00

137 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 共走(两人/多人一起走过同一道大事)的【角色对应关系】单测 —— 纯函数、不需 DB。
// 证四件事:① 性别识别对真人可靠(后/妃/公主/则天→女,官员→男,显式 sex 可覆盖);
// ② 女性首发只筛出【真实女性权力角色】、排除男性、去重已认领;③ 二选一是"你 vs 历史"、不编下场;
// ④ 合传把双证(cite)原样带出,不丢不编。
// 跑:node --test test/gongzou.test.js
import test from 'node:test';
import assert from 'node:assert/strict';
import { sexOf, pickRoles, guestChoice, weave, femalePath, listNvziDynasties, nvziRoles, nvziZhaoming, nvMing, femaleEnding } from '../src/services/gongzou.js';
// 取自 唐·永徽六年「废王立武」真实 cast(people[],各带 zhishi/cite 双证)。
const CAST = [
{ ren: '武则天', guan: '太后 → 聖神皇帝', verb: '改國號周', role: 'throne', zhishi: '本太宗才人…载初元年改唐为周、称帝。', cite: '《新唐書》卷四:「壬午,改國號周。」' },
{ ren: '王皇后', guan: '皇后(王氏)', verb: '被廢', role: 'fell', zhishi: '高宗原配…永徽六年被废。', cite: '《新唐書》卷四:「高宗廢皇后王氏」' },
{ ren: '褚遂良', guan: '尚書左僕射', verb: '力諫·被貶', role: 'other', zhishi: '太宗顾命…力谏废王立武被贬。', cite: '《新唐書·宰相表》卷61' },
{ ren: '长孙无忌', guan: '太尉', verb: '力沮·後賜死', role: 'other', zhishi: '太宗元舅、顾命首辅…自缢死。', cite: '《新唐書·宰相表》卷61' },
];
test('★ 性别识别:后/妃/公主/则天→女,官员→男,显式 sex 覆盖', () => {
assert.equal(sexOf(CAST[0]), 'f', '武则天(则天/太后)应为女');
assert.equal(sexOf(CAST[1]), 'f', '王皇后(皇后)应为女');
assert.equal(sexOf(CAST[2]), 'm', '褚遂良(仆射)应为男');
assert.equal(sexOf(CAST[3]), 'm', '长孙无忌(太尉)应为男');
assert.equal(sexOf({ ren: '某女官', guan: '尚宮' }), 'f', '女官/尚宫应为女');
assert.equal(sexOf({ ren: '某人', guan: '太尉', sex: 'f' }), 'f', '显式 sex 必须覆盖头衔启发');
});
test('★★★ 防误判:guan 里【引用】别人的女称,绝不把男性当成女性角色', () => {
assert.equal(sexOf({ ren: '于琮', guan: '宰相 · 駙馬(尚廣德公主)' }), 'm', '尚公主的驸马是男');
assert.equal(sexOf({ ren: '仁宗(趙禎)', guan: '皇帝(幼沖·太后垂簾)' }), 'm', '太后垂帘下的皇帝是男');
assert.equal(sexOf({ ren: '后勝', guan: '齊相' }), 'm', '姓后的齐相是男(裸「后」不判女)');
assert.equal(sexOf({ ren: '陈后主', guan: '皇帝' }), 'm', '「后主」是男帝');
// 真女性仍命中:ren 带女称 / guan 开头是本人官职
assert.equal(sexOf({ ren: '武氏', guan: '宸妃 → 皇后' }), 'f', 'guan 开头本人官职→女');
assert.equal(sexOf({ ren: '賈后(賈南風)', guan: '皇后' }), 'f', 'guan 皇后→女');
assert.equal(sexOf({ ren: '太平公主', guan: '鎮國太平公主' }), 'f');
});
test('★★ 女性首发:只筛出真实女性权力角色、排除男性、去重已认领', () => {
const f = pickRoles(CAST, 'f', []);
assert.deepEqual(f.map((r) => r.ren), ['武则天', '王皇后'], '女性只应看到武则天/王皇后');
assert.ok(f.every((r) => r.cite), '每个对位角色必须带 cite 双证');
const f2 = pickRoles(CAST, 'f', ['武则天']); // 武则天已被认领
assert.deepEqual(f2.map((r) => r.ren), ['王皇后'], '已认领的角色不再出现');
const m = pickRoles(CAST, 'm', []);
assert.deepEqual(m.map((r) => r.ren), ['褚遂良', '长孙无忌'], '兄弟一起=筛男性角色(引擎中立,零改动)');
const any = pickRoles(CAST, 'any', []);
assert.equal(any.length, 4, 'any=全体在场');
});
test('★ 二选一是"你 vs 历史"、两个选项、不预告下场', () => {
const ch = guestChoice({ ren: '武则天', guan: '太后', verb: '改國號周', role: 'throne', sex: 'f' });
assert.match(ch.prompt, /武则天/);
assert.match(ch.prompt, /改國號周/, '提示里要带她史上真做的事');
assert.equal(ch.options.length, 2);
assert.deepEqual(ch.options.map((o) => o.id), ['go', 'stop']);
assert.match(ch.prompt, /只给男人留位置/, '女性专门设计:提示要点出"只给男人留位置的朝堂"');
});
test('★★ 女子那条真实的路:从官衔判出 女皇/临朝/后位/宗室/后宫/女官', () => {
assert.equal(femalePath({ ren: '武则天', guan: '太后 → 聖神皇帝' }).key, 'nvhuang', '称帝→女皇(顶)');
assert.equal(femalePath({ guan: '太皇太后 · 臨朝稱制' }).key, 'linchao', '临朝称制');
assert.equal(femalePath({ guan: '皇后(王氏)' }).key, 'houwei', '后位');
assert.equal(femalePath({ ren: '太平公主', guan: '鎮國太平公主' }).key, 'gongzhu', '天家之女');
assert.equal(femalePath({ guan: '宸妃 → 皇后' }).key, 'houwei', '已升皇后→后位(皇后先于妃)');
assert.equal(femalePath({ guan: '尚宮' }).key, 'nvguan', '女官');
});
test('★★ 单独玩(A):nvziRoles 出真实女性角色,带路径/两难/双证/所属大事', () => {
const { roles } = nvziRoles('tang');
assert.ok(roles.length >= 3, '唐应有多位女性(武则天/太平公主/王皇后/武氏…)');
assert.ok(roles.some((r) => r.ren === '武则天' && r.path.key === 'nvhuang'), '武则天=女皇');
for (const r of roles) {
assert.equal(r.sex, 'f', '单独玩只出女性');
assert.ok(r.choice && r.choice.options.length === 2, '每个女子都带两难');
assert.ok(r.event && r.event.title, '带所属大事');
}
});
test('★ 单独玩(A):listNvziDynasties 只列有女性的朝、带数量与样本', () => {
const ds = listNvziDynasties();
assert.ok(ds.length >= 8, '至少 8 朝有女性角色');
assert.ok(ds.every((d) => d.count > 0 && d.name), '每朝都有数量与朝名');
assert.ok(ds.some((d) => d.code === 'tang'), '含唐');
});
test('★★ 女子照命(B):各朝天花板取最高女子路径、用真到顶的女人做证、最高在前', () => {
const z = nvziZhaoming();
assert.ok(z.length >= 8, '至少 8 朝');
const tang = z.find((x) => x.code === 'tang');
assert.ok(tang && tang.top.path.key === 'nvhuang' && tang.top.ren === '武则天', '唐顶=女皇·武则天');
assert.equal(z[0].code, 'tang', '女皇那朝(唐)天花板最高、排最前');
assert.ok(z.every((x) => x.top && x.top.ren && x.top.path), '每朝都有做证的女人+路径');
});
test('★★ 女子照命·个人推演(B):门第/才→各朝女子命,落差随门第变、命被门第压制、明标推演', () => {
const rich = nvMing({ jiashi: 3, cai: 1 });
assert.equal(rich.tuiyan, true, '必须明标推演');
assert.ok(rich.rows.length >= 8);
const tang = rich.rows.find((r) => r.code === 'tang');
assert.ok(tang && tang.linchaoReachable, '名门女子·唐(顶=女皇) 应望临朝');
assert.ok(rich.spread, '门第高各朝有落差');
assert.ok(rich.rows.every((r) => r.ceiling && r.ceiling.ren), '每朝天花板都有真人做证');
const poor = nvMing({ jiashi: 0, cai: 0 });
assert.ok(poor.rows.every((r) => !r.linchaoReachable), '寒微无才:哪朝都望不到临朝');
assert.ok(poor.rows.every((r) => r.reach.tier <= 3), '寒微无才:各朝都低 —— 命被门第压制');
});
test('★★ 女主角结局:传世替无名女子立传,到顶者不谎称被抹', () => {
const e1 = femaleEnding({ ren: '武则天', guan: '太后 → 聖神皇帝' }); // 女皇
assert.match(e1.chuanshiTop, /本纪|后妃传|留了位置/, '到顶的女子:史书留了名(不谎称她被抹去)');
assert.match(e1.chuanshiClose, /替她们立/, '也替无数无名女子立传');
const e2 = femaleEnding({ ren: '某宫人', guan: '才人' }); // 后宫(未到顶)
assert.match(e2.chuanshiTop, /某氏|某女|没给女子立传/, '未到顶:史书多半没立传');
assert.ok(e1.huixiang && e2.huixiang, '回响两者都有');
});
test('★★ 合传:双证(cite)原样带出、织成"你们俩一起走过"一句', () => {
const session = {
dynName: '唐', hostName: '阿康',
ev: { year: 655, title: '废王立武', gloss: null },
hostSeat: { weiLabel: '门下·谏官', roleKey: 'juzhong', roleLabel: '身在局中' },
hostPick: { text: '附立', end: { tone: 'sheng', word: '受赏' } },
};
const seat = { ...CAST[0], sex: 'f' };
const join = { name: '小敏', seatRen: '武则天', pick: { id: 'go', text: '也走到那一步' } };
const hz = weave(session, join, seat);
assert.equal(hz.guest.ren, '武则天');
assert.equal(hz.guest.truth.cite, CAST[0].cite, '合传必须把真人 cite 双证原样带出');
assert.match(hz.line, /唐·废王立武/);
assert.match(hz.line, /阿康/);
assert.match(hz.line, /小敏是武则天/);
});