launch-fixes.test.mjs 5.9 KB

1234567891011121314
  1. import {test} from 'node:test';import assert from 'node:assert/strict';import {readFile,writeFile,mkdtemp,rm} from 'node:fs/promises';import {tmpdir} from 'node:os';import {join} from 'node:path';import {PGlite} from '@electric-sql/pglite';import {syncAlert} from '../lib/sync-alerts.mjs';
  2. test('同步告警:失败去重、持续失败次日提醒、恢复一次提醒、差异增长提醒且不包含原始错误',async()=>{const root=await mkdtemp(join(tmpdir(),'xs-alert-'));try{const messages=[];const notify=async m=>messages.push(m);await syncAlert({status:'blocked',reason:'secret-token'},root,notify,1000);await syncAlert({status:'blocked'},root,notify,2000);assert.equal(messages.length,1);await syncAlert({status:'blocked'},root,notify,86402000);assert.equal(messages.length,2);await syncAlert({status:'completed',conflicts:100},root,notify,86403000);assert.equal(messages.length,3);await syncAlert({status:'already_completed',previous:{conflicts:100}},root,notify,86404000);assert.equal(messages.length,3);await syncAlert({status:'completed',conflicts:250},root,notify,86405000);assert.equal(messages.length,4);assert(!messages.join().includes('secret'));}finally{await rm(root,{recursive:true});}});
  3. test('注销:重复申请仅一条、跨帐套隔离、未实际注销不可登记完成、并发版本拒绝',async()=>{const db=new PGlite();try{await db.exec(`CREATE TABLE "_User"("objectId" text,company text,"isDeleted" boolean);INSERT INTO "_User" VALUES('u','co',false)`);await db.exec(await readFile(new URL('../sql/account-deletion.sql',import.meta.url),'utf8'));const call=async(sql,args)=>(await db.query(sql,args)).rows[0].r;const a=await call('SELECT xs_request_deletion($1,$2,$3) r',['co','u','申请注销']);const b=await call('SELECT xs_request_deletion($1,$2,$3) r',['co','u','再次提交']);assert.equal(a.id,b.id);await assert.rejects(call('SELECT xs_request_deletion($1,$2,$3) r',['other','u','申请注销']),/账号不存在/);await assert.rejects(call('SELECT xs_process_deletion($1,$2,$3,$4,$5,$6) r',['co','admin',a.id,1,'completed','已核对']),/请先完成/);const c=await call('SELECT xs_process_deletion($1,$2,$3,$4,$5,$6) r',['co','admin',a.id,1,'processing','核对权益']);assert.equal(c.version,2);await assert.rejects(call('SELECT xs_process_deletion($1,$2,$3,$4,$5,$6) r',['co','admin',a.id,1,'rejected','版本旧']),/已更新/);await db.exec(`UPDATE "_User" SET "isDeleted"=true`);assert.equal((await call('SELECT xs_process_deletion($1,$2,$3,$4,$5,$6) r',['co','admin',a.id,2,'completed','已完成注销'])).status,'completed');}finally{await db.close();}});
  4. test('工资截止核验:区分文字差异与金额差异、版本改变失效、缺失记录拦截、整月审核门槛',async()=>{const db=new PGlite();try{
  5. await db.exec(`CREATE TABLE "CommonModel"("objectId" text,company text,"generalId" numeric,"modelId" numeric,"itemId" numeric,title text,subtitle text);CREATE TABLE "CourseAppointment"(company text,id numeric,yysj text);CREATE TABLE "LessonRecord"(company text,id numeric,"lessonAt" text,"teacherPaySnapshot" numeric);CREATE TABLE "MemoryPracticeRecord"(company text,id numeric,wcsj text);CREATE TABLE "LegacySyncConflict"("objectId" text,company text,dataset text,"legacyKey" text,message text,resolved boolean);CREATE TABLE "ReviewReward"(id text);CREATE TABLE "TeachingCompensationRule"(id text);CREATE TABLE "CourseTeachingProfile"(id text);CREATE TABLE "CoachPayRate"(id text);CREATE TABLE "LessonRecordFact"(id text);CREATE FUNCTION xs_payroll_time(v text) RETURNS timestamptz LANGUAGE plpgsql AS $$BEGIN RETURN v::timestamptz;EXCEPTION WHEN others THEN RETURN NULL;END$$;CREATE FUNCTION xs_payroll_source_version(co text) RETURNS text LANGUAGE sql AS $$SELECT md5(COALESCE(jsonb_agg(to_jsonb(l))::text,'')) FROM "LessonRecord" l WHERE company=co$$;`);
  6. await db.exec(await readFile(new URL('../sql/legacy-direct-sync.sql',import.meta.url),'utf8'));await db.exec(await readFile(new URL('../sql/payroll-cutoff.sql',import.meta.url),'utf8'));
  7. await db.exec(`INSERT INTO "LegacyDirectRun"(company,"businessDate","runId",owner,"leaseUntil",status,"finishedAt") VALUES('co','2026-10-13','run','owner',now(),'completed','2026-10-13T15:00:00Z');INSERT INTO "CourseAppointment" VALUES('co',54,'2026-10-02');INSERT INTO "LessonRecord" VALUES('co',59,'2026-10-02',20);INSERT INTO "MemoryPracticeRecord" VALUES('co',60,'2026-10-02');INSERT INTO "CommonModel" SELECT 'c'||i,'co',i,i,i,'学员','单词' FROM unnest(ARRAY[54,59,60]) i;INSERT INTO "LegacyDirectEvidence" SELECT 'co','run',i,to_jsonb(ARRAY[i]),1,md5(i::text),md5(i::text),0,'content' FROM unnest(ARRAY[54,59,60]) i;INSERT INTO "LegacySyncConflict" VALUES('cosmetic','co','54','54','{"fields":["title"]}',false),('pay','co','59','59','{"fields":["teacherPaySnapshot"]}',false);`);
  8. const one=async(sql,args=[])=>(await db.query(sql,args)).rows[0].r;
  9. const snapshot=()=>one(`SELECT xs_payroll_cutoff_snapshot('co','2026-10') r`);
  10. let s=await snapshot();assert.equal(s.pending,1);assert.equal(s.complete,true);assert.equal(s.approved,false);
  11. const confirm=(v,cid='',fp='')=>one(`SELECT xs_payroll_cutoff_confirm('co','manager','2026-10',$1,'人工核对记录',$2,$3) r`,[v,cid,fp]);
  12. await assert.rejects(confirm(s.version),/仍有差异/);const diff=s.conflicts.find(x=>x.id==='pay');s=await confirm(s.version,diff.id,diff.fingerprint);assert.equal(s.pending,0);s=await confirm(s.version);assert.equal(s.approved,true);await db.query(`SELECT xs_payroll_assert_cutoff('co','2026-10','create',$1)`,[s.version]);await assert.rejects(db.query(`SELECT xs_payroll_assert_cutoff('co','2026-10','review',$1)`,[s.version]),/整月/);
  13. await db.exec(`UPDATE "LessonRecord" SET "teacherPaySnapshot"=30`);const stale=await snapshot();assert.equal(stale.approved,false);assert.equal(stale.pending,1);await assert.rejects(confirm(s.version),/来源已更新/);await db.exec(`DELETE FROM "CourseAppointment"`);assert.equal((await snapshot()).complete,false);
  14. }finally{await db.close();}});