teaching-course-mapping.test.mjs 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import { test, before, after } 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 context = { Psql: { query: async (sql, values) => (await db.query(sql, values)).rows } };
  8. vm.createContext(context);
  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], context);
  11. const profile = (id, categoryKey) => ({ id: String(id), get: key => ({ courseId: id, categoryKey, confirmed: true })[key] });
  12. context.companyIdOf = () => 'company-a';
  13. context.companyPointer = () => 'company-a';
  14. context.requireRole = () => {};
  15. context.reasonOf = () => 'mapping test';
  16. context.newClassObjects = async name => name === 'CourseTeachingProfile' ? [profile(454, 'word'), profile(464, 'word'), profile(398, 'word')] : [];
  17. before(async () => {
  18. await db.exec(`
  19. CREATE TABLE "Node" (company text,"nodeId" numeric,"parentId" numeric,"nodeName" text);
  20. CREATE TABLE "CourseBinding" (company text,kcid text);
  21. CREATE TABLE "CourseAppointment" (company text,id numeric,kcid text);
  22. CREATE TABLE "CommonModel" (company text,"modelId" numeric,"itemId" numeric,status numeric,subtitle text);
  23. INSERT INTO "Node" VALUES ('company-a',8,0,'小学'),('company-a',81,8,'小学语法写作课'),('company-b',464,0,'其他帐套课程');
  24. INSERT INTO "CourseBinding" VALUES ('company-a','81');
  25. INSERT INTO "CourseAppointment" VALUES ('company-a',1,'398'),('company-a',2,'454'),('company-a',3,'464'),('company-a',4,'999'),('company-b',5,'455');
  26. INSERT INTO "CommonModel" VALUES ('company-a',54,1,99,'阅读写作'),('company-a',54,2,99,'牛津上海版初中新课标'),('company-a',54,3,99,'体育单招1700词'),('company-a',54,4,-2,'已取消的缺失课程'),('company-b',54,5,99,'其他帐套课程');
  27. `);
  28. });
  29. after(() => db.close());
  30. test('writing maps only to its explicit school stage; generic writing is never suggested as word lessons', () => {
  31. for (const stage of ['primary', 'middle', 'high']) assert.equal(context.suggestedTeachingCategory('阅读写作', stage), stage + '_writing');
  32. assert.equal(context.suggestedTeachingCategory('语法写作课', ''), '');
  33. for (const name of ['牛津上海版初中新课标', '体育单招1700词']) assert.equal(context.suggestedTeachingCategory(name, ''), 'word');
  34. });
  35. test('real course query recovers missing Node courses, preserves tenant isolation and stage checks', async () => {
  36. const rows = await context.courseTeachingRows({}, {});
  37. const byId = new Map(rows.map(row => [row.courseId, row]));
  38. assert.equal(byId.get(81).stageKey, 'primary');
  39. assert.equal(byId.get(81).suggestedCategoryKey, 'primary_writing');
  40. assert.equal(byId.get(454).stageKey, 'middle');
  41. assert.equal(byId.get(454).confirmed, true);
  42. assert.equal(byId.get(464).courseName, '体育单招1700词');
  43. assert.equal(byId.get(464).confirmed, true);
  44. assert.equal(byId.get(398).confirmed, false);
  45. assert.equal(byId.get(398).suggestedCategoryKey, '');
  46. assert.equal(byId.has(999), false);
  47. assert.equal(byId.has(455), false);
  48. });
  49. test('mapping rejects cross-stage writing, unknown courses and generic writing before saving', async () => {
  50. await assert.rejects(context.commitCourseMappings({}, {}, { items: [{ courseId: 81, categoryKey: 'high_writing' }] }), /语法写作分类必须/);
  51. await assert.rejects(context.commitCourseMappings({}, {}, { items: [{ courseId: 398, categoryKey: 'word' }] }), /语法写作课程缺少学段/);
  52. await assert.rejects(context.commitCourseMappings({}, {}, { items: [{ courseId: 999, categoryKey: 'word' }] }), /未找到该课程/);
  53. });