|
|
@@ -1,5 +1,33 @@
|
|
|
import {test} from 'node:test';import assert from 'node:assert/strict';import vm from 'node:vm';import {readFileSync} from 'node:fs';import {PGlite} from '@electric-sql/pglite';
|
|
|
const code=readFileSync(new URL('../cloud/account-security.js',import.meta.url),'utf8');
|
|
|
+const gatewaySource=readFileSync(new URL('../deploy-admin-functions.mjs',import.meta.url),'utf8');
|
|
|
+test('会员姓名和绑定手机号解析到同一账号,昵称不参与登录,同名要求手机号',async()=>{
|
|
|
+ const db=new PGlite();try{
|
|
|
+ await db.exec(`CREATE TABLE "_User"("objectId" text,"username" text,"company" text,"realName" text,"mobile" text,"phone" text,"legacyUserId" numeric,"legacyUserData" jsonb,"isDeleted" boolean);INSERT INTO "_User" VALUES('u1','account-one','co','可爱同学','13800000001',NULL,1,'{"TrueName":"可爱同学","HoneyName":"可改昵称"}',false),('other','another','other-company','可爱同学','13800000009',NULL,9,'{}',false);`);
|
|
|
+ const c=vm.createContext({DEFAULT_COMPANY_ID:'co',Psql:{query:async(sql,args)=>(await db.query(sql,args)).rows},legacyLoginResult:()=>({}),number:v=>Number(v)||0,firstValue:(...a)=>a.find(v=>v!==undefined&&v!==null&&v!==''),fail:(status,message)=>{throw Object.assign(Error(message),{status})},Parse:{Query:class{async get(id){return{id}}}}});
|
|
|
+ vm.runInContext(gatewaySource.slice(gatewaySource.indexOf('async function migratedLoginTarget('),gatewaySource.indexOf('const READING_CLASSES=')),c);
|
|
|
+ assert.equal((await c.migratedLoginTarget('可爱同学')).id,'u1');assert.equal((await c.migratedLoginTarget('13800000001')).id,'u1');assert.equal(await c.migratedLoginTarget('可改昵称'),null);assert.equal(await c.migratedLoginTarget("' OR 1=1 --"),null);
|
|
|
+ await db.exec(`INSERT INTO "_User" VALUES('u2','account-two','co','可爱同学','13800000002',NULL,2,'{}',false);`);
|
|
|
+ await assert.rejects(c.migratedLoginTarget('可爱同学'),/姓名重复/);assert.equal((await c.migratedLoginTarget('13800000001')).id,'u1');
|
|
|
+ await db.exec(`UPDATE "_User" SET "isDeleted"=true WHERE "objectId"='u2'`);assert.equal((await c.migratedLoginTarget('可爱同学')).id,'u1');
|
|
|
+ }finally{await db.close();}
|
|
|
+});
|
|
|
+test('创建会员由服务端统一设置初始密码,编辑资料不会重置密码',()=>{
|
|
|
+ const fn=gatewaySource.split('\n').find(line=>line.startsWith('function memberProfilePayload('));const c=vm.createContext({text:v=>String(v||'').trim(),number:v=>Number(v)||0,fail:(_,m)=>{throw Error(m)}});vm.runInContext(fn,c);
|
|
|
+ const profile={displayName:'新同学',mobile:'13800000001'};assert.equal(c.memberProfilePayload(profile,true).password,'12345678');assert.equal(c.memberProfilePayload({...profile,password:'caller-selected-password'},true).password,'12345678');assert.equal(c.memberProfilePayload(profile,false).password,'');
|
|
|
+});
|
|
|
+test('学生修改昵称时不能同时改动绑定姓名和手机号',async()=>{
|
|
|
+ const attrs={legacyGroupId:1,realName:'会员姓名',mobile:'13800000001',nickname:'旧昵称',legacyUserData:{TrueName:'会员姓名',Mobile:'13800000001',HoneyName:'旧昵称'}};let saved=0;const current={get:k=>attrs[k],set:(k,v)=>attrs[k]=v,save:async()=>saved++};
|
|
|
+ const c=vm.createContext({ownLegacyId:()=>1,number:v=>Number(v)||0,isAdminUser:()=>false,isMemberAccount:()=>true,parseObject:v=>v||{},fieldsOf:async()=>({realName:{},mobile:{},nickname:{}}),legacyData:u=>u.get('legacyUserData'),writeValue:v=>v,cleanText:v=>v,legacyUser:()=>({}),legacyUserAddon:()=>({})});
|
|
|
+ vm.runInContext(gatewaySource.slice(gatewaySource.indexOf('async function updateUserProfile('),gatewaySource.indexOf('function legacyUserRow(')),c);
|
|
|
+ await c.updateUserProfile({mu:{trueName:'篡改姓名',mobile:'13900000000',honeyName:'新昵称'}},current);assert.equal(saved,1);assert.equal(attrs.realName,'会员姓名');assert.equal(attrs.mobile,'13800000001');assert.equal(attrs.legacyUserData.TrueName,'会员姓名');assert.equal(attrs.legacyUserData.Mobile,'13800000001');assert.equal(attrs.nickname,'新昵称');assert.equal(attrs.legacyUserData.HoneyName,'新昵称');
|
|
|
+});
|
|
|
+test('旧分组为 3 的已确认会员可以由管理员重置,陪练身份仍被拒绝',async()=>{
|
|
|
+ const values={legacyGroupId:3,identityType:'member',legacyUserId:22};const target={get:k=>values[k],toJSON:()=>values};
|
|
|
+ const c=vm.createContext({scopedObject:async()=>target,number:v=>Number(v)||0,displayName:()=> '会员姓名',pointerId:()=> 'co',Psql:{oneOrNone:async()=>null},fail:(_,m)=>{throw Error(m)}});
|
|
|
+ vm.runInContext(gatewaySource.split('\n').find(line=>line.startsWith('async function memberTarget(')),c);
|
|
|
+ assert.equal(await c.memberTarget({company:{}},{},'student'),target);values.identityType='coach';await assert.rejects(c.memberTarget({company:{}},{},'coach'),/身份已发生变化/);
|
|
|
+});
|
|
|
test('旧密码只允许显式首次迁移,已重置和历史不明账号拒绝且不调用旧系统',async()=>{
|
|
|
for(const state of [{},{legacyPasswordMigrationAllowed:false},{legacyPasswordMigrationAllowed:true,passwordResetRequired:true}]){
|
|
|
let verified=0,saved=0;const c=vm.createContext({migratedLoginTarget:async()=>({get:k=>state[k],save:async()=>saved++}),verifyLegacyPassword:async()=>verified++,fail:(status,msg)=>{throw Error(msg)}});vm.runInContext(code,c);await assert.rejects(c.migrateLegacyLogin('a','old'),/密码错误/);assert.equal(saved,0);assert.equal(verified,0);
|