Files
DormitoryNotice/smoke/smoke.mjs
T
2026-09-20 08:52:02 +08:00

162 lines
9.1 KiB
JavaScript
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.
#!/usr/bin/env node
/**
* 宿舍通知(DormitoryNotice)后端 P8 端到端冒烟脚本。
*
* 背景:/api/* 受基础平台 CAS/SSO 过滤器保护,无登录态会返回 302→SSO,脚本无法自证身份。
* 因此用「浏览器登录后复制 Cookie」的方式携带会话:
* 1) 起后端:先跑 sql/dormnotice_init.sql 建库+字典,再 `java -jar target/dormnotice.jar`
* (或 IDEA 运行 DormNoticeApplication,端口 8105)。
* 2) 浏览器用教师账号 lijing 走 SSO 登录前端(http://localhost:8078 或后端直连),
* 打开 DevTools→Network→任一 /api 请求,复制其「Cookie」请求头整串值。
* 3) 同样用学生账号(kegemo / csxs1)登录,复制一份学生 Cookie。
* 4) 运行:
* TEACHER_COOKIE='<粘贴教师Cookie>' STUDENT_COOKIE='<粘贴学生Cookie>' \
* node smoke/smoke.mjs
* 可选:BASE(默认 http://localhost:8105)。
*
* 只读为主;Create 冒烟会创建一条草稿再删除,不留脏数据。退出码非 0 表示有必过项失败。
*/
const BASE = (process.env.BASE || "http://localhost:8105").replace(/\/+$/, "");
const TEACHER = process.env.TEACHER_COOKIE || "";
const STU = process.env.STUDENT_COOKIE || "";
// 写链路默认执行(用一次性数据并自清理);设 WRITE=0 跳过所有增删改,仅跑只读冒烟。
const WRITE = (process.env.WRITE || "1") !== "0";
const results = [];
let failed = 0;
function hdr(cookie, extra = {}) {
const h = { Accept: "application/json", ...extra };
if (cookie) h.Cookie = cookie;
return h;
}
async function req(label, method, path, { cookie = TEACHER, body, form, json = true, must = true } = {}) {
const url = BASE + path;
const headers = hdr(cookie);
let payload;
if (body !== undefined) { headers["Content-Type"] = "application/json;charset=UTF-8"; payload = JSON.stringify(body); }
else if (form !== undefined) { headers["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8"; payload = new URLSearchParams(form).toString(); }
let status, data, raw;
try {
const resp = await fetch(url, { method, headers, body: payload, redirect: "manual" });
status = resp.status;
raw = await resp.text();
if (json) { try { data = JSON.parse(raw); } catch { data = null; } }
} catch (e) {
push(label, false, `网络错误 ${e.message}`, must);
return null;
}
if (status === 302 || status === 301) {
push(label, false, `${status}→SSO 重定向:Cookie 无效/未登录`, must);
return null;
}
push(label, status === 200 && (!json || (data && data.code === 0)), `HTTP ${status} code=${data && data.code} msg=${data && data.msg}`, must);
return data ? data.data : raw;
}
function push(name, ok, detail, must) {
results.push({ name, ok, detail, must });
if (!ok && must) failed++;
console.log(`${ok ? "PASS" : "FAIL"} ${name} — ${detail}`);
}
async function main() {
console.log(`\n=== DormNotice 冒烟 @ ${BASE} ===`);
if (!TEACHER) { console.log("⚠ 未提供 TEACHER_COOKIE,鉴权类接口会 302。请先按脚本头注释获取。"); }
// 0. 免鉴权健康(actuator 返回 {status:UP},非 {code} 信封,只验 HTTP 200)
await req("actuator/health", "GET", "/actuator/health", { must: false, json: false });
// 1. 平台代理
await req("base/GetUserInfo", "GET", "/api/base/GetUserInfo");
await req("base/GetUserMod", "GET", "/api/base/GetUserMod");
await req("base/GetDormTree", "GET", "/api/base/GetDormTree", { must: false });
await req("base/SetRole(parent)", "POST", "/api/base/SetRole", { cookie: STU || TEACHER, body: { as: "parent" }, must: false });
// 2. 字典(B2:type 参数化 + 无“文字重复”)
const types = await req("dict/List?noticeType", "GET", "/api/dict/List?type=noticeType");
const depts = await req("dict/ManageList?dept", "GET", "/api/dict/ManageList?type=dept");
await req("dict/Sync", "GET", "/api/dict/Sync", { must: false });
// 断言无逗号重复名
if (Array.isArray(types)) {
const dup = types.some(t => String(t.v || t.k || "").includes(","));
push("字典名无“X,X”重复", !dup, dup ? "存在含逗号字典名,检查 B1 是否真后端未装" : "OK", true);
}
// 3. 宿舍域
const buildings = await req("dorm/BuildingList", "GET", "/api/dorm/BuildingList", { must: false });
await req("dorm/RoomList", "GET", "/api/dorm/RoomList", { must: false });
await req("dorm/PendingStudents", "GET", "/api/dorm/PendingStudents", { must: false });
// 4. 通知:FormTemplates / SendList / Create(草稿)→Detail→Delete
await req("notice/FormTemplates", "GET", "/api/notice/FormTemplates");
await req("notice/SendList", "GET", "/api/notice/SendList?pageIndex=1&pageSize=10");
await req("notice/Statis", "GET", "/api/notice/Statis", { must: false });
const deptCode = Array.isArray(depts) && depts[0] ? depts[0].CodeID : "D002";
const receiveList = Array.isArray(buildings) && buildings[0] ? String(buildings[0].buildingId * 10000) : "ALL";
// 4b. 通知写链路:Create(草稿) → Edit 改标题 → GetDetail 校验 → Delete 自清理
if (WRITE && TEACHER) {
const nid = await req("notice/Create(draft)", "POST", "/api/notice/Create", {
body: { title: "【冒烟】通知A", content: "<p>smoke</p>", noticeType: "其他",
level: "normal", receiptMode: "none", deptId: deptCode, receiveType: "room",
receiveList, draft: true },
});
if (nid) {
await req("notice/Edit(改标题)", "POST", "/api/notice/Edit", { body: { id: nid, title: "【冒烟】通知B", receiptMode: "none" } });
const d = await req("notice/GetDetail(校验)", "GET", `/api/notice/GetDetail?serid=${nid}`);
push("notice/Edit 生效(标题→B)", !!(d && /通知B/.test(String(d.title || ""))), `title=${d && d.title}`, false);
await req("notice/Delete(cleanup)", "POST", "/api/notice/Delete", { body: [nid] });
}
}
// 5. 家长端(学生 Cookie)
await req("notice/List(parent)", "GET", "/api/notice/List?pageIndex=1&pageSize=20", { cookie: STU, must: false });
await req("notice/MyNotices(parent)", "GET", "/api/notice/MyNotices", { cookie: STU, must: false });
// 6. 写链路自清理(用一次性数据,跑完即删;设 WRITE=0 跳过)
if (WRITE && TEACHER) {
// 6.1 字典:Add(noticeType) → ManageList 查得 → Delete
const tn = "冒烟类型" + Date.now();
await req("dict/Add(noticeType)", "POST", "/api/dict/Add", { form: { name: tn, type: "noticeType" } });
const ml = await req("dict/ManageList(noticeType)", "GET", "/api/dict/ManageList?type=noticeType", { must: false });
const dictItem = Array.isArray(ml) ? ml.find(x => x.Name === tn) : null;
push("dict/Add→ManageList 可查得", !!dictItem, dictItem ? "SerID=" + dictItem.SerID : "未找到新增项", false);
if (dictItem) await req("dict/Delete(cleanup)", "POST", "/api/dict/Delete", { form: { serid: dictItem.SerID, type: "noticeType" } });
// 6.2 联系组:AddGroup → GetGroup 查得 → DelGroup
const gn = "冒烟组" + Date.now();
await req("contacts/AddGroup", "POST", "/api/contacts/AddGroup", { form: { name: gn } });
const gp = await req("contacts/GetGroup", "GET", "/api/contacts/GetGroup?page=1&count=200", { must: false });
const grp = gp && Array.isArray(gp.list) ? gp.list.find(x => x.GroupName === gn) : null;
push("contacts/AddGroup→GetGroup 可查得", !!grp, grp ? "serid=" + grp.serid : "未找到", false);
if (grp) await req("contacts/DelGroup(cleanup)", "POST", "/api/contacts/DelGroup", { form: { ids: grp.serid } });
// 6.3 宿舍:AddBuilding → BuildingList 查得 → AddRoom → RoomList 查得 → DelRoom → DelBuilding
const bn = "冒烟楼" + Date.now();
await req("dorm/AddBuilding", "POST", "/api/dorm/AddBuilding", { form: { buildingName: bn, gender: "混合", floorCount: 1, roomsPerFloor: 1, bedsPerRoom: 2 } });
const bl = await req("dorm/BuildingList", "GET", "/api/dorm/BuildingList", { must: false });
const b = Array.isArray(bl) ? bl.find(x => x.buildingName === bn) : null;
push("dorm/AddBuilding→BuildingList 可查得", !!b, b ? "buildingId=" + b.buildingId : "未找到", false);
if (b) {
await req("dorm/AddRoom", "POST", "/api/dorm/AddRoom", { form: { buildingId: b.buildingId, floor: 1, roomNo: "901", bedCount: 2 } });
const rl = await req("dorm/RoomList", "GET", "/api/dorm/RoomList?buildingId=" + b.buildingId, { must: false });
const room = Array.isArray(rl) ? rl.find(x => x.roomNo === "901") : null;
push("dorm/AddRoom→RoomList 可查得", !!room, room ? "roomId=" + room.roomId : "未找到", false);
if (room) await req("dorm/DelRoom(cleanup)", "POST", "/api/dorm/DelRoom", { form: { roomId: room.roomId } });
await req("dorm/DelBuilding(cleanup)", "POST", "/api/dorm/DelBuilding", { form: { buildingId: b.buildingId } });
}
} else {
console.log("SKIP 写链路(WRITE=0 或缺 TEACHER_COOKIE)");
}
console.log("\n=== 汇总 ===");
const pass = results.filter(r => r.ok).length;
console.log(`${pass}/${results.length} 通过;必过失败 ${failed} 项`);
process.exit(failed ? 1 : 0);
}
main().catch(e => { console.error("脚本异常:", e); process.exit(2); });