import test from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { runInNewContext } from 'node:vm'; const source = readFileSync(new URL('../deploy-admin-functions.mjs', import.meta.url), 'utf8'); const gateway = source.slice(source.indexOf('async function queryUsers(context,input)')); function functionSource(name) { const start = gateway.search(new RegExp(`^(?:async )?function ${name}\\(`, 'm')); assert.ok(start >= 0, `Missing ${name}`); const rest = gateway.slice(start); const end = rest.slice(1).search(/^(?:async )?function /m); return end < 0 ? rest : rest.slice(0, end + 1); } for (const live of [false, true]) { test(`门店在学正常人数复用会员筛选,按归属汇总且不受门店分页影响(旧门店数据:${live})`, async () => { const member = (userId, parentUserId, learningStatus, status) => ({ userId, parentUserId, learningStatus, status }); const members = [ member(101, 1, 'studying', 'active'), member(102, 1, 'studying', 'active'), member(103, 1, 'studying', 'disabled'), member(104, 1, 'completed', 'active'), member(105, 1, 'trial', 'active'), member(106, 1, 'not_started', 'active'), member(201, 2, 'studying', 'active'), member(202, 2, 'completed', 'active'), member(401, 4, 'studying', 'active'), member(901, 0, 'studying', 'active'), ]; const stores = [1, 2, 3].map((id) => ({ objectId: `store-${id}`, legacyUserId: id, username: `门店${id}` })); const context = { number: (value, fallback = 0) => Number(value) || fallback, text: (value) => String(value ?? ''), companyIdOf: () => 'company', roleOf: () => 'ops-manager', legacyOf: (row) => row.legacyUserData || {}, mobileOf: () => '', maskMobile: () => '', registeredAtOf: () => '', firstDateIso: () => '', dateIso: () => '', businessDate: () => '2026-09-25', Psql: { query: async (sql) => sql.startsWith('SELECT u.*') ? stores : [] }, coachRows: async () => [], appointmentSnapshot: async () => ({ appointments: [], live: {} }), optionalLegacyMemberSnapshot: async () => ({ available: live, coachIds: [], items: live ? [{ userId: 4, groupId: 2, username: '旧门店4' }] : [] }), queryUsers: async () => members, operationalSyncIndicator: async () => ({}), searchRows: (rows) => rows, }; runInNewContext(['pageInput', 'pageRows', 'filterMembers', 'storeNameOf', 'storeMobileOf', 'storeRows'].map(functionSource).join('\n') + '\nthis.listStores = storeRows;', context); const result = await context.listStores({}, { page: 1, pageSize: 20 }); const byId = new Map(result.items.map((row) => [row.userId, row])); assert.equal(byId.get(1).memberCount, 6); assert.equal(byId.get(1).activeStudyingMemberCount, 2); assert.equal(byId.get(2).activeStudyingMemberCount, 1); assert.equal(byId.get(3).activeStudyingMemberCount, 0); if (live) assert.equal(byId.get(4).activeStudyingMemberCount, 1); for (const row of result.items) { const expected = context.filterMembers(members, { filters: { parentUserId: row.userId, status: 'active', learningStatus: 'studying' } }).length; assert.equal(row.activeStudyingMemberCount, expected); } const paged = await context.listStores({}, { page: 2, pageSize: 1 }); assert.equal(paged.items.length, 1); assert.equal(paged.items[0].activeStudyingMemberCount, byId.get(paged.items[0].userId).activeStudyingMemberCount); }); }