2026-04-28 08:45:12 +08:00
|
|
|
|
import { expect, test } from './fixtures';
|
2026-04-28 11:53:48 +08:00
|
|
|
|
import {
|
|
|
|
|
|
decodeJwtPayload,
|
|
|
|
|
|
detectOrgSelectState,
|
|
|
|
|
|
extractOrgIdFromTokenPayload,
|
|
|
|
|
|
resolveOrgContextForCoursePage,
|
|
|
|
|
|
establishSession,
|
|
|
|
|
|
waitForCourseRealmVisible,
|
|
|
|
|
|
} from './helpers/music-room-session';
|
2026-04-28 08:09:36 +08:00
|
|
|
|
|
|
|
|
|
|
const moicenUnionid = process.env.MOICEN_E2E_UNIONID?.trim();
|
|
|
|
|
|
|
|
|
|
|
|
test.describe('多机构数据隔离(会话与 token)', () => {
|
2026-04-28 10:04:19 +08:00
|
|
|
|
test.describe.configure({ timeout: 180_000 });
|
|
|
|
|
|
|
2026-04-28 08:09:36 +08:00
|
|
|
|
test.skip(!moicenUnionid, '需要 MOICEN_E2E_UNIONID(Secret 或 .env.e2e)');
|
|
|
|
|
|
|
|
|
|
|
|
test('本地 CurrentOrgId 与 JWT current_org_id 一致', async ({ page }) => {
|
2026-04-28 11:53:48 +08:00
|
|
|
|
await establishSession(page, moicenUnionid!);
|
2026-04-28 08:09:36 +08:00
|
|
|
|
if (
|
|
|
|
|
|
await page.getByText('请返回微信小程序完成登录').isVisible().catch(() => false)
|
|
|
|
|
|
) {
|
|
|
|
|
|
test.skip(true, '当前 unionid 会话未处于可用登录态');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 08:17:54 +08:00
|
|
|
|
await resolveOrgContextForCoursePage(page);
|
|
|
|
|
|
|
2026-04-28 08:09:36 +08:00
|
|
|
|
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();
|
2026-04-28 08:26:20 +08:00
|
|
|
|
const jwtOrgId = extractOrgIdFromTokenPayload(payload);
|
|
|
|
|
|
expect(jwtOrgId, 'JWT(含 sub 嵌套)应能解析出 current_org_id').toBeTruthy();
|
2026-04-28 08:34:50 +08:00
|
|
|
|
if (storage.currentOrgId) {
|
|
|
|
|
|
expect(storage.currentOrgId).toBe(jwtOrgId);
|
|
|
|
|
|
}
|
2026-04-28 08:09:36 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('机构选择页存在多个机构时,切换后 CurrentOrgId 变化', async ({
|
|
|
|
|
|
page,
|
|
|
|
|
|
}) => {
|
2026-04-28 11:53:48 +08:00
|
|
|
|
await establishSession(page, moicenUnionid!);
|
2026-04-28 08:09:36 +08:00
|
|
|
|
if (
|
|
|
|
|
|
await page.getByText('请返回微信小程序完成登录').isVisible().catch(() => false)
|
|
|
|
|
|
) {
|
|
|
|
|
|
test.skip(true, '当前 unionid 会话未处于可用登录态');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 08:17:54 +08:00
|
|
|
|
await resolveOrgContextForCoursePage(page);
|
|
|
|
|
|
|
2026-04-28 08:09:36 +08:00
|
|
|
|
await page.goto('/org/select', {
|
2026-04-28 10:20:04 +08:00
|
|
|
|
waitUntil: 'load',
|
|
|
|
|
|
timeout: 90_000,
|
2026-04-28 08:09:36 +08:00
|
|
|
|
});
|
2026-04-28 10:20:04 +08:00
|
|
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 90_000 });
|
|
|
|
|
|
await page.waitForURL(/\/org\/select/, { timeout: 90_000 });
|
2026-04-28 10:26:45 +08:00
|
|
|
|
|
|
|
|
|
|
if (
|
|
|
|
|
|
await page.getByText('请返回微信小程序完成登录').isVisible().catch(() => false)
|
|
|
|
|
|
) {
|
|
|
|
|
|
test.skip(true, '打开机构页时会话已退回访客态');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 10:59:24 +08:00
|
|
|
|
const orgSelectState = await detectOrgSelectState(page, 90_000);
|
|
|
|
|
|
if (orgSelectState === 'guest') {
|
|
|
|
|
|
test.skip(true, '打开机构页时会话已退回访客态');
|
|
|
|
|
|
}
|
|
|
|
|
|
if (orgSelectState === 'timeout') {
|
|
|
|
|
|
test.skip(true, `机构页未渲染可识别结构:${page.url()}`);
|
|
|
|
|
|
}
|
2026-04-28 10:26:45 +08:00
|
|
|
|
|
2026-04-28 10:20:04 +08:00
|
|
|
|
if (
|
|
|
|
|
|
await page.getByText('暂无可用机构').isVisible().catch(() => false)
|
|
|
|
|
|
) {
|
|
|
|
|
|
test.skip(true, '机构接口无数据,跳过多机构切换断言');
|
|
|
|
|
|
}
|
2026-04-28 08:09:36 +08:00
|
|
|
|
|
2026-04-28 10:26:45 +08:00
|
|
|
|
const orgCells = page.locator('#app .van-cell-group .van-cell');
|
2026-04-28 08:09:36 +08:00
|
|
|
|
const count = await orgCells.count();
|
|
|
|
|
|
test.skip(
|
|
|
|
|
|
count < 2,
|
|
|
|
|
|
'账号仅绑定单一机构时跳过切换断言(仍可通过 JWT 对齐用例校验隔离键)'
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-04-28 08:34:50 +08:00
|
|
|
|
let beforeOrg: string | null = await page.evaluate(() =>
|
2026-04-28 08:09:36 +08:00
|
|
|
|
window.localStorage.getItem('CurrentOrgId')
|
|
|
|
|
|
);
|
2026-04-28 08:34:50 +08:00
|
|
|
|
if (!beforeOrg) {
|
|
|
|
|
|
const authSnap = await page.evaluate(() =>
|
|
|
|
|
|
window.localStorage.getItem('Authorization')
|
|
|
|
|
|
);
|
|
|
|
|
|
beforeOrg =
|
|
|
|
|
|
extractOrgIdFromTokenPayload(
|
|
|
|
|
|
decodeJwtPayload(authSnap ?? '')
|
|
|
|
|
|
) ?? null;
|
|
|
|
|
|
}
|
|
|
|
|
|
expect(beforeOrg, '切换前应能从 localStorage 或 JWT 解析当前机构').toBeTruthy();
|
2026-04-28 08:09:36 +08:00
|
|
|
|
|
|
|
|
|
|
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 ?? '');
|
2026-04-28 08:26:20 +08:00
|
|
|
|
const jwtOrgAfter = extractOrgIdFromTokenPayload(payloadAfter);
|
|
|
|
|
|
expect(jwtOrgAfter, '切换后 JWT 应携带 current_org_id').toBeTruthy();
|
|
|
|
|
|
expect(jwtOrgAfter).toBe(afterOrg);
|
2026-04-28 08:09:36 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('课程体系页加载成功且不进入列表错误态(机构维度接口可用)', async ({
|
|
|
|
|
|
page,
|
|
|
|
|
|
}) => {
|
2026-04-28 11:53:48 +08:00
|
|
|
|
await establishSession(page, moicenUnionid!);
|
2026-04-28 08:09:36 +08:00
|
|
|
|
if (
|
|
|
|
|
|
await page.getByText('请返回微信小程序完成登录').isVisible().catch(() => false)
|
|
|
|
|
|
) {
|
|
|
|
|
|
test.skip(true, '当前 unionid 会话未处于可用登录态');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-28 08:17:54 +08:00
|
|
|
|
await resolveOrgContextForCoursePage(page);
|
|
|
|
|
|
|
2026-04-28 08:45:12 +08:00
|
|
|
|
await page.goto('/course', {
|
|
|
|
|
|
waitUntil: 'domcontentloaded',
|
|
|
|
|
|
timeout: 90_000,
|
|
|
|
|
|
});
|
|
|
|
|
|
await expect(page.locator('#app')).toBeVisible({ timeout: 90_000 });
|
|
|
|
|
|
await waitForCourseRealmVisible(page);
|
2026-04-28 08:09:36 +08:00
|
|
|
|
|
2026-04-28 10:04:19 +08:00
|
|
|
|
const listError = page.getByText('请求失败,点击重新加载');
|
|
|
|
|
|
await expect(listError).toHaveCount(0, {
|
|
|
|
|
|
timeout: 45_000,
|
|
|
|
|
|
});
|
2026-04-28 08:09:36 +08:00
|
|
|
|
});
|
|
|
|
|
|
});
|