Files
2026-04-28 14:07:01 -04:00

83 lines
2.8 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { Chess } from 'chess.js';
import { buildView, ownSquares } from '../../src/view.js';
import type { Game } from '../../src/state.js';
import { RATE_LIMIT } from '../../src/state.js';
function makeGame(mode: 'blind' | 'vanilla', fen?: string, status: 'active' | 'finished' = 'active'): Game {
return {
id: 'testtest',
mode,
highlightingEnabled: false,
status,
createdAt: Date.now(),
chess: fen ? new Chess(fen) : new Chess(),
moveHistory: [],
announcements: [],
players: {
w: { token: 'w'.repeat(24), socket: null, joinedAt: 0, rateBucket: { tokens: RATE_LIMIT.capacity, last: 0 } },
b: { token: 'b'.repeat(24), socket: null, joinedAt: 0, rateBucket: { tokens: RATE_LIMIT.capacity, last: 0 } },
},
armed: null,
drawOffer: null,
disconnectAt: {},
lastBroadcastIdx: { w: 0, b: 0 },
};
}
describe('buildView: security boundary', () => {
it('blind/active white view contains zero black pieces', () => {
const g = makeGame('blind');
const view = buildView(g, 'w');
for (const piece of Object.values(view.pieces)) {
expect(piece?.color).toBe('w');
}
expect(Object.keys(view.pieces).length).toBe(16); // all 16 white pieces
});
it('blind/active black view contains zero white pieces', () => {
const g = makeGame('blind');
const view = buildView(g, 'b');
for (const piece of Object.values(view.pieces)) {
expect(piece?.color).toBe('b');
}
expect(Object.keys(view.pieces).length).toBe(16);
});
it('vanilla/active shows both colors', () => {
const g = makeGame('vanilla');
const view = buildView(g, 'w');
expect(Object.keys(view.pieces).length).toBe(32);
});
it('blind/finished reveals both colors (post-game review)', () => {
const g = makeGame('blind', undefined, 'finished');
const view = buildView(g, 'w');
expect(Object.keys(view.pieces).length).toBe(32);
});
it('blind: inCheck is null for non-actor (info leak prevention)', () => {
// Black to move and is in check. White's view says null (it's not white's turn,
// and revealing inCheck-status of opponent leaks info).
const g = makeGame('blind', 'rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3');
const view = buildView(g, 'b');
// It's white's turn here. Black viewer is not the to-move side.
expect(view.inCheck).toBeNull();
});
});
describe('ownSquares', () => {
it('starting position returns 16 own squares', () => {
const g = makeGame('blind');
expect(ownSquares(g, 'w').size).toBe(16);
expect(ownSquares(g, 'b').size).toBe(16);
});
it('contains only own-color squares', () => {
const g = makeGame('blind');
const wSet = ownSquares(g, 'w');
expect(wSet.has('e2')).toBe(true);
expect(wSet.has('e7')).toBe(false);
});
});