Files
ClassHelper/scripts/ui-smoke/live-probe.mjs
T

158 lines
5.2 KiB
JavaScript
Raw Normal View History

/**
* UI 冒烟测试用「真实上课时段探针」。
*
* 为了验证「上课时段发布紧急通知 → 全屏警告 + 3 秒倒计时」这条链路,
* 测试必须先让服务端真实认定"现在正在上课"。本模块直接用 HTTP 调后端接口:
*
* 1. 登录(拿到 token)
* 2. 取该教师第一个班级 + 该班级第一门课程
* 3. 新建一条"今天 00:00-23:59、第 1-30 教学周"的课表 —— 覆盖当前时刻
* 4. 调 /api/schedules/current 确认服务端确实返回 inClass = true
*
* 测试结束后 cleanup 会删除这条探针课表,并删除本次测试发布的通知,
* 保证不污染演示数据。
*
* 注意:这里只使用 Node 内置 fetch(Node 20+),不引入额外依赖。
*/
const DEFAULT_TIMEOUT_MS = 10_000;
async function call(baseUrl, path, { method = 'GET', token, body } = {}) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
try {
const response = await fetch(`${baseUrl}${path}`, {
method,
signal: controller.signal,
headers: {
...(body ? { 'content-type': 'application/json' } : {}),
...(token ? { authorization: `Bearer ${token}` } : {}),
},
...(body ? { body: JSON.stringify(body) } : {}),
});
const text = await response.text();
let payload = null;
try {
payload = text ? JSON.parse(text) : null;
} catch {
payload = { raw: text.slice(0, 200) };
}
return { status: response.status, payload };
} finally {
clearTimeout(timer);
}
}
/** 今天对应的课表星期(1=周一 … 7=周日) */
function todayDayOfWeek(now = new Date()) {
const weekday = now.getDay();
return weekday === 0 ? 7 : weekday;
}
/**
* 制造一次真实的上课时段。
* @returns {Promise<{token: string, classId: string, className: string, courseId: string,
* scheduleId: string, inClass: boolean, period: object|null}>}
*/
export async function setupInClassProbe({
baseUrl,
username = 'teacher1',
password = 'teacher123',
classId,
} = {}) {
const base = (baseUrl ?? 'http://127.0.0.1:4000').replace(/\/$/, '');
const login = await call(base, '/api/auth/login', {
method: 'POST',
body: { username, password },
});
const token = login.payload?.data?.token;
if (login.status !== 200 || !token) {
throw new Error(`探针登录失败:HTTP ${login.status} ${JSON.stringify(login.payload)?.slice(0, 160)}`);
}
const classes = await call(base, '/api/classes', { token });
const targetClass = Array.isArray(classes.payload?.data)
? classId
? classes.payload.data.find((item) => item.id === classId)
: classes.payload.data[0]
: null;
if (!targetClass) throw new Error('探针失败:该教师名下没有班级,无法制造上课时段');
const courses = await call(base, `/api/courses?classId=${encodeURIComponent(targetClass.id)}`, {
token,
});
const targetCourse = Array.isArray(courses.payload?.data) ? courses.payload.data[0] : null;
if (!targetCourse) throw new Error(`探针失败:班级「${targetClass.name}」没有课程,无法制造上课时段`);
const schedule = await call(base, '/api/schedules', {
method: 'POST',
token,
body: {
classId: targetClass.id,
courseId: targetCourse.id,
dayOfWeek: todayDayOfWeek(),
startTime: '00:00',
endTime: '23:59',
weekStart: 1,
weekEnd: 30,
location: 'UI 冒烟探针',
},
});
const scheduleId = schedule.payload?.data?.id;
if (schedule.status !== 201 || !scheduleId) {
throw new Error(
`探针失败:创建课表返回 HTTP ${schedule.status} ${JSON.stringify(schedule.payload)?.slice(0, 200)}`,
);
}
const status = await call(base, `/api/schedules/current?classId=${encodeURIComponent(targetClass.id)}`, {
token,
});
return {
baseUrl: base,
token,
classId: targetClass.id,
className: targetClass.name,
courseId: targetCourse.id,
scheduleId,
inClass: Boolean(status.payload?.data?.inClass),
period: status.payload?.data?.current ?? null,
statusCode: status.status,
};
}
/** 删除探针课表 + 本次测试发布的通知,恢复原始数据 */
export async function cleanupInClassProbe(probe, { notificationTitle } = {}) {
const removed = [];
if (!probe?.baseUrl || !probe?.token) return removed;
if (notificationTitle) {
const list = await call(
probe.baseUrl,
`/api/notifications?classId=${encodeURIComponent(probe.classId)}&keyword=${encodeURIComponent(notificationTitle)}`,
{ token: probe.token },
);
const matched = Array.isArray(list.payload?.data)
? list.payload.data.filter((item) => item.title === notificationTitle)
: [];
for (const item of matched) {
const result = await call(probe.baseUrl, `/api/notifications/${item.id}`, {
method: 'DELETE',
token: probe.token,
});
if (result.status === 200) removed.push(`notification:${item.id}`);
}
}
if (probe.scheduleId) {
const result = await call(probe.baseUrl, `/api/schedules/${probe.scheduleId}`, {
method: 'DELETE',
token: probe.token,
});
if (result.status === 200) removed.push(`schedule:${probe.scheduleId}`);
}
return removed;
}