154 行
5.1 KiB
JavaScript
154 行
5.1 KiB
JavaScript
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();
|
|
});
|
|
});
|