test(course-package): add org_visible and course_package_item sync E2E tests

- API test: course_group org_visible toggle (create → visible → list → invisible)
- API test: course_package_item sync with SUPERVISOR permission check
- UI test: "包含课节" picker link on course-package add page
- UI test: "开放给机构" switch on course-group add page

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-30 14:31:25 +08:00
parent 524c27c278
commit de058d1e5e
+225
View File
@@ -213,6 +213,159 @@ test.describe('课包(course_package', () => {
expect(typeof orgBody.d?.[2] === 'number').toBe(true);
});
test('course_group org_visible 开关全链路:创建 → 设为机构可见 → 查询可见列表 → 取消可见 → 验证移除', async ({
page,
request,
}) => {
const authToken = await loginAndGetJwt(page);
const headers: Record<string, string> = {
Authorization: authToken,
HtySudoerToken: authToken,
HtyHost: new URL(kcBase).hostname,
};
const groupName = `e2e-test-org-visible-${Date.now()}`;
let groupId: string;
// ---- CREATE course_group ----
const createRes = await request.post(`${kcBase}/api/v1/ws/create_course_group`, {
headers: { ...headers, 'Content-Type': 'application/json' },
data: { group_name: groupName },
});
expect(createRes.ok(), `CREATE GROUP HTTP ${createRes.status()}`).toBeTruthy();
const createBody = await createRes.json();
expect(createBody.r, `CREATE GROUP 业务失败: ${JSON.stringify(createBody)}`).toBe(true);
expect(createBody.d?.id).toBeTruthy();
expect(createBody.d?.group_name).toBe(groupName);
groupId = createBody.d.id;
try {
// ---- UPDATE org_visible=true ----
const updateVisibleRes = await request.post(`${kcBase}/api/v1/ws/update_course_group`, {
headers: { ...headers, 'Content-Type': 'application/json' },
data: { id: groupId, group_name: groupName, org_visible: true },
});
expect(updateVisibleRes.ok(), `UPDATE VISIBLE HTTP ${updateVisibleRes.status()}`).toBeTruthy();
const updateVisibleBody = await updateVisibleRes.json();
expect(updateVisibleBody.r, `UPDATE VISIBLE 业务失败: ${JSON.stringify(updateVisibleBody)}`).toBe(true);
// ---- FIND org-visible groups - should include our group ----
const visibleRes = await request.get(`${kcBase}/api/v1/ws/find_org_visible_course_groups`, {
headers,
});
expect(visibleRes.ok(), `FIND VISIBLE HTTP ${visibleRes.status()}`).toBeTruthy();
const visibleBody = await visibleRes.json();
expect(visibleBody.r, `FIND VISIBLE 业务失败: ${JSON.stringify(visibleBody)}`).toBe(true);
const visibleList: any[] = visibleBody.d ?? [];
const foundVisible = visibleList.some((g: any) => g.id === groupId);
expect(foundVisible, 'org_visible=true 的分组应出现在机构可见列表中').toBe(true);
// ---- UPDATE org_visible=false ----
const updateInvisibleRes = await request.post(`${kcBase}/api/v1/ws/update_course_group`, {
headers: { ...headers, 'Content-Type': 'application/json' },
data: { id: groupId, group_name: groupName, org_visible: false },
});
expect(updateInvisibleRes.ok(), `UPDATE INVISIBLE HTTP ${updateInvisibleRes.status()}`).toBeTruthy();
// ---- FIND org-visible groups - should NOT include our group ----
const invisibleRes = await request.get(`${kcBase}/api/v1/ws/find_org_visible_course_groups`, {
headers,
});
expect(invisibleRes.ok(), `FIND INVISIBLE HTTP ${invisibleRes.status()}`).toBeTruthy();
const invisibleBody = await invisibleRes.json();
expect(invisibleBody.r, `FIND INVISIBLE 业务失败: ${JSON.stringify(invisibleBody)}`).toBe(true);
const invisibleList: any[] = invisibleBody.d ?? [];
const foundInvisible = invisibleList.some((g: any) => g.id === groupId);
expect(foundInvisible, 'org_visible=false 的分组不应出现在机构可见列表中').toBe(false);
} finally {
// ---- CLEANUP: delete course_group ----
await request.post(`${kcBase}/api/v1/ws/delete_course_group/${groupId}`, { headers });
}
});
test('course_package_item sync 全链路(SUPERVISOR):创建分组 → 设 org_visible → 创建课包 → sync → 列表验证', async ({
page,
request,
}) => {
const authToken = await loginAndGetJwt(page);
const payload = decodeJwtPayload(authToken);
const roles = extractRoleKeys(payload);
const isSupervisor = roles.includes('SUPERVISOR');
test.skip(!isSupervisor, '当前账号无 SUPERVISOR 权限,跳过 sync 测试');
const headers: Record<string, string> = {
Authorization: authToken,
HtySudoerToken: authToken,
HtyHost: new URL(kcBase).hostname,
};
const ts = Date.now();
const groupName = `e2e-test-sync-group-${ts}`;
const pkgName = `e2e-test-sync-pkg-${ts}`;
let groupId: string;
let pkgId: string;
// ---- CREATE course_group with org_visible=true ----
const createGroupRes = await request.post(`${kcBase}/api/v1/ws/create_course_group`, {
headers: { ...headers, 'Content-Type': 'application/json' },
data: { group_name: groupName },
});
expect(createGroupRes.ok(), `CREATE GROUP HTTP ${createGroupRes.status()}`).toBeTruthy();
const createGroupBody = await createGroupRes.json();
expect(createGroupBody.r, `CREATE GROUP 业务失败: ${JSON.stringify(createGroupBody)}`).toBe(true);
groupId = createGroupBody.d.id;
await request.post(`${kcBase}/api/v1/ws/update_course_group`, {
headers: { ...headers, 'Content-Type': 'application/json' },
data: { id: groupId, group_name: groupName, org_visible: true },
});
try {
// ---- CREATE course_package ----
const createPkgRes = await request.post(`${kcBase}/api/v1/clazz/course-package/create`, {
headers: { ...headers, 'Content-Type': 'application/json' },
data: {
package_name: pkgName,
description: 'e2e test sync',
package_status: 'ACTIVE',
},
});
expect(createPkgRes.ok(), `CREATE PKG HTTP ${createPkgRes.status()}`).toBeTruthy();
const createPkgBody = await createPkgRes.json();
expect(createPkgBody.r, `CREATE PKG 业务失败: ${JSON.stringify(createPkgBody)}`).toBe(true);
expect(createPkgBody.d?.id).toBeTruthy();
pkgId = createPkgBody.d.id;
// ---- SYNC items ----
const syncRes = await request.post(`${kcBase}/api/v1/clazz/course-package/item/sync`, {
headers: { ...headers, 'Content-Type': 'application/json' },
data: { package_id: pkgId, course_group_ids: [groupId] },
});
expect(syncRes.ok(), `SYNC HTTP ${syncRes.status()}`).toBeTruthy();
const syncBody = await syncRes.json();
expect(syncBody.r, `SYNC 业务失败: ${JSON.stringify(syncBody)}`).toBe(true);
// ---- LIST items ----
const listRes = await request.get(`${kcBase}/api/v1/clazz/course-package/item/list/${pkgId}`, {
headers,
});
expect(listRes.ok(), `LIST ITEMS HTTP ${listRes.status()}`).toBeTruthy();
const listBody = await listRes.json();
expect(listBody.r, `LIST ITEMS 业务失败: ${JSON.stringify(listBody)}`).toBe(true);
const items: any[] = listBody.d ?? [];
const matched = items.some((g: any) => g.id === groupId);
expect(matched, 'sync 后列表应包含该分组').toBe(true);
} finally {
// ---- CLEANUP ----
if (pkgId) {
await request.post(`${kcBase}/api/v1/clazz/course-package/delete/${pkgId}`, { headers }).catch(() => {});
}
if (groupId) {
await request.post(`${kcBase}/api/v1/ws/delete_course_group/${groupId}`, { headers }).catch(() => {});
}
}
});
test('教师端 UI:导航栏出现"课包"入口,页面加载种子数据', async ({
page,
}) => {
@@ -413,4 +566,76 @@ test.describe('音乐教室端(huike-front)课包 UI', () => {
// 验证提交按钮
await expect(page.getByText('保存')).toBeVisible({ timeout: 10_000 });
});
test('课包新增页面:包含课节选择器可见', async ({ page }) => {
const q = new URLSearchParams({ unionid: moicenUnionid!, status: '2' });
await page.goto(`/?${q.toString()}`, {
waitUntil: 'domcontentloaded',
timeout: 60_000,
});
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
await page.waitForFunction(
() => !window.location.search.includes('status=2'),
{ timeout: 90_000 },
);
if (await page.getByText('请选择您的登录身份').isVisible().catch(() => false)) {
await page.locator('.van-grid-item').first().click();
await page.waitForFunction(
() => !window.location.search.includes('status=2'),
{ timeout: 30_000 },
);
}
const { resolveOrgContextForCoursePage } = await import('./helpers/music-room-session');
await resolveOrgContextForCoursePage(page);
await page.goto('/course/course-package/add', {
waitUntil: 'domcontentloaded',
timeout: 60_000,
});
await expect(page.locator('#app')).toBeVisible({ timeout: 30_000 });
// 验证"包含课节"选择器链接可见
const pickerCell = page.locator('.van-cell').filter({ hasText: '包含课节' });
await expect(pickerCell).toBeVisible({ timeout: 10_000 });
// 验证点击后跳转到选择页
await pickerCell.click();
await expect(page).toHaveURL(/\/course\/group\/pick/, { timeout: 10_000 });
});
test('课节新增页面:开放给机构开关可见', async ({ page }) => {
const q = new URLSearchParams({ unionid: moicenUnionid!, status: '2' });
await page.goto(`/?${q.toString()}`, {
waitUntil: 'domcontentloaded',
timeout: 60_000,
});
await expect(page.locator('#app')).toBeVisible({ timeout: 60_000 });
await page.waitForFunction(
() => !window.location.search.includes('status=2'),
{ timeout: 90_000 },
);
if (await page.getByText('请选择您的登录身份').isVisible().catch(() => false)) {
await page.locator('.van-grid-item').first().click();
await page.waitForFunction(
() => !window.location.search.includes('status=2'),
{ timeout: 30_000 },
);
}
const { resolveOrgContextForCoursePage } = await import('./helpers/music-room-session');
await resolveOrgContextForCoursePage(page);
await page.goto('/course/group/add', {
waitUntil: 'domcontentloaded',
timeout: 60_000,
});
await expect(page.locator('#app')).toBeVisible({ timeout: 30_000 });
// 验证"开放给机构"切换开关可见
const orgVisibleCell = page.locator('.van-cell').filter({ hasText: '开放给机构' });
await expect(orgVisibleCell).toBeVisible({ timeout: 10_000 });
// 验证开关组件存在
const switchEl = orgVisibleCell.locator('.van-switch');
await expect(switchEl).toBeVisible({ timeout: 10_000 });
});
});