perf(P22PROD1-3,P22PROD1-4): 资产exact-first搜索优化与三条benchmark脚本
这个提交包含在:
@@ -109,7 +109,7 @@ async function buildApp({ employee, prisma }) {
|
||||
}
|
||||
|
||||
describe('assets search route', () => {
|
||||
it('adds q matcher for assetNo serialNo and name fallback', async () => {
|
||||
it('prefers exact assetNo matching before serial or fuzzy lookup', async () => {
|
||||
const prisma = buildPrisma({
|
||||
items: [buildAssetRecord()],
|
||||
});
|
||||
@@ -131,14 +131,104 @@ describe('assets search route', () => {
|
||||
serialNo: 'SN-001',
|
||||
}),
|
||||
);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenCalledTimes(1);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: expect.objectContaining({
|
||||
OR: [
|
||||
{ asset_no: 'ZC-001' },
|
||||
{ serial_no: 'ZC-001' },
|
||||
{ name: { contains: 'ZC-001', mode: 'insensitive' } },
|
||||
],
|
||||
asset_no: 'ZC-001',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('falls back to serialNo exact lookup when assetNo misses', async () => {
|
||||
const prisma = {
|
||||
assetCard: {
|
||||
findMany: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValueOnce([buildAssetRecord({ id: 'asset-2', assetNo: 'ZC-003', serialNo: 'SN-003' })]),
|
||||
},
|
||||
};
|
||||
const app = await buildApp({
|
||||
employee: { id: 'manager-1', permissionCodes: ['asset.view_all'] },
|
||||
prisma,
|
||||
});
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/assets?q=SN-003',
|
||||
headers: { authorization: 'Bearer token' },
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenCalledTimes(2);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
where: expect.objectContaining({
|
||||
asset_no: 'SN-003',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
where: expect.objectContaining({
|
||||
serial_no: 'SN-003',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('falls back to fuzzy name search only after both exact lookups miss', async () => {
|
||||
const prisma = {
|
||||
assetCard: {
|
||||
findMany: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValueOnce([buildAssetRecord({ id: 'asset-3', assetNo: 'ZC-004', serialNo: 'SN-004', name: '研发测试笔记本' })]),
|
||||
},
|
||||
};
|
||||
const app = await buildApp({
|
||||
employee: { id: 'manager-1', permissionCodes: ['asset.view_all'] },
|
||||
prisma,
|
||||
});
|
||||
|
||||
const response = await app.inject({
|
||||
method: 'GET',
|
||||
url: '/api/assets?q=测试笔记本',
|
||||
headers: { authorization: 'Bearer token' },
|
||||
});
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenCalledTimes(3);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
where: expect.objectContaining({
|
||||
asset_no: '测试笔记本',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
where: expect.objectContaining({
|
||||
serial_no: '测试笔记本',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
expect.objectContaining({
|
||||
where: expect.objectContaining({
|
||||
name: { contains: '测试笔记本', mode: 'insensitive' },
|
||||
}),
|
||||
}),
|
||||
);
|
||||
@@ -147,9 +237,14 @@ describe('assets search route', () => {
|
||||
});
|
||||
|
||||
it('forces own-scope lookup even when caller passes mine=0', async () => {
|
||||
const prisma = buildPrisma({
|
||||
items: [buildAssetRecord({ assigneeEmployeeId: 'employee-1', assetNo: 'ZC-002', serialNo: 'SN-002' })],
|
||||
});
|
||||
const prisma = {
|
||||
assetCard: {
|
||||
findMany: vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce([])
|
||||
.mockResolvedValueOnce([buildAssetRecord({ assigneeEmployeeId: 'employee-1', assetNo: 'ZC-002', serialNo: 'SN-002' })]),
|
||||
},
|
||||
};
|
||||
const app = await buildApp({
|
||||
employee: { id: 'employee-1', permissionCodes: ['asset.view_own'] },
|
||||
prisma,
|
||||
@@ -163,15 +258,22 @@ describe('assets search route', () => {
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.json().data.mine).toBe(true);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenCalledWith(
|
||||
expect(prisma.assetCard.findMany).toHaveBeenCalledTimes(2);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({
|
||||
where: expect.objectContaining({
|
||||
assignee_employee_id: 'employee-1',
|
||||
OR: [
|
||||
{ asset_no: 'SN-002' },
|
||||
{ serial_no: 'SN-002' },
|
||||
{ name: { contains: 'SN-002', mode: 'insensitive' } },
|
||||
],
|
||||
asset_no: 'SN-002',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(prisma.assetCard.findMany).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({
|
||||
where: expect.objectContaining({
|
||||
assignee_employee_id: 'employee-1',
|
||||
serial_no: 'SN-002',
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
在新工单中引用
屏蔽一个用户