Files
Friendship-and-chat-api/script/repair-im-users/test/core.test.mjs
T

115 lines
5.3 KiB
JavaScript

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), /清单不一致/);
});