coach-identity-projection.test.mjs 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import { before, after, test } from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import { readFileSync } from 'node:fs';
  4. import vm from 'node:vm';
  5. import { PGlite } from '@electric-sql/pglite';
  6. const db = new PGlite();
  7. const gateway = {};
  8. vm.createContext(gateway);
  9. const source = readFileSync(new URL('../deploy-admin-functions.mjs', import.meta.url), 'utf8');
  10. vm.runInContext(source.match(/const operationsGatewayCode = String.raw`([\s\S]*?)\n`;/)[1], gateway);
  11. gateway.companyIdOf = () => 'co';
  12. gateway.companyPointer = () => ({ id: 'co' });
  13. gateway.Psql = {
  14. query: async (sql, args) => (await db.query(sql, args)).rows,
  15. oneOrNone: async (sql, args) => (await db.query(sql, args)).rows[0] || null,
  16. };
  17. // Parse is external; SQL evidence queries and persisted identity changes use the real database.
  18. gateway.Parse = {
  19. Query: class {
  20. async get(id) {
  21. const row = (await db.query('SELECT * FROM "_User" WHERE "objectId"=$1', [id])).rows[0];
  22. return {
  23. get: key => row[key], set: (key, value) => { row[key] = value; },
  24. save: async () => db.query('UPDATE "_User" SET "identityType"=$2 WHERE "objectId"=$1', [id, row.identityType]),
  25. };
  26. }
  27. },
  28. Object: class { set() {} async save() {} },
  29. };
  30. before(async () => {
  31. await db.exec(`
  32. CREATE TABLE "_User"("objectId" text,company text,"legacyUserId" int,"legacyGroupId" int,"legacyUserData" jsonb,"isAdmin" boolean,"isDeleted" boolean,"identityType" text);
  33. CREATE TABLE "CourseAppointment"(company text,pl text,fxpl text,szyh text);
  34. CREATE TABLE "LessonRecord"(company text,jsmz text,xymz text);
  35. CREATE TABLE "DailyStudyRecord"(company text,"userId" int);
  36. CREATE TABLE "CourseBinding"(company text,yhid text);
  37. CREATE TABLE "PracticeRecord"(company text,yhid text);
  38. CREATE TABLE "LegacySyncConflict"("objectId" text,company text,dataset text,"legacyKey" text,resolved boolean);
  39. INSERT INTO "_User" VALUES
  40. ('teacher','co',1535,3,'{}',false,false,'conflict'),
  41. ('student','co',2,1,'{}',false,false,'member'),
  42. ('ambiguous','co',3,1,'{}',false,false,'conflict'),
  43. ('dual-binding','co',4,3,'{}',false,false,'conflict'),
  44. ('dual-lesson','co',5,3,'{}',false,false,'conflict'),
  45. ('store','co',6,2,'{}',false,false,'store');
  46. INSERT INTO "CourseAppointment" VALUES ('co','1535','1535','99'),('co','3',null,'99'),('co','4',null,'99'),('co','6',null,'6'),('other','99',null,'1535');
  47. INSERT INTO "LessonRecord" VALUES ('co','5','5');
  48. INSERT INTO "CourseBinding" VALUES ('co','4');
  49. INSERT INTO "PracticeRecord" VALUES ('co','1535'),('co','2'),('co','3');
  50. `);
  51. await gateway.refreshIdentityProjection({}, {});
  52. });
  53. after(() => db.close());
  54. const identity = async id => (await db.query('SELECT "identityType" FROM "_User" WHERE "objectId"=$1', [id])).rows[0].identityType;
  55. test('正式老师自己的单词练习不再把老师误标为冲突,其他帐套学员证据不串入', async () => {
  56. assert.equal(await identity('teacher'), 'coach');
  57. await gateway.refreshIdentityProjection({}, {});
  58. assert.equal(await identity('teacher'), 'coach');
  59. });
  60. test('仅练习的普通学员仍是学员,分组未经确认的双重身份仍需核实', async () => {
  61. assert.equal(await identity('student'), 'member');
  62. assert.equal(await identity('ambiguous'), 'conflict');
  63. });
  64. test('老师同时有正式学员课程或课后记录时仍保留冲突,门店角色不改变', async () => {
  65. assert.equal(await identity('dual-binding'), 'conflict');
  66. assert.equal(await identity('dual-lesson'), 'conflict');
  67. assert.equal(await identity('store'), 'store');
  68. });