数字我 Digital Me:全量入库(大文件走 Git LFS)
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
nginx serve /uploads direct (audio speed) / patch-nginx (push) Failing after 1m34s
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
// 家长监护的【强制闸】回归测试(《人工智能拟人化互动服务管理暂行办法》第十四条第二款)。
|
||||
//
|
||||
// ⚠ 这组测试存在的理由跟代码本身一样重要:commit 7c423f3 把这些闸门整个删掉过一次,
|
||||
// 没有任何测试变红,所以没有人发现。删一个纯函数会被 guardian.test.js 拦住,但
|
||||
// 「把 requireAdult 从 wallet 路由上摘掉」「gateAsk 里不再查屏蔽」这类删法,纯函数测试
|
||||
// 一条都拦不住 —— 它们只在【接线】上体现。所以这里测的是接线:
|
||||
// · 未成年人模式的账号,钱包是不是真的 403(限制充值消费)
|
||||
// · 监护人屏蔽了某个分身后,gateAsk 是不是真的拦(屏蔽特定角色)
|
||||
// · 成年人是不是完全不受影响(不误伤 —— 误伤了运营就会把整个功能关掉)
|
||||
// 哪天有人再"顺手清理"一次,这里会红。
|
||||
//
|
||||
// 需要本地 MongoDB;连不上则整组 skip(不阻塞无 DB 的 CI),与 store-idor.test.js 同一口径。
|
||||
process.env.NODE_ENV = 'test';
|
||||
process.env.MONGO_DB = process.env.MONGO_DB_TEST || 'digital_me_test';
|
||||
process.env.FEATURE_MINOR_MODE = 'on';
|
||||
process.env.MINOR_DAILY_LIMIT_MIN = process.env.MINOR_DAILY_LIMIT_MIN || '60';
|
||||
process.env.JWT_SECRET = process.env.JWT_SECRET || 'guardian-gate-test';
|
||||
|
||||
import { test, before, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import express from 'express';
|
||||
import http from 'node:http';
|
||||
|
||||
let server, base, db, closeDb, mongoUp = true;
|
||||
let ObjectId, signToken, gateAsk, G;
|
||||
let kid, adult, twinA, twinB;
|
||||
|
||||
before(async () => {
|
||||
const dbm = await import('../src/db.js');
|
||||
closeDb = dbm.closeDb;
|
||||
try { db = await dbm.connectDb(); }
|
||||
catch { mongoUp = false; return; }
|
||||
|
||||
({ ObjectId } = await import('mongodb'));
|
||||
({ signToken } = await import('../src/middleware/auth.js'));
|
||||
({ gateAsk } = await import('../src/services/minor.js'));
|
||||
G = await import('../src/services/guardian.js');
|
||||
const { walletRouter } = await import('../src/routes/wallet.js');
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use('/api/wallet', walletRouter);
|
||||
server = http.createServer(app).listen(0);
|
||||
await new Promise((ok) => server.once('listening', ok));
|
||||
base = `http://127.0.0.1:${server.address().port}`;
|
||||
|
||||
kid = new ObjectId(); adult = new ObjectId();
|
||||
twinA = new ObjectId(); twinB = new ObjectId();
|
||||
await db.collection('users').insertOne({
|
||||
_id: kid, name: '测试未成年人', birthYear: 2014,
|
||||
minorMode: 'on', minorLocked: true,
|
||||
// 不满 14 周岁 → 没有监护人同意的话 gateAsk 会先被第十四条第三款那道闸拦下,
|
||||
// 就测不到本组要测的屏蔽闸了。这里补上,把测试对准要测的东西。
|
||||
guardianConsent: { at: new Date(), level: 'attested' },
|
||||
});
|
||||
await db.collection('users').insertOne({ _id: adult, name: '测试成年人' });
|
||||
await db.collection('twins').insertOne({ _id: twinA, displayName: '测试分身A', slug: `t-a-${Date.now()}` });
|
||||
await db.collection('twins').insertOne({ _id: twinB, displayName: '测试分身B', slug: `t-b-${Date.now()}` });
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
if (server) server.close();
|
||||
if (mongoUp && db) {
|
||||
const ids = [kid, adult].filter(Boolean);
|
||||
if (ids.length) await db.collection('users').deleteMany({ _id: { $in: ids } });
|
||||
const tids = [twinA, twinB].filter(Boolean);
|
||||
if (tids.length) await db.collection('twins').deleteMany({ _id: { $in: tids } });
|
||||
if (kid) {
|
||||
await db.collection('guardian_blocks').deleteMany({ wardId: kid });
|
||||
await db.collection('minor_usage').deleteMany({ userId: kid });
|
||||
}
|
||||
}
|
||||
if (mongoUp && closeDb) await closeDb();
|
||||
});
|
||||
|
||||
const walletGet = async (id) =>
|
||||
fetch(`${base}/api/wallet/`, { headers: { authorization: `Bearer ${signToken({ id: id.toString(), email: 't@e.st' })}` } });
|
||||
|
||||
// ── 限制充值消费 ──
|
||||
test('【第十四条第二款·限制充值消费】未成年人模式的账号:钱包 403', async (t) => {
|
||||
if (!mongoUp) return t.skip('无本地 MongoDB,跳过 DB 集成测试');
|
||||
const res = await walletGet(kid);
|
||||
const body = await res.json();
|
||||
assert.equal(res.status, 403, 'requireAdult 被从 walletRouter 上摘掉了?');
|
||||
assert.equal(body.error, 'minor-spending-blocked');
|
||||
// 前端 errMsg 优先取 data.message —— 只回英文码的话用户看到的是一串机器码。
|
||||
assert.match(body.message, /未成年人模式/);
|
||||
});
|
||||
|
||||
test('不误伤:成年账号钱包照常 200', async (t) => {
|
||||
if (!mongoUp) return t.skip('无本地 MongoDB,跳过 DB 集成测试');
|
||||
const res = await walletGet(adult);
|
||||
assert.equal(res.status, 200, '误伤成年人 = 运营会把整个未成年人模式关掉,那才是最坏结局');
|
||||
});
|
||||
|
||||
// ── 屏蔽特定角色 ──
|
||||
test('【第十四条第二款·屏蔽特定角色】屏蔽后 gateAsk 服务端硬拦,解除后恢复', async (t) => {
|
||||
if (!mongoUp) return t.skip('无本地 MongoDB,跳过 DB 集成测试');
|
||||
await db.collection('guardian_blocks').deleteMany({ wardId: kid });
|
||||
|
||||
const before_ = await gateAsk(kid.toString(), '你好啊', twinA);
|
||||
assert.ok(!before_.blocked, '未屏蔽时应正常放行');
|
||||
|
||||
await G.blockTwin(adult.toString(), kid.toString(), twinA.toString());
|
||||
const after_ = await gateAsk(kid.toString(), '你好啊', twinA);
|
||||
assert.ok(after_.blocked, '★ 屏蔽后必须拦住 —— 前端藏起来是没用的,curl 直接就绕过去了');
|
||||
assert.equal(after_.code, 'guardian-twin-blocked');
|
||||
|
||||
const other = await gateAsk(kid.toString(), '你好啊', twinB);
|
||||
assert.ok(!other.blocked, '没被屏蔽的分身不能受影响');
|
||||
|
||||
await G.unblockTwin(kid.toString(), twinA.toString());
|
||||
const restored = await gateAsk(kid.toString(), '你好啊', twinA);
|
||||
assert.ok(!restored.blocked, '解除屏蔽后必须恢复');
|
||||
});
|
||||
|
||||
test('屏蔽是幂等的(唯一索引 + 11000 当成功):重复点不报错、不产生第二条', async (t) => {
|
||||
if (!mongoUp) return t.skip('无本地 MongoDB,跳过 DB 集成测试');
|
||||
await db.collection('guardian_blocks').deleteMany({ wardId: kid });
|
||||
await G.blockTwin(adult.toString(), kid.toString(), twinA.toString());
|
||||
await G.blockTwin(adult.toString(), kid.toString(), twinA.toString());
|
||||
assert.equal((await G.listBlocks(kid.toString())).length, 1);
|
||||
await db.collection('guardian_blocks').deleteMany({ wardId: kid });
|
||||
});
|
||||
|
||||
test('不误伤:成年人(未开未成年人模式)即使库里有屏蔽记录也一律放行', async (t) => {
|
||||
if (!mongoUp) return t.skip('无本地 MongoDB,跳过 DB 集成测试');
|
||||
await db.collection('guardian_blocks').insertOne({ wardId: adult, twinId: twinA, guardianId: kid, createdAt: new Date() });
|
||||
const g = await gateAsk(adult.toString(), '你好', twinA);
|
||||
assert.ok(!g.blocked && !g.notice, 'minorMode 未开时 gateAsk 应在读到 mode 后立刻返回,连屏蔽都不查');
|
||||
await db.collection('guardian_blocks').deleteMany({ wardId: adult });
|
||||
});
|
||||
|
||||
// ── 针对不同年龄段的时长上限 ──
|
||||
test('【第十四条第二款·针对不同年龄段保护需要】监护人只能把上限调得更严,不能更松', async (t) => {
|
||||
if (!mongoUp) return t.skip('无本地 MongoDB,跳过 DB 集成测试');
|
||||
const { getMinorState } = await import('../src/services/minor.js');
|
||||
|
||||
await G.setWardDailyLimit(kid.toString(), 20);
|
||||
assert.equal((await getMinorState(kid.toString())).dailyLimitMin, 20, '家长设的更严的值必须生效');
|
||||
|
||||
await G.setWardDailyLimit(kid.toString(), 999);
|
||||
assert.equal(
|
||||
(await getMinorState(kid.toString())).dailyLimitMin, 60,
|
||||
'★ 家长不能把上限调得比平台默认更松 —— 否则等于给每个家长发了一个"关掉时长限制"的按钮'
|
||||
);
|
||||
|
||||
await G.setWardDailyLimit(kid.toString(), 0);
|
||||
assert.equal((await getMinorState(kid.toString())).dailyLimitMin, 60, '设 0 = 取消覆盖,回落平台默认');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user