import test from 'node:test'; import assert from 'node:assert/strict'; import { assertSame, createPlan, expectedIm, memberId, profile, rollbackChanges, temporaryName, validatePlan } from '../core.mjs'; function user(id, name) { return { id: String(id), user_name: name, nick_name: name, head_image: '', head_image_thumb: '', member_state: 0, sync_version: '0', auth_version: '0', access_disabled: false, is_banned: false, password_hash: 'preserved' }; } function fixture() { return { members: [ { id: '100', tenant_id: '1', nickname: '老芋头', avatar: '', mobile: null, status: 1, deleted: true }, { id: '131', tenant_id: '1', nickname: '老芋头', avatar: 'new-avatar', mobile: null, status: 0, deleted: false }, ], tasks: [], users: [user('-200', '客服'), user('-100', '消息'), user('100', 'u131'), user('131', 'u100'), user('900', '孤立账号')], friends: [{ id: '1', user_id: '1', friend_id: '131', friend_nick_name: '旧昵称', friend_head_image: '' }], groupMembers: [{ id: '1', group_id: '1', user_id: '131', user_nick_name: '旧昵称', head_image: '', remark_nick_name: '我的备注' }], groups: [{ id: '1', owner_id: '100', dissolve: 0 }], messages: { private: { count: '10', maximumId: '10' } }, }; } test('same nicknames remain distinct and deleted/orphan accounts are archived', () => { const plan = createPlan(fixture(), {}, 'abcdef'); assert.deepEqual(plan.userChanges.map(row => [row.id, row.user_name, row.member_state]), [['100', 'u100', 2], ['131', 'u131', 0], ['900', 'u900', 3]]); assert.equal(plan.nextMemberId, '901'); assert.equal(plan.userChanges[0].auth_version, '1'); assert.equal(plan.userChanges[1].auth_version, '0'); assert.equal(expectedIm(plan).groupMembers[0].remark_nick_name, '我的备注'); assert.equal(expectedIm(plan).users[3].password_hash, 'preserved'); }); test('IDs and versions preserve precision beyond the JavaScript safe-integer range', () => { const before = fixture(); before.members[1].id = '9007199254740993'; before.users[3].id = '9007199254740993'; before.users[3].sync_version = '9007199254740994'; const plan = createPlan(before, {}, 'abcdef'); assert.equal(plan.nextMemberId, '9007199254740994'); assert.equal(plan.userChanges[1].user_name, 'u9007199254740993'); assert.equal(plan.userChanges[1].sync_version, '9007199254740995'); }); test('historical message references reserve IDs even when the old IM user row is absent', () => { const before = fixture(); before.messages.private.maximumSender = '9007199254740993'; assert.equal(createPlan(before, {}, 'abcdef').nextMemberId, '9007199254740994'); }); test('precheck rejects invalid historical identities and differences identify missing fields', () => { const before = fixture(); before.messages.private.maximumSender = '9223372036854775808'; assert.throws(() => createPlan(before, {}, 'abcdef'), /Long/); assert.throws(() => assertSame({ id: '131' }, { id: '131', nickname: '原昵称' }, '会员'), /会员.nickname/); }); test('maximum Long fits protocol and temporary usernames', () => { const member = fixture().members[1]; member.id = '9223372036854775807'; assert.equal(profile(member).userName.length, 20); assert.ok(temporaryName(member.id, 'abcdef').length <= 20); assert.throws(() => memberId('9223372036854775808')); assert.throws(() => memberId(0)); assert.throws(() => memberId(-1)); }); test('unknown accounts in the durable queue receive orphan snapshots', () => { const before = fixture(); before.tasks.push({ member_id: '900', tenant_id: '1', version: '9', profile_json: JSON.stringify({ userId: '900', nickName: 'old' }) }); const plan = createPlan(before, {}, 'abcdef'); assert.equal(plan.taskChanges[2].member_state, 3); assert.equal(plan.taskChanges[2].version, '10'); assert.equal(plan.userChanges[2].sync_version, '10'); }); test('precheck catches system namespace conflicts and nickname limits', () => { const before = fixture(); before.users[0].user_name = 'U131'; assert.throws(() => createPlan(before, {}, 'abcdef'), /冲突/); before.users[0].user_name = '客服'; before.members[1].nickname = '字'.repeat(31); assert.throws(() => createPlan(before, {}, 'abcdef'), /30字符/); }); test('plan checksum detects edits', () => { const plan = createPlan(fixture(), {}, 'abcdef'); validatePlan(plan); plan.userChanges[0].user_name = 'other'; assert.throws(() => validatePlan(plan), /校验值/); }); test('rollback restores profiles and advances both versions', () => { const plan = createPlan(fixture(), {}, 'abcdef'); const current = expectedIm(plan); const restored = rollbackChanges(plan, current); assert.equal(restored.users[0].user_name, 'u131'); assert.equal(restored.users[0].auth_version, '2'); assert.equal(restored.users[0].sync_version, '2'); assert.equal(restored.users[0].member_state, 2); assert.equal(restored.users[0].access_disabled, true); current.friends[0].friend_nick_name = 'later edit'; assert.throws(() => rollbackChanges(plan, current), /清单不一致/); }); test('rollback rejects later profile, relation and message changes', () => { const plan = createPlan(fixture(), {}, 'abcdef'); const current = expectedIm(plan); current.users[3].nick_name = 'later edit'; assert.throws(() => rollbackChanges(plan, current), /清单不一致/); });