/** * buildShrimpRouter 独立装配冒烟测试 * * 验证 buildShrimpRouter 不依赖 ShrimpServer 类,消费方完全可以: * - 用 MemoryShrimpCrud(而非 MongoShrimpCrud) * - 自己造 ITokenAuth(而非默认 JWT) * - 自己实现 IUserAccessor * 然后拼出可工作的 Express Router。 * * 这是"内部解耦"的核心契约 — 如果哪天 buildShrimpRouter 偷偷耦合回 * ShrimpServer / mongoose / jsonwebtoken,本测试会编译失败或运行失败。 */ import { describe, it, expect } from "vitest" import express from "express" import { Shrimp, MemoryShrimpCrud, UserModule, RoleModule, OperationLogModule, plainHasher, } from "../src/main" import { buildShrimpRouter } from "../src/forExpress/buildShrimpRouter" import type { ITokenAuth, IUserAccessor } from "../src/forExpress/types" import type { WithId, IUser, IRole } from "../src/main" function makeFakeTokenAuth(): ITokenAuth { const store = new Map>() let seq = 0 return { sign(payload) { const tok = `tok-${++seq}` store.set(tok, payload) return tok }, verify(token) { const payload = store.get(token) if (!payload) throw new Error("invalid token") return payload }, } } const userAccessor: IUserAccessor = { getJwtPayload: (user) => ({ _id: user._id }), isUserActive: (user) => user.active === true, getUserRoleIds: (user) => Array.isArray(user.roleId) ? user.roleId.map(String) : user.roleId ? [String(user.roleId)] : [], isRoleSuperAdmin: (role) => !!role?.isSuperAdmin, getRolePowers: (role) => role?.powers || [], getUserDisplayName: (user) => user.nickname || "", } async function makeApp() { Shrimp.passwordHasher = plainHasher const shrimp = new Shrimp() const userCrud = new MemoryShrimpCrud>() const roleCrud = new MemoryShrimpCrud>() const logCrud = new MemoryShrimpCrud() const userModule = new UserModule(shrimp, userCrud) const roleModule = new RoleModule(shrimp, roleCrud) const operationLogModule = new OperationLogModule(userModule, logCrud) const adminRole = await roleCrud.add({ name: "admin", description: "", isSuperAdmin: true, powers: [], } as any) await userModule.add({ username: "alice", password: "secret", nickname: "Alice", roleId: [adminRole._id], active: true, } as any) const tokenAuth = makeFakeTokenAuth() const router = buildShrimpRouter({ shrimp, userModule, roleModule, operationLogModule, tokenAuth, config: { title: "smoke" }, globalOptions: {}, forcedOptionAccess: new Map(), userAccessor, }) const app = express() app.use(express.json()) app.use(router) return app } async function jsonFetch(app: express.Express, method: string, path: string, body?: any, headers: Record = {}) { return new Promise<{ status: number; body: any }>((resolve, reject) => { const server = app.listen(0, () => { const port = (server.address() as any).port const data = body == null ? undefined : JSON.stringify(body) const req = require("http").request( { method, host: "127.0.0.1", port, path, headers: { "content-type": "application/json", ...(data ? { "content-length": Buffer.byteLength(data) } : {}), ...headers, }, }, (res: any) => { let buf = "" res.on("data", (c: any) => (buf += c)) res.on("end", () => { server.close() try { resolve({ status: res.statusCode, body: JSON.parse(buf) }) } catch (e) { reject(new Error("non-JSON body: " + buf)) } }) } ) req.on("error", reject) if (data) req.write(data) req.end() }) }) } describe("buildShrimpRouter 独立装配", () => { it("可以用纯内存 CRUD + 假 token 鉴权拼出可工作的 Router", async () => { const app = await makeApp() // /config 不需要鉴权 const cfg = await jsonFetch(app, "GET", "/config") expect(cfg.body.data).toEqual({ title: "smoke" }) // /login 用注入的 IUserAccessor + fake tokenAuth const login = await jsonFetch(app, "POST", "/login", { username: "alice", password: "secret" }) expect(login.body.code).toBe(0) const token = login.body.data.token expect(typeof token).toBe("string") // 无 token 拒绝 const noTok = await jsonFetch(app, "GET", "/me") expect(noTok.body.code).not.toBe(0) // 有 token 通过 const me = await jsonFetch(app, "GET", "/me", undefined, { authorization: `Bearer ${token}` }) expect(me.body.code).toBe(0) expect(me.body.data.nickname).toBe("Alice") }) })