Files
CONSOLGAMES/tests/sim/transactions.test.ts
T

218 lines
8.7 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { buyBack, changeLoyaltyProgram, recordLostRequest, recordSatisfiedRequest, sell, trade } from '../../src/sim/transactions';
import { createNewGame } from '../../src/sim/state';
import { backroomQty, getSlot } from '../../src/sim/inventory';
import { issueCard, setProgram } from '../../src/sim/loyalty';
import { makeCustomer, stock, testCatalog } from './helpers';
const catalog = testCatalog();
const tetrisNew = { gameId: 'tetris', condition: 'new' as const };
const tetrisLoose = { gameId: 'tetris', condition: 'loose' as const };
const smlComplete = { gameId: 'sml', condition: 'complete' as const };
describe('sell', () => {
it('takes the item, cashes the price and records the sale', () => {
const s = createNewGame(1);
stock(s, 'slot-0', tetrisNew, 2);
const cash = s.cash;
const result = sell(s, catalog, 'slot-0', makeCustomer());
expect(result.priceCents).toBe(3034);
expect(s.cash).toBe(cash + 3034);
expect(getSlot(s.inventory, 'slot-0').qty).toBe(1);
expect(s.day.salesCount).toBe(1);
expect(s.day.revenueCents).toBe(3034);
expect(s.reputation).toBeCloseTo(20.1);
});
it('stamps the card of a card holder', () => {
const s = createNewGame(1);
const card = issueCard(s.loyalty);
stock(s, 'slot-0', tetrisNew, 1);
sell(s, catalog, 'slot-0', makeCustomer({ cardId: card.id }));
expect(card.stamps).toEqual([1]);
});
it('applies a free used game reward', () => {
const s = createNewGame(1);
const card = issueCard(s.loyalty);
card.pendingRewards.push({ kind: 'free_used_game', maxValueCents: 2000 });
stock(s, 'slot-0', tetrisLoose, 1);
const cash = s.cash;
const result = sell(s, catalog, 'slot-0', makeCustomer({ cardId: card.id }));
expect(result.priceCents).toBe(0);
expect(result.rewardUsed).toEqual({ kind: 'free_used_game', maxValueCents: 2000 });
expect(s.cash).toBe(cash);
expect(card.pendingRewards).toEqual([]);
});
it('applies a percentage discount', () => {
const s = createNewGame(1);
const card = issueCard(s.loyalty);
card.pendingRewards.push({ kind: 'discount_percent', percent: 50 });
stock(s, 'slot-0', tetrisNew, 1);
expect(sell(s, catalog, 'slot-0', makeCustomer({ cardId: card.id })).priceCents).toBe(1517);
});
it('spends the card voucher', () => {
const s = createNewGame(1);
const card = issueCard(s.loyalty);
card.voucherCents = 1000;
stock(s, 'slot-0', tetrisNew, 1);
const cash = s.cash;
expect(sell(s, catalog, 'slot-0', makeCustomer({ cardId: card.id })).priceCents).toBe(2034);
expect(s.cash).toBe(cash + 2034);
expect(card.voucherCents).toBe(0);
});
});
describe('buyBack', () => {
it('is refused below what the customer accepts', () => {
const s = createNewGame(1);
const cash = s.cash;
expect(buyBack(s, catalog, makeCustomer({ intent: 'buy_back', offered: smlComplete }), 796)).toEqual({ accepted: false });
expect(s.cash).toBe(cash);
});
it('buys the game into the backroom when accepted', () => {
const s = createNewGame(1);
const cash = s.cash;
expect(buyBack(s, catalog, makeCustomer({ intent: 'buy_back', offered: smlComplete }), 797)).toEqual({ accepted: true });
expect(s.cash).toBe(cash - 797);
expect(backroomQty(s.inventory, smlComplete)).toBe(1);
expect(s.day.buyBacks).toBe(1);
expect(s.day.expensesCents).toBe(797);
});
it('throws when the shop cannot pay', () => {
const s = createNewGame(1);
s.cash = 100;
expect(() => buyBack(s, catalog, makeCustomer({ intent: 'buy_back', offered: smlComplete }), 797)).toThrow('Not enough cash');
});
it('stamps buy_back cards', () => {
const s = createNewGame(1);
setProgram(s.loyalty, [{ stampType: 'buy_back', slots: 5, reward: { kind: 'free_trade' } }]);
const card = issueCard(s.loyalty);
buyBack(s, catalog, makeCustomer({ intent: 'buy_back', offered: smlComplete, cardId: card.id }), 797);
expect(card.stamps).toEqual([1]);
});
});
describe('trade', () => {
it('is refused below the trade value the customer expects', () => {
const s = createNewGame(1);
stock(s, 'slot-0', tetrisNew, 1);
const result = trade(s, catalog, makeCustomer({ intent: 'trade', offered: smlComplete }), 'slot-0', 1138);
expect(result.accepted).toBe(false);
expect(getSlot(s.inventory, 'slot-0').qty).toBe(1);
});
it('makes the customer pay the difference', () => {
const s = createNewGame(1);
stock(s, 'slot-0', tetrisNew, 1);
const cash = s.cash;
const result = trade(s, catalog, makeCustomer({ intent: 'trade', offered: smlComplete }), 'slot-0', 1139);
expect(result).toEqual({ accepted: true, customerPaysCents: 1895, shopPaysCents: 0, voucherGivenCents: 0 });
expect(s.cash).toBe(cash + 1895);
expect(backroomQty(s.inventory, smlComplete)).toBe(1);
expect(getSlot(s.inventory, 'slot-0').qty).toBe(0);
expect(s.day.trades).toBe(1);
});
it('pays the excess in cash to customers without a card', () => {
const s = createNewGame(1);
stock(s, 'slot-0', tetrisNew, 1);
const cash = s.cash;
const result = trade(s, catalog, makeCustomer({ intent: 'trade', offered: smlComplete }), 'slot-0', 4000);
expect(result).toEqual({ accepted: true, customerPaysCents: 0, shopPaysCents: 966, voucherGivenCents: 0 });
expect(s.cash).toBe(cash - 966);
});
it('gives the excess as a voucher to card holders', () => {
const s = createNewGame(1);
const card = issueCard(s.loyalty);
stock(s, 'slot-0', tetrisNew, 1);
const cash = s.cash;
const result = trade(s, catalog, makeCustomer({ intent: 'trade', offered: smlComplete, cardId: card.id }), 'slot-0', 4000);
expect(result.voucherGivenCents).toBe(966);
expect(card.voucherCents).toBe(966);
expect(s.cash).toBe(cash);
});
it('waives the difference with a free trade reward', () => {
const s = createNewGame(1);
const card = issueCard(s.loyalty);
card.pendingRewards.push({ kind: 'free_trade' });
stock(s, 'slot-0', tetrisNew, 1);
const result = trade(s, catalog, makeCustomer({ intent: 'trade', offered: smlComplete, cardId: card.id }), 'slot-0', 1139);
expect(result.customerPaysCents).toBe(0);
expect(card.pendingRewards).toEqual([]);
});
});
describe('requests', () => {
it('costs reputation when the game is missing, clamped at 0', () => {
const s = createNewGame(1);
recordLostRequest(s);
expect(s.reputation).toBe(19);
expect(s.day.lostRequests).toBe(1);
s.reputation = 0.5;
recordLostRequest(s);
expect(s.reputation).toBe(0);
});
it('earns reputation when the game is found', () => {
const s = createNewGame(1);
recordSatisfiedRequest(s);
expect(s.reputation).toBe(20.5);
});
});
describe('reward and cash edge cases', () => {
it('keeps the free trade reward when there is no difference to waive', () => {
const s = createNewGame(1);
const card = issueCard(s.loyalty);
card.pendingRewards.push({ kind: 'free_trade' });
stock(s, 'slot-0', tetrisNew, 1);
trade(s, catalog, makeCustomer({ intent: 'trade', offered: smlComplete, cardId: card.id }), 'slot-0', 3034);
expect(card.pendingRewards).toEqual([{ kind: 'free_trade' }]);
});
it('prefers a free used game over a discount', () => {
const s = createNewGame(1);
const card = issueCard(s.loyalty);
card.pendingRewards.push({ kind: 'discount_percent', percent: 10 }, { kind: 'free_used_game', maxValueCents: 2000 });
stock(s, 'slot-0', tetrisLoose, 1);
const result = sell(s, catalog, 'slot-0', makeCustomer({ cardId: card.id }));
expect(result.priceCents).toBe(0);
expect(card.pendingRewards).toEqual([{ kind: 'discount_percent', percent: 10 }]);
});
it('throws before mutating when the shop cannot pay the trade excess', () => {
const s = createNewGame(1);
stock(s, 'slot-0', tetrisNew, 1);
s.cash = 100;
expect(() => trade(s, catalog, makeCustomer({ intent: 'trade', offered: smlComplete }), 'slot-0', 4000)).toThrow('Not enough cash');
expect(getSlot(s.inventory, 'slot-0').qty).toBe(1);
expect(s.cash).toBe(100);
expect(s.day.trades).toBe(0);
});
});
describe('changeLoyaltyProgram', () => {
it('lowers reputation by 2 when the program becomes less generous', () => {
const s = createNewGame(1);
s.reputation = 50;
changeLoyaltyProgram(s, [{ stampType: 'buy_new', slots: 20, reward: { kind: 'voucher', valueCents: 500 } }]);
expect(s.reputation).toBe(48);
expect(s.loyalty.programs).toHaveLength(2);
});
it('leaves reputation unchanged when the program is more generous', () => {
const s = createNewGame(1);
s.reputation = 50;
changeLoyaltyProgram(s, [{ stampType: 'buy_new', slots: 5, reward: { kind: 'voucher', valueCents: 1000 } }]);
expect(s.reputation).toBe(50);
});
});