import { test, before, after } from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import vm from 'node:vm'; import { PGlite } from '@electric-sql/pglite'; const db = new PGlite(); const context = { Psql: { query: async (sql, values) => (await db.query(sql, values)).rows } }; vm.createContext(context); const source = readFileSync(new URL('../deploy-admin-functions.mjs', import.meta.url), 'utf8'); vm.runInContext(source.match(/const operationsGatewayCode = String.raw`([\s\S]*?)\n`;/)[1], context); const profile = (id, categoryKey) => ({ id: String(id), get: key => ({ courseId: id, categoryKey, confirmed: true })[key] }); context.companyIdOf = () => 'company-a'; context.companyPointer = () => 'company-a'; context.requireRole = () => {}; context.reasonOf = () => 'mapping test'; context.newClassObjects = async name => name === 'CourseTeachingProfile' ? [profile(454, 'word'), profile(464, 'word'), profile(398, 'word')] : []; before(async () => { await db.exec(` CREATE TABLE "Node" (company text,"nodeId" numeric,"parentId" numeric,"nodeName" text,zstatus numeric); CREATE TABLE "_User" (company text,"legacyUserId" numeric,"isDisabled" boolean,"isDeleted" boolean); CREATE TABLE "CourseBinding" ("objectId" text,company text,id numeric,yhid text,kcid text,"categoryKey" text,"categoryName" text,"enrollmentStatus" text,"updatedAt" timestamptz); CREATE TABLE "CourseAppointment" (company text,id numeric,kcid text); CREATE TABLE "CommonModel" ("objectId" text,company text,"modelId" numeric,"itemId" numeric,status numeric,subtitle text); INSERT INTO "Node" VALUES ('company-a',8,0,'小学',99),('company-a',81,8,'小学语法写作课',99),('company-b',464,0,'其他帐套课程',99); INSERT INTO "_User" VALUES ('company-a',20,false,false); INSERT INTO "CourseBinding" ("objectId",company,id,yhid,kcid,"enrollmentStatus") VALUES ('binding-81','company-a',81,20,'81','pending'),('binding-active','company-a',82,99,'81','active'); INSERT INTO "CourseAppointment" VALUES ('company-a',1,'398'),('company-a',2,'454'),('company-a',3,'464'),('company-a',4,'999'),('company-b',5,'455'); INSERT INTO "CommonModel" VALUES ('appointment-1','company-a',54,1,99,'阅读写作'),('appointment-2','company-a',54,2,99,'牛津上海版初中新课标'),('appointment-3','company-a',54,3,99,'体育单招1700词'),('appointment-4','company-a',54,4,-2,'已取消的缺失课程'),('appointment-5','company-b',54,5,99,'其他帐套课程'),('binding-common','company-a',58,81,99,''); `); }); after(() => db.close()); test('writing maps only to its explicit school stage; generic writing is never suggested as word lessons', () => { for (const stage of ['primary', 'middle', 'high']) assert.equal(context.suggestedTeachingCategory('阅读写作', stage), stage + '_writing'); assert.equal(context.suggestedTeachingCategory('语法写作课', ''), ''); for (const name of ['牛津上海版初中新课标', '体育单招1700词']) assert.equal(context.suggestedTeachingCategory(name, ''), 'word'); }); test('real course query recovers missing Node courses, preserves tenant isolation and stage checks', async () => { const rows = await context.courseTeachingRows({}, {}); const byId = new Map(rows.map(row => [row.courseId, row])); assert.equal(byId.get(81).stageKey, 'primary'); assert.equal(byId.get(81).suggestedCategoryKey, 'primary_writing'); assert.equal(byId.get(454).stageKey, 'middle'); assert.equal(byId.get(454).confirmed, true); assert.equal(byId.get(464).courseName, '体育单招1700词'); assert.equal(byId.get(464).confirmed, true); assert.equal(byId.get(398).confirmed, false); assert.equal(byId.get(398).suggestedCategoryKey, ''); assert.equal(byId.has(999), false); assert.equal(byId.has(455), false); }); test('mapping rejects cross-stage writing, unknown courses and generic writing before saving', async () => { await assert.rejects(context.commitCourseMappings({}, {}, { items: [{ courseId: 81, categoryKey: 'high_writing' }] }), /语法写作分类必须/); await assert.rejects(context.commitCourseMappings({}, {}, { items: [{ courseId: 398, categoryKey: 'word' }] }), /语法写作课程缺少学段/); await assert.rejects(context.commitCourseMappings({}, {}, { items: [{ courseId: 999, categoryKey: 'word' }] }), /未找到该课程/); }); test('confirmed course mapping activates a valid unambiguous historical binding', async () => { const rows = await context.syncConfirmedCourseBindings({}, {}, 81, 'primary_writing', '小学语法写作课'); assert.deepEqual(rows.map(row => row.enrollmentStatus), ['active']); const binding = (await db.query('SELECT "categoryKey","categoryName","enrollmentStatus" FROM "CourseBinding" WHERE "objectId"=\'binding-81\'')).rows[0]; assert.deepEqual(binding, { categoryKey: 'primary_writing', categoryName: '小学语法写作课', enrollmentStatus: 'active' }); const preserved = (await db.query('SELECT "categoryKey","enrollmentStatus" FROM "CourseBinding" WHERE "objectId"=\'binding-active\'')).rows[0]; assert.deepEqual(preserved, { categoryKey: null, enrollmentStatus: 'active' }); });