import { MarketsService } from './markets.service'; function selection(selectionCode: string, odds = 1.9) { return { selectionCode, selectionName: selectionCode, odds, status: 'OPEN', }; } function errorCode(error: unknown) { const response = (error as { getResponse?: () => unknown }).getResponse?.(); return (response as { code?: string } | undefined)?.code; } function createPrismaMock() { let marketId = 100n; let selectionId = 1000n; return { match: { findUnique: jest.fn().mockResolvedValue({ id: 1n }), }, market: { findFirst: jest.fn().mockResolvedValue(null), create: jest.fn().mockImplementation(async ({ data }) => ({ id: marketId++, ...data })), update: jest.fn().mockImplementation(async ({ where, data }) => ({ id: where.id, ...data })), findUnique: jest.fn().mockImplementation(async ({ where }) => ({ id: where.id, selections: [] })), findMany: jest.fn().mockResolvedValue([]), updateMany: jest.fn().mockResolvedValue({ count: 0 }), }, marketSelection: { findMany: jest.fn().mockResolvedValue([]), create: jest.fn().mockImplementation(async ({ data }) => ({ id: selectionId++, ...data })), update: jest.fn(), updateMany: jest.fn().mockResolvedValue({ count: 0 }), }, oddsChangeLog: { create: jest.fn(), }, }; } describe('MarketsService line value rules', () => { it('exposes usesLineValue in market definitions', () => { const service = new MarketsService(createPrismaMock() as never); const definitions = service.listMarketDefinitions(); expect(definitions.find((d) => d.marketType === 'FT_HANDICAP')?.usesLineValue).toBe(true); expect(definitions.find((d) => d.marketType === 'FT_1X2')?.usesLineValue).toBe(false); }); it('rejects line markets without a numeric line value', async () => { const service = new MarketsService(createPrismaMock() as never); let caughtCode: string | undefined; try { await service.saveMatchMarkets(1n, [ { marketType: 'FT_HANDICAP', lineValue: null, selections: [selection('HOME'), selection('AWAY')], }, ]); } catch (error) { caughtCode = errorCode(error); } expect(caughtCode).toBe('MARKET_LINE_REQUIRED'); }); it('rejects non-line markets with a line value', async () => { const service = new MarketsService(createPrismaMock() as never); let caughtCode: string | undefined; try { await service.saveMatchMarkets(1n, [ { marketType: 'FT_1X2', lineValue: 1, selections: [selection('HOME'), selection('DRAW'), selection('AWAY')], }, ]); } catch (error) { caughtCode = errorCode(error); } expect(caughtCode).toBe('MARKET_LINE_NOT_ALLOWED'); }); it('allows non-line markets with null line value', async () => { const prisma = createPrismaMock(); const service = new MarketsService(prisma as never); const result = await service.saveMatchMarkets(1n, [ { marketType: 'FT_1X2', lineValue: null, selections: [selection('HOME'), selection('DRAW'), selection('AWAY')], }, ]); expect(result).toEqual({ updated: 1, closed: 0 }); expect(prisma.market.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ marketType: 'FT_1X2', lineKey: 'FT_1X2:none', lineValue: null, }), }), ); }); it('keeps distinct line keys for copied line markets', async () => { const prisma = createPrismaMock(); const service = new MarketsService(prisma as never); const result = await service.saveMatchMarkets(1n, [ { marketType: 'FT_HANDICAP', lineValue: -0.5, selections: [selection('HOME'), selection('AWAY')], }, { marketType: 'FT_HANDICAP', lineValue: -0.25, selections: [selection('HOME'), selection('AWAY')], }, ]); expect(result).toEqual({ updated: 2, closed: 0 }); expect(prisma.market.create.mock.calls.map(([arg]) => arg.data.lineKey)).toEqual([ 'FT_HANDICAP:-0.50', 'FT_HANDICAP:-0.25', ]); }); });