import { PhoneRoutingLookupService } from './phone-routing-lookup.service'; function createPrismaMock() { return { phoneCarrierRule: { findMany: jest.fn().mockResolvedValue([ { carrier: 'telecom', pattern: '^133' }, { carrier: 'mobile', pattern: '^13[5-9]' }, ]), }, phoneSegment: { findMany: jest.fn().mockResolvedValue([ { prefix: '1380000', province: '山东' }, ]), }, }; } describe('PhoneRoutingLookupService', () => { it('compiles and caches active carrier rules across concurrent lookups', async () => { const prisma = createPrismaMock(); const service = new PhoneRoutingLookupService(prisma as never); await expect(Promise.all([ service.identifyCarrier('13800000001'), service.identifyCarrier('13300000001'), service.identifyCarrier('13800000002'), ])).resolves.toEqual(['mobile', 'telecom', 'mobile']); expect(prisma.phoneCarrierRule.findMany).toHaveBeenCalledTimes(1); expect(prisma.phoneCarrierRule.findMany).toHaveBeenCalledWith({ where: { status: 'active' }, orderBy: [{ priority: 'asc' }, { createdAt: 'asc' }], select: { carrier: true, pattern: true }, }); }); it('reloads carrier rules immediately after explicit invalidation', async () => { const prisma = createPrismaMock(); const service = new PhoneRoutingLookupService(prisma as never); await service.identifyCarrier('13800000001'); service.invalidateCarrierRules(); await service.identifyCarrier('13800000001'); expect(prisma.phoneCarrierRule.findMany).toHaveBeenCalledTimes(2); }); it('does not let an in-flight stale load overwrite an invalidated cache', async () => { const prisma = createPrismaMock(); let resolveFirstLoad: ((rows: Array<{ carrier: string; pattern: string }>) => void) | undefined; prisma.phoneCarrierRule.findMany .mockImplementationOnce(() => new Promise((resolve) => { resolveFirstLoad = resolve; })) .mockResolvedValueOnce([{ carrier: 'telecom', pattern: '^138' }]); const service = new PhoneRoutingLookupService(prisma as never); const staleLookup = service.identifyCarrier('13800000001'); service.invalidateCarrierRules(); await expect(service.identifyCarrier('13800000001')).resolves.toBe('telecom'); resolveFirstLoad?.([{ carrier: 'mobile', pattern: '^138' }]); await expect(staleLookup).resolves.toBe('mobile'); await expect(service.identifyCarrier('13800000001')).resolves.toBe('telecom'); expect(prisma.phoneCarrierRule.findMany).toHaveBeenCalledTimes(2); }); it('finds the longest phone prefix with one database query', async () => { const prisma = createPrismaMock(); prisma.phoneSegment.findMany.mockResolvedValue([ { prefix: '138', province: '全国' }, { prefix: '1380000', province: '山东' }, { prefix: '13800', province: '华东' }, ]); const service = new PhoneRoutingLookupService(prisma as never); await expect(service.identifyProvince('13800000001')).resolves.toBe('山东'); expect(prisma.phoneSegment.findMany).toHaveBeenCalledTimes(1); expect(prisma.phoneSegment.findMany).toHaveBeenCalledWith({ where: { prefix: { in: ['1380000', '138000', '13800', '1380', '138'] } }, select: { prefix: true, province: true }, }); }); it('returns null after one query when no phone prefix exists', async () => { const prisma = createPrismaMock(); prisma.phoneSegment.findMany.mockResolvedValue([]); const service = new PhoneRoutingLookupService(prisma as never); await expect(service.identifyProvince('00000000000')).resolves.toBeNull(); expect(prisma.phoneSegment.findMany).toHaveBeenCalledTimes(1); }); });