From ed3814fccd01ba591f69da6b4dfbcac9a3744549 Mon Sep 17 00:00:00 2001 From: shunli Date: Sat, 26 Sep 2026 08:23:07 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=90=88=E5=B9=B6=E5=90=8E=E6=B8=85?= =?UTF-8?q?=E7=90=86=E9=87=8D=E5=A4=8D=E6=B5=8B=E8=AF=95=E4=B8=8E=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20CEO/=E8=B4=A2=E5=8A=A1=E5=85=9C=E5=BA=95=E8=84=9A?= =?UTF-8?q?=E6=9C=AC=E6=B3=A8=E5=85=A5=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - apply-ceo-finance-fallback.js: execute() 原先在传入 mock pool 时仍强制要求 DATABASE_URL 环境变量,导致单测无法注入 mock pool 验证事务逻辑。改为仅在 未传入 pool 时才检查 DATABASE_URL。 - portal.test.js: 删除本次合并前遗留的重复用例(期望旧的 portal.feature_disabled reasonCode),候选分支自带的等价用例更完整 (校验 reasonCode 以及全部零写入断言)。 - todos.test.js: 删除本次合并前遗留的文件,候选分支的 todos.errors.test.js 覆盖等价且更完整。 --- .../src/scripts/apply-ceo-finance-fallback.js | 7 +- server/tests/unit/routes/portal.test.js | 22 ------ server/tests/unit/routes/todos.test.js | 77 ------------------- 3 files changed, 5 insertions(+), 101 deletions(-) delete mode 100644 server/tests/unit/routes/todos.test.js diff --git a/server/src/scripts/apply-ceo-finance-fallback.js b/server/src/scripts/apply-ceo-finance-fallback.js index 26838f38..e98d7241 100644 --- a/server/src/scripts/apply-ceo-finance-fallback.js +++ b/server/src/scripts/apply-ceo-finance-fallback.js @@ -164,8 +164,11 @@ function countsEqual(left, right) { .every((field) => Number(left[field]) === Number(right[field])); } -async function execute(options, pool = new Pool({ connectionString: process.env.DATABASE_URL })) { - if (!process.env.DATABASE_URL) throw new Error("missing_database_url"); +async function execute(options, pool) { + if (!pool) { + if (!process.env.DATABASE_URL) throw new Error("missing_database_url"); + pool = new Pool({ connectionString: process.env.DATABASE_URL }); + } if (options.apply && process.env[APPLY_ENV_KEY] !== "1") { throw new Error(`apply_requires_${APPLY_ENV_KEY}`); } diff --git a/server/tests/unit/routes/portal.test.js b/server/tests/unit/routes/portal.test.js index 05467df4..8708fdd0 100644 --- a/server/tests/unit/routes/portal.test.js +++ b/server/tests/unit/routes/portal.test.js @@ -573,26 +573,4 @@ describe("portal routes", () => { await app.close(); }); - it("returns a precise reasonCode and skips business queries when portal home is disabled", async () => { - featureFlagState.portalHome = false; - const { app, prisma } = await buildApp({}); - - const response = await app.inject({ - method: "GET", - url: "/api/portal/home", - headers: { authorization: "Bearer test" }, - }); - - expect(response.statusCode).toBe(409); - expect(response.json()).toMatchObject({ - error: { - code: "VALID_001", - details: { featureFlag: "portal_home_v1_disabled", reason: "env_hard_off", reasonCode: "portal.feature_disabled" }, - }, - }); - expect(prisma.reimbursementForm.count).not.toHaveBeenCalled(); - expect(prisma.workflowTask.count).not.toHaveBeenCalled(); - - await app.close(); - }); }); diff --git a/server/tests/unit/routes/todos.test.js b/server/tests/unit/routes/todos.test.js deleted file mode 100644 index a77320a6..00000000 --- a/server/tests/unit/routes/todos.test.js +++ /dev/null @@ -1,77 +0,0 @@ -const Fastify = require("fastify"); -const todosRoutes = require("../../../src/routes/todos"); -const { AppError } = require("../../../src/utils/AppError"); - -async function buildApp({ featureEnabled = true } = {}) { - const app = Fastify(); - const prisma = { - featureFlag: { - findUnique: vi.fn(async () => ({ - flag_key: "todo_board_v1", - override_mode: featureEnabled ? "force_on" : "force_off", - rollout_type: "global", - department_ids: [], - })), - }, - todoItem: { - findMany: vi.fn(async () => []), - create: vi.fn(), - findUnique: vi.fn(), - update: vi.fn(), - }, - }; - app.decorate("prisma", prisma); - app.decorate("config", { todoBoardEnabled: true }); - app.decorate("authenticate", async (request) => { - request.employee = { id: "employee-1", department_id: "department-1", permissionCodes: [] }; - }); - app.decorate("hasPermission", () => false); - app.setErrorHandler((error, request, reply) => { - if (error instanceof AppError) { - return reply.code(error.statusCode).send({ error: { code: error.code, details: error.details ?? {} } }); - } - return reply.code(500).send({ error: { code: "SYS_001" } }); - }); - await app.register(todosRoutes, { prefix: "/api" }); - return { app, prisma }; -} - -describe("todos feature gate", () => { - it("returns a precise reasonCode and skips todo reads when the board is disabled", async () => { - const { app, prisma } = await buildApp({ featureEnabled: false }); - try { - const getResponse = await app.inject({ method: "GET", url: "/api/todos" }); - expect(getResponse.statusCode).toBe(409); - expect(getResponse.json()).toEqual({ - error: { - code: "VALID_001", - details: { featureFlag: "todo_board_v1_disabled", reason: "flag_force_off", reasonCode: "todo.feature_disabled" }, - }, - }); - expect(prisma.todoItem.findMany).not.toHaveBeenCalled(); - - const postResponse = await app.inject({ - method: "POST", - url: "/api/todos", - payload: { title: "合成待办" }, - }); - expect(postResponse.statusCode).toBe(409); - expect(postResponse.json().error.details.reasonCode).toBe("todo.feature_disabled"); - expect(prisma.todoItem.create).not.toHaveBeenCalled(); - } finally { - await app.close(); - } - }); - - it("lists todos normally without leaking a reasonCode when the board is enabled", async () => { - const { app, prisma } = await buildApp({ featureEnabled: true }); - try { - const response = await app.inject({ method: "GET", url: "/api/todos" }); - expect(response.statusCode).toBe(200); - expect(response.json().data).toEqual({ items: [] }); - expect(prisma.todoItem.findMany).toHaveBeenCalledTimes(1); - } finally { - await app.close(); - } - }); -});