Files
Dune/tests/public-deals-deferred-http.test.ts
T

101 lines
6.1 KiB
TypeScript

// @dune-suite integration
import assert from 'node:assert/strict';
import test from 'node:test';
import type { GameView } from '../game/engine';
const base = process.env.DUNE_TEST_URL ?? 'http://localhost:3000';
async function request(path: string, cookie?: string, body?: unknown, selected?: string) {
const response = await fetch(base + path, {
method: body === undefined ? 'GET' : 'POST', signal: AbortSignal.timeout(15_000),
headers: {
...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
...(cookie ? { cookie } : {}),
...(selected ? { 'X-Dune-Seat': selected } : {}),
},
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
return { status: response.status, cookie: response.headers.get('set-cookie')?.split(';')[0],
data: await response.json() as GameView & { error?: string } };
}
/** This HTTP test creates only its own room on the isolated test server. It
* neither reuses nor deletes any existing room or local development game. */
void test('authenticated participants activate and the exact payer settles a deferred public deal once', async () => {
const host = await request('/api/rooms', undefined, {
name: 'Deferred deal recipient', faction: 'atreides', advanced: false, expansions: [],
});
assert.equal(host.status, 201, JSON.stringify(host.data)); assert.ok(host.cookie);
const path = `/api/rooms/${host.data.code}`;
const payer = await request(path, undefined, { type: 'join', name: 'Deferred deal payer', faction: 'harkonnen' });
assert.equal(payer.status, 200, JSON.stringify(payer.data)); assert.ok(payer.cookie);
const third = await request(path, undefined, { type: 'join', name: 'Deferred deal witness', faction: 'emperor' });
assert.equal(third.status, 200, JSON.stringify(third.data)); assert.ok(third.cookie);
const seats = [
{ id: host.data.me, cookie: host.cookie },
{ id: payer.data.me, cookie: payer.cookie },
{ id: third.data.me, cookie: third.cookie },
];
let view = third.data;
const act = async (cookie: string, action: object) => {
const next = await request(path, cookie, { version: view.version, action });
assert.equal(next.status, 200, JSON.stringify(next.data)); view = next.data; return next.data;
};
for (const seat of seats) await act(seat.cookie, { type: 'ready' });
await act(host.cookie, { type: 'start' });
for (const seat of seats) {
const own = await request(path, seat.cookie);
assert.equal(own.status, 200, JSON.stringify(own.data)); view = own.data;
const choices = own.data.players.find(player => player.id === seat.id)?.traitorChoices;
if (choices?.length) await act(seat.cookie, { type: 'traitor', leader: choices[0] });
}
assert.equal(view.status, 'playing'); assert.equal(view.phase, 0);
const payerBefore = await request(path, payer.cookie);
const recipientBefore = await request(path, host.cookie);
assert.equal(payerBefore.status, 200); assert.equal(recipientBefore.status, 200);
const payerSpice = payerBefore.data.players.find(player => player.id === payer.data.me)!.spice;
const recipientSpice = recipientBefore.data.players.find(player => player.id === host.data.me)!.spice;
const recipientIncoming = recipientBefore.data.bribeOptions.incoming;
assert.ok(typeof payerSpice === 'number' && payerSpice >= 3);
const payment = { kind: 'deferredBribe', payer: payer.data.me, recipient: host.data.me,
amount: 3, dueTurn: view.turn };
await act(host.cookie, { type: 'dealOffer', targets: [payer.data.me, third.data.me],
terms: 'Pay the agreed spice this turn.', payment });
const deal = view.deals.at(-1); assert.ok(deal); assert.equal(deal.status, 'offered');
assert.deepEqual(deal.payment, payment);
await act(payer.cookie, { type: 'dealRespond', deal: deal.id, accept: true });
assert.equal(view.deals.find(row => row.id === deal.id)?.status, 'offered');
await act(third.cookie, { type: 'dealRespond', deal: deal.id, accept: true });
assert.equal(view.deals.find(row => row.id === deal.id)?.status, 'active');
assert.equal(view.deals.find(row => row.id === deal.id)?.paymentStatus, 'pending');
for (const seat of seats) {
const read = await request(path, seat.cookie); assert.equal(read.status, 200, JSON.stringify(read.data));
assert.equal(read.data.deals.find(row => row.id === deal.id)?.paymentStatus, 'pending');
}
const pendingPayer = await request(path, payer.cookie);
const pendingRecipient = await request(path, host.cookie);
assert.equal(pendingPayer.data.players.find(player => player.id === payer.data.me)!.spice, payerSpice);
assert.equal(pendingRecipient.data.bribeOptions.incoming, recipientIncoming);
assert.equal(pendingPayer.data.deferredDealPaymentOptions[deal.id]?.blocked, null);
assert.equal(pendingRecipient.data.deferredDealPaymentOptions[deal.id], undefined);
const pay = { type: 'dealPay', deal: deal.id };
const otherSeat = await request(path, host.cookie, { version: view.version, action: pay });
assert.equal(otherSeat.status, 409, JSON.stringify(otherSeat.data));
const forged = await request(path, host.cookie, { version: view.version, action: pay }, payer.data.me);
assert.equal(forged.status, 409, JSON.stringify(forged.data));
const previousVersion = view.version;
await act(payer.cookie, pay);
assert.equal(view.deals.find(row => row.id === deal.id)?.paymentStatus, 'paid');
const paidPayer = await request(path, payer.cookie);
const paidRecipient = await request(path, host.cookie);
assert.equal(paidPayer.data.players.find(player => player.id === payer.data.me)!.spice, payerSpice - 3);
assert.equal(paidRecipient.data.players.find(player => player.id === host.data.me)!.spice, recipientSpice);
assert.equal(paidRecipient.data.bribeOptions.incoming, recipientIncoming + 3);
const stale = await request(path, payer.cookie, { version: previousVersion, action: pay });
assert.equal(stale.status, 409, JSON.stringify(stale.data));
const replay = await request(path, payer.cookie, { version: view.version, action: pay });
assert.equal(replay.status, 409, JSON.stringify(replay.data));
assert.equal((await request(path, host.cookie)).data.bribeOptions.incoming, recipientIncoming + 3);
});