R23-1/R23-2/R23-4:完成项目沟通回填与收口治理后端
这个提交包含在:
@@ -0,0 +1,153 @@
|
||||
const Fastify = require("fastify");
|
||||
|
||||
const { AppError } = require("../../../src/utils/AppError");
|
||||
const searchRoutes = require("../../../src/routes/search");
|
||||
|
||||
const NOW = new Date("2026-03-22T10:00:00.000Z");
|
||||
|
||||
function buildFeatureFlagRecord(flagKey) {
|
||||
return {
|
||||
id: `flag-${flagKey}`,
|
||||
flag_key: flagKey,
|
||||
override_mode: "force_on",
|
||||
rollout_type: "global",
|
||||
department_ids: [],
|
||||
note: "unit-test",
|
||||
version: 1,
|
||||
created_at: NOW,
|
||||
created_by: null,
|
||||
updated_at: NOW,
|
||||
updated_by: null,
|
||||
};
|
||||
}
|
||||
|
||||
function createPrisma() {
|
||||
const contractRow = {
|
||||
id: "11111111-1111-4111-8111-111111111111",
|
||||
contract_no: "HT-001",
|
||||
counterparty_name: "涉密客户",
|
||||
confidentiality_level: "restricted",
|
||||
summary: "极密摘要内容",
|
||||
updated_at: NOW,
|
||||
project: { project_no: "PRJ-001" },
|
||||
};
|
||||
const docRow = {
|
||||
id: "22222222-2222-4222-8222-222222222222",
|
||||
title: "极密技术方案",
|
||||
document_no: "DOC-001",
|
||||
offline_archive_no: "ARC-001",
|
||||
confidentiality_level: "confidential",
|
||||
updated_at: NOW,
|
||||
project: { project_no: "PRJ-001" },
|
||||
};
|
||||
|
||||
return {
|
||||
featureFlag: {
|
||||
findUnique: vi.fn().mockImplementation(async ({ where }) => buildFeatureFlagRecord(where.flag_key)),
|
||||
},
|
||||
employee: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
portalAnnouncement: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
assetCard: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
reimbursementForm: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
leaveRequest: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
weeklyReport: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
todoItem: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
purchaseRequest: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
travelRequest: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
project: { findMany: vi.fn().mockResolvedValue([]) },
|
||||
contractRegistry: {
|
||||
findMany: vi.fn().mockImplementation(async ({ where }) => {
|
||||
const query = where?.OR?.[0]?.contract_no?.contains ?? "";
|
||||
return ["HT-001", "PRJ-001"].includes(query) ? [contractRow] : [];
|
||||
}),
|
||||
},
|
||||
confidentialDocumentRef: {
|
||||
findMany: vi.fn().mockImplementation(async ({ where }) => {
|
||||
const query = where?.OR?.[0]?.document_no?.contains ?? "";
|
||||
return ["DOC-001", "PRJ-001", "ARC-001"].includes(query) ? [docRow] : [];
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function buildApp({ employee, prisma }) {
|
||||
const app = Fastify();
|
||||
app.decorate("prisma", prisma);
|
||||
app.decorate("config", {
|
||||
globalSearchEnabled: true,
|
||||
announcementsEnabled: false,
|
||||
weeklyReportEnabled: false,
|
||||
todoBoardEnabled: false,
|
||||
projectDomainEnabled: false,
|
||||
projectLinkingEnabled: true,
|
||||
confidentialDocRefsEnabled: true,
|
||||
});
|
||||
app.decorate("authenticate", async (request) => {
|
||||
if (!request.headers.authorization) {
|
||||
throw new AppError({ code: "AUTH_001", message: "未登录", statusCode: 401 });
|
||||
}
|
||||
request.employee = employee;
|
||||
});
|
||||
app.decorate("hasPermission", (actor, permissionCode) => {
|
||||
return (actor?.permissionCodes ?? []).includes("*") || (actor?.permissionCodes ?? []).includes(permissionCode);
|
||||
});
|
||||
app.setErrorHandler((error, request, reply) => {
|
||||
if (error instanceof AppError) {
|
||||
return reply.status(error.statusCode).send({
|
||||
requestId: request.id,
|
||||
error: { code: error.code, message: error.message, details: error.details ?? {} },
|
||||
});
|
||||
}
|
||||
return reply.status(500).send({
|
||||
requestId: request.id,
|
||||
error: { code: "SYS_001", message: "服务器内部错误", details: {} },
|
||||
});
|
||||
});
|
||||
|
||||
await app.register(searchRoutes, { prefix: "/api" });
|
||||
return app;
|
||||
}
|
||||
|
||||
describe("global search R23 confidential boundary", () => {
|
||||
it("does not match sensitive summary text but still matches minimal identifiers", async () => {
|
||||
const prisma = createPrisma();
|
||||
const app = await buildApp({
|
||||
employee: { id: "employee-1", department_id: null, permissionCodes: ["system.settings"] },
|
||||
prisma,
|
||||
});
|
||||
|
||||
const sensitiveResponse = await app.inject({
|
||||
method: "GET",
|
||||
url: "/api/search?q=%E6%9E%81%E5%AF%86%E6%91%98%E8%A6%81",
|
||||
headers: { authorization: "Bearer test-token" },
|
||||
});
|
||||
expect(sensitiveResponse.statusCode).toBe(200);
|
||||
expect(sensitiveResponse.json().data.items).toEqual([]);
|
||||
|
||||
const contractResponse = await app.inject({
|
||||
method: "GET",
|
||||
url: "/api/search?q=HT-001",
|
||||
headers: { authorization: "Bearer test-token" },
|
||||
});
|
||||
expect(contractResponse.statusCode).toBe(200);
|
||||
expect(contractResponse.json().data.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ entityType: "contract_registry", title: "HT-001", subtitle: "涉密合同台账" }),
|
||||
]),
|
||||
);
|
||||
|
||||
const docResponse = await app.inject({
|
||||
method: "GET",
|
||||
url: "/api/search?q=DOC-001",
|
||||
headers: { authorization: "Bearer test-token" },
|
||||
});
|
||||
expect(docResponse.statusCode).toBe(200);
|
||||
expect(docResponse.json().data.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ entityType: "confidential_doc_ref", title: "DOC-001", subtitle: "涉密资料引用" }),
|
||||
]),
|
||||
);
|
||||
|
||||
await app.close();
|
||||
});
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户