Files
huike-e2e-moicen/tests/org-data-isolation.spec.ts
T
weli d85027a933 feat(e2e): 核心全链路用例与辅助模块
新增 smoke-http、core-full-chain(访客深链守卫、登录黄金路径、可选健康 URL);抽出 music-room-session 辅助;Playwright 忽略本地 admin_debug;CI 注入可选 MOICEN_HEALTHCHECK_URL。

Made-with: Cursor
2026-04-28 11:53:48 +08:00

156 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { expect, test } from './fixtures';
import {
decodeJwtPayload,
detectOrgSelectState,
extractOrgIdFromTokenPayload,
resolveOrgContextForCoursePage,
establishSession,
waitForCourseRealmVisible,
} from './helpers/music-room-session';
const moicenUnionid = process.env.MOICEN_E2E_UNIONID?.trim();
test.describe('多机构数据隔离(会话与 token', () => {
test.describe.configure({ timeout: 180_000 });
test.skip(!moicenUnionid, '需要 MOICEN_E2E_UNIONIDSecret 或 .env.e2e');
test('本地 CurrentOrgId 与 JWT current_org_id 一致', async ({ page }) => {
await establishSession(page, moicenUnionid!);
if (
await page.getByText('请返回微信小程序完成登录').isVisible().catch(() => false)
) {
test.skip(true, '当前 unionid 会话未处于可用登录态');
}
await resolveOrgContextForCoursePage(page);
const storage = await page.evaluate(() => ({
currentOrgId: window.localStorage.getItem('CurrentOrgId'),
authorization: window.localStorage.getItem('Authorization'),
}));
expect(storage.authorization, '登录后应有 Authorization').toBeTruthy();
const payload = decodeJwtPayload(storage.authorization!);
expect(payload, 'Authorization 应为可解析的 JWT').not.toBeNull();
const jwtOrgId = extractOrgIdFromTokenPayload(payload);
expect(jwtOrgId, 'JWT(含 sub 嵌套)应能解析出 current_org_id').toBeTruthy();
if (storage.currentOrgId) {
expect(storage.currentOrgId).toBe(jwtOrgId);
}
});
test('机构选择页存在多个机构时,切换后 CurrentOrgId 变化', async ({
page,
}) => {
await establishSession(page, moicenUnionid!);
if (
await page.getByText('请返回微信小程序完成登录').isVisible().catch(() => false)
) {
test.skip(true, '当前 unionid 会话未处于可用登录态');
}
await resolveOrgContextForCoursePage(page);
await page.goto('/org/select', {
waitUntil: 'load',
timeout: 90_000,
});
await expect(page.locator('#app')).toBeVisible({ timeout: 90_000 });
await page.waitForURL(/\/org\/select/, { timeout: 90_000 });
if (
await page.getByText('请返回微信小程序完成登录').isVisible().catch(() => false)
) {
test.skip(true, '打开机构页时会话已退回访客态');
}
const orgSelectState = await detectOrgSelectState(page, 90_000);
if (orgSelectState === 'guest') {
test.skip(true, '打开机构页时会话已退回访客态');
}
if (orgSelectState === 'timeout') {
test.skip(true, `机构页未渲染可识别结构:${page.url()}`);
}
if (
await page.getByText('暂无可用机构').isVisible().catch(() => false)
) {
test.skip(true, '机构接口无数据,跳过多机构切换断言');
}
const orgCells = page.locator('#app .van-cell-group .van-cell');
const count = await orgCells.count();
test.skip(
count < 2,
'账号仅绑定单一机构时跳过切换断言(仍可通过 JWT 对齐用例校验隔离键)'
);
let beforeOrg: string | null = await page.evaluate(() =>
window.localStorage.getItem('CurrentOrgId')
);
if (!beforeOrg) {
const authSnap = await page.evaluate(() =>
window.localStorage.getItem('Authorization')
);
beforeOrg =
extractOrgIdFromTokenPayload(
decodeJwtPayload(authSnap ?? '')
) ?? null;
}
expect(beforeOrg, '切换前应能从 localStorage 或 JWT 解析当前机构').toBeTruthy();
await orgCells.nth(1).click();
await page.waitForURL(
(u) => {
try {
return new URL(u).pathname === '/' || new URL(u).pathname === '';
} catch {
return false;
}
},
{ timeout: 90_000 }
);
await expect(page.locator('#app')).toBeVisible({ timeout: 90_000 });
const afterOrg = await page.evaluate(() =>
window.localStorage.getItem('CurrentOrgId')
);
expect(afterOrg).toBeTruthy();
expect(afterOrg).not.toBe(beforeOrg);
const authAfter = await page.evaluate(() =>
window.localStorage.getItem('Authorization')
);
const payloadAfter = decodeJwtPayload(authAfter ?? '');
const jwtOrgAfter = extractOrgIdFromTokenPayload(payloadAfter);
expect(jwtOrgAfter, '切换后 JWT 应携带 current_org_id').toBeTruthy();
expect(jwtOrgAfter).toBe(afterOrg);
});
test('课程体系页加载成功且不进入列表错误态(机构维度接口可用)', async ({
page,
}) => {
await establishSession(page, moicenUnionid!);
if (
await page.getByText('请返回微信小程序完成登录').isVisible().catch(() => false)
) {
test.skip(true, '当前 unionid 会话未处于可用登录态');
}
await resolveOrgContextForCoursePage(page);
await page.goto('/course', {
waitUntil: 'domcontentloaded',
timeout: 90_000,
});
await expect(page.locator('#app')).toBeVisible({ timeout: 90_000 });
await waitForCourseRealmVisible(page);
const listError = page.getByText('请求失败,点击重新加载');
await expect(listError).toHaveCount(0, {
timeout: 45_000,
});
});
});