Files
thebet365/apps/api/src/domains/odds/markets.service.spec.ts
2026-06-13 17:38:25 +08:00

138 lines
4.2 KiB
TypeScript

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',
]);
});
});