import test from 'node:test'; import assert from 'node:assert/strict'; import { indexBoardMembers, seedMemberGroups, changeMemberGroups } from '../src/core/member-groups.js'; import { buildModel } from '../src/core/kfs/build.js'; import { MemberGroupService } from '../src/services/member-groups.js'; const raw = () => ({ session: [ { objectId: 's1', studentName: '原昵称', groupId: 'G1', meta: { participantKey: 'participant:old1' }, employee: { key: 'channel-employee:stable', displayName: '小智' } }, { objectId: 's2', studentName: '另一昵称', groupId: 'G1', meta: { participantKey: 'participant:old2' }, employee: { key: 'channel-employee:stable', displayName: '小智' } }, { objectId: 's3', studentName: '小智', groupId: 'G2', meta: { participantKey: 'participant:other' } }, ], artifact: [{ objectId: 'a1', ownerName: '原昵称', uploadedByKey: 'participant:old1', ownerSession: 's1' }], project: [{ objectId: 'p1', ownerName: '原昵称', uploadedByKey: 'participant:old2', ownerSession: 's2' }], opportunity: [{ objectId: 'o1', title: '机会', uploadedBy: '原昵称', uploadedByKey: 'participant:old1', sourceSession: 's1' }] }); test('同一员工跨会话、旧键与昵称仍归同人,同名其他身份不合并', () => { const input = raw(), db = buildModel(input); assert.equal(db.members.length, 2); const m = db.memberById['channel-employee:stable']; assert.equal(m.name, '小智(G1)');assert.deepEqual(m.sessionIds, ['s1','s2']); assert.deepEqual(m.artifactIds, ['a1']);assert.deepEqual(m.projectIds, ['p1']);assert.deepEqual(m.oppIds, ['o1']); assert.equal(db.memberById['participant:other'].name, '小智(G2)'); const renamed = raw();renamed.session[0].employee.displayName = '新称呼';renamed.session[1].employee.displayName = '新称呼'; assert.equal(indexBoardMembers(renamed).members[0].id, m.id); assert.equal(input.artifact[0].ownerName, '原昵称'); }); test('姓名补充不能改变旧数据的分组编号,前端与服务端成员索引逐项一致', () => { const input = { session: [ { objectId: 's1', studentName: '陈静', groupId: 'G1' }, { objectId: 's2', studentName: '陈静', groupId: 'G1', meta: { participantKey: 'legacy-account-1' } }, ], artifact: [{ objectId: 'a1', ownerName: '陈静', ownerSession: 's1', uploadedByKey: 'legacy-account-2' }], project: [], opportunity: [] }; const directory = indexBoardMembers(input), db = buildModel(input); assert.deepEqual(db.members.map((m) => m.id).sort(), directory.members.map((m) => m.id).sort()); assert.equal(db.members.length, 1); assert.equal(db.members[0].name, '陈静'); assert.equal(seedMemberGroups(db.members).assignments[db.members[0].id], 'legacy-group:G1'); }); test('分组名独立于成员身份,重命名不改归属,删除只移回未分组', () => { const members = indexBoardMembers(raw()).members, ids = new Set(members.map((m) => m.id)); const state = seedMemberGroups(members), before = JSON.stringify(members); const assigned = changeMemberGroups(state, { action: 'assign', groupId: state.groups[1].id, memberIds: [...ids] }, ids); const renamed = changeMemberGroups(assigned, { action: 'rename', groupId: state.groups[1].id, name: '新品研发' }, ids); assert.equal(renamed.groups[1].name, '新品研发');assert.deepEqual(renamed.assignments, assigned.assignments); const deleted = changeMemberGroups(renamed, { action: 'delete', groupId: state.groups[1].id }, ids); assert.equal(Object.keys(deleted.assignments).length, 0);assert.equal(JSON.stringify(members), before); assert.equal(state.revision, 0); }); test('重复、空白、保留名、长名及未知成员不会产生有效分组变更', () => { const members = indexBoardMembers(raw()).members, ids = new Set(members.map((m) => m.id)), state = seedMemberGroups(members); for (const name of ['G1',' G1 ','', '未分组','全部成员','字'.repeat(31)]) assert.throws(() => changeMemberGroups(state, { action:'create',name }, ids, 'new'), { code:'VALIDATION_FAILED' }); assert.throws(() => changeMemberGroups(state, { action:'assign',groupId:state.groups[0].id,memberIds:[members[0].id,'private-person'] }, ids), { code:'VALIDATION_FAILED' }); assert.equal(state.revision, 0); }); test('分组服务发送版本与稳定编号,网络异常不自动重试', async () => { let calls=0; const service = new MemberGroupService({ url:'/fixture',call:async (input) => { calls++;assert.equal(input.name,'ksfManagePublicMemberGroups');assert.equal(input.params.expectedRevision,3);assert.equal(input.params.requestId,'same');throw Error('timeout'); } }); await assert.rejects(() => service.change({revision:3},{action:'assign',memberIds:['employee:one'],groupId:null},'same'),/timeout/); assert.equal(calls,1); });