完成五人团队正式内用最小收口(07-18-small-team-internal-readiness)

这个提交包含在:
2026-07-18 15:46:14 +08:00
父节点 58c8decef9
当前提交 5d0a5f9a38
共修改 134 个文件,包含 10790 行新增和 1413 行删除
@@ -1,6 +1,7 @@
const Fastify = require("fastify");
const employeesRoutes = require("../../../src/routes/employees");
const { resolvePermissionsForRoles } = require("../../../src/constants/permissions");
const { AppError } = require("../../../src/utils/AppError");
async function buildApp({ permissionCodes = ["system.users"], stats } = {}) {
@@ -202,9 +203,54 @@ describe("employees stats route", () => {
await app.close();
});
it("returns salary and performance summary for system settings viewers", async () => {
const { app, payslipAggregate } = await buildApp({
permissionCodes: ["system.users", "system.settings"],
it("does not expose sensitive summaries to system administrators with ordinary stats access", async () => {
const {
app,
performanceCycleFindFirst,
performanceReviewAggregate,
performanceReviewGroupBy,
payslipAggregate,
} = await buildApp({
permissionCodes: [
...resolvePermissionsForRoles(["system_admin"]),
"report.dashboard.view",
],
});
const response = await app.inject({
method: "GET",
url: "/api/employees/stats",
});
expect(response.statusCode).toBe(200);
expect(response.json().data.salarySummary).toBeNull();
expect(response.json().data.performanceSummary).toEqual({
cycle: null,
averageScore: null,
reviewedCount: 0,
distribution: [],
});
expect(performanceCycleFindFirst).not.toHaveBeenCalled();
expect(performanceReviewAggregate).not.toHaveBeenCalled();
expect(performanceReviewGroupBy).not.toHaveBeenCalled();
expect(payslipAggregate).not.toHaveBeenCalled();
await app.close();
});
it("returns salary and performance summaries with explicit sensitive permissions", async () => {
const {
app,
performanceCycleFindFirst,
performanceReviewAggregate,
performanceReviewGroupBy,
payslipAggregate,
} = await buildApp({
permissionCodes: [
"report.dashboard.view",
"payroll.manage",
"system.users_sensitive",
],
});
const response = await app.inject({
@@ -231,6 +277,9 @@ describe("employees stats route", () => {
id: "cycle-active",
name: "2026 Q2",
});
expect(performanceCycleFindFirst).toHaveBeenCalledTimes(2);
expect(performanceReviewAggregate).toHaveBeenCalledTimes(1);
expect(performanceReviewGroupBy).toHaveBeenCalledTimes(1);
expect(payslipAggregate).toHaveBeenCalledTimes(2);
await app.close();