electro-sb's picture
Containerization and frontend backend communication issue fixed
d086f83
import { describe, it, expect, vi, beforeEach } from 'vitest';
import axios from 'axios';
import {
makeMove,
isPromotionRequired,
getPromotionRequirement,
createPromotionMove,
type GameResponse,
type PromotionRequirement,
type GameState
} from './api';
// Mock axios
vi.mock('axios');
const mockedAxios = vi.mocked(axios);
describe('API Service - Promotion Support', () => {
beforeEach(() => {
vi.clearAllMocks();
});
const mockGameState: GameState = {
fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
turn: 'white',
game_state: 'active',
legal_moves: ['e2e4', 'e2e3'],
in_check: false,
move_count: 1
};
describe('makeMove function', () => {
it('should make a normal move successfully', async () => {
const mockResponse: GameResponse = {
status: 'success',
board_state: mockGameState,
player_move: 'e2e4'
};
mockedAxios.post.mockResolvedValueOnce({ data: mockResponse });
const result = await makeMove('e2e4');
expect(mockedAxios.post).toHaveBeenCalledWith('/api/game/move', { move: 'e2e4' });
expect(result).toEqual(mockResponse);
});
it('should make a promotion move with piece specified', async () => {
const mockResponse: GameResponse = {
status: 'success',
board_state: mockGameState,
player_move: 'e7e8q'
};
mockedAxios.post.mockResolvedValueOnce({ data: mockResponse });
const result = await makeMove('e7e8', 'queen');
expect(mockedAxios.post).toHaveBeenCalledWith('/api/game/move', { move: 'e7e8q' });
expect(result).toEqual(mockResponse);
});
it('should handle promotion required error response', async () => {
const errorResponse = {
status: 'promotion_required',
message: 'Pawn promotion requires piece selection',
board_state: mockGameState,
promotion_details: {
from: 'e7',
to: 'e8',
available_pieces: ['queen', 'rook', 'bishop', 'knight']
}
};
const axiosError = {
isAxiosError: true,
response: {
status: 400,
data: errorResponse
}
};
mockedAxios.post.mockRejectedValueOnce(axiosError);
mockedAxios.isAxiosError.mockReturnValueOnce(true);
const result = await makeMove('e7e8');
expect(result.status).toBe('promotion_required');
expect(result.promotion_required).toEqual({
from: 'e7',
to: 'e8',
available_pieces: ['queen', 'rook', 'bishop', 'knight']
});
});
it('should handle promotion required error with minimal data', async () => {
const errorResponse = {
status: 'promotion_required',
message: 'Pawn promotion requires piece selection'
};
const axiosError = {
isAxiosError: true,
response: {
status: 400,
data: errorResponse
}
};
mockedAxios.post.mockRejectedValueOnce(axiosError);
mockedAxios.isAxiosError.mockReturnValueOnce(true);
const result = await makeMove('e7e8');
expect(result.status).toBe('promotion_required');
expect(result.promotion_required).toEqual({
from: 'e7',
to: 'e8',
available_pieces: ['queen', 'rook', 'bishop', 'knight']
});
});
it('should re-throw non-promotion errors', async () => {
const axiosError = {
isAxiosError: true,
response: {
status: 500,
data: { message: 'Internal server error' }
}
};
mockedAxios.post.mockRejectedValueOnce(axiosError);
mockedAxios.isAxiosError.mockReturnValueOnce(true);
await expect(makeMove('e2e4')).rejects.toEqual(axiosError);
});
it('should re-throw non-axios errors', async () => {
const networkError = new Error('Network error');
mockedAxios.post.mockRejectedValueOnce(networkError);
mockedAxios.isAxiosError.mockReturnValueOnce(false);
await expect(makeMove('e2e4')).rejects.toEqual(networkError);
});
it('should handle different promotion pieces correctly', async () => {
const testCases = [
{ piece: 'queen', expected: 'e7e8q' },
{ piece: 'rook', expected: 'e7e8r' },
{ piece: 'bishop', expected: 'e7e8b' },
{ piece: 'knight', expected: 'e7e8n' }
] as const;
for (const testCase of testCases) {
const mockResponse: GameResponse = {
status: 'success',
board_state: mockGameState,
player_move: testCase.expected
};
mockedAxios.post.mockResolvedValueOnce({ data: mockResponse });
await makeMove('e7e8', testCase.piece);
expect(mockedAxios.post).toHaveBeenCalledWith('/api/game/move', { move: testCase.expected });
}
});
});
describe('isPromotionRequired function', () => {
it('should return true when promotion is required', () => {
const response: GameResponse = {
status: 'promotion_required',
board_state: mockGameState,
promotion_required: {
from: 'e7',
to: 'e8',
available_pieces: ['queen', 'rook', 'bishop', 'knight']
}
};
expect(isPromotionRequired(response)).toBe(true);
});
it('should return false when promotion is not required', () => {
const response: GameResponse = {
status: 'success',
board_state: mockGameState
};
expect(isPromotionRequired(response)).toBe(false);
});
it('should return false when status is promotion_required but no promotion_required field', () => {
const response: GameResponse = {
status: 'promotion_required',
board_state: mockGameState
};
expect(isPromotionRequired(response)).toBe(false);
});
});
describe('getPromotionRequirement function', () => {
it('should return promotion requirement when present', () => {
const promotionReq: PromotionRequirement = {
from: 'e7',
to: 'e8',
available_pieces: ['queen', 'rook', 'bishop', 'knight']
};
const response: GameResponse = {
status: 'promotion_required',
board_state: mockGameState,
promotion_required: promotionReq
};
expect(getPromotionRequirement(response)).toEqual(promotionReq);
});
it('should return null when no promotion requirement', () => {
const response: GameResponse = {
status: 'success',
board_state: mockGameState
};
expect(getPromotionRequirement(response)).toBeNull();
});
});
describe('createPromotionMove function', () => {
it('should create correct UCI notation for different pieces', () => {
expect(createPromotionMove('e7', 'e8', 'queen')).toBe('e7e8q');
expect(createPromotionMove('e7', 'e8', 'rook')).toBe('e7e8r');
expect(createPromotionMove('e7', 'e8', 'bishop')).toBe('e7e8b');
expect(createPromotionMove('e7', 'e8', 'knight')).toBe('e7e8n');
});
it('should handle different squares correctly', () => {
expect(createPromotionMove('a7', 'a8', 'queen')).toBe('a7a8q');
expect(createPromotionMove('h2', 'h1', 'rook')).toBe('h2h1r');
expect(createPromotionMove('d7', 'd8', 'bishop')).toBe('d7d8b');
});
});
describe('Integration scenarios', () => {
it('should handle complete promotion flow', async () => {
// First call - promotion required
const promotionRequiredError = {
isAxiosError: true,
response: {
status: 400,
data: {
status: 'promotion_required',
message: 'Pawn promotion requires piece selection',
promotion_details: {
from: 'e7',
to: 'e8',
available_pieces: ['queen', 'rook', 'bishop', 'knight']
}
}
}
};
mockedAxios.post.mockRejectedValueOnce(promotionRequiredError);
mockedAxios.isAxiosError.mockReturnValueOnce(true);
const firstResult = await makeMove('e7e8');
expect(isPromotionRequired(firstResult)).toBe(true);
const promotionReq = getPromotionRequirement(firstResult);
expect(promotionReq).toBeTruthy();
expect(promotionReq!.available_pieces).toContain('queen');
// Second call - with promotion piece
const successResponse: GameResponse = {
status: 'success',
board_state: mockGameState,
player_move: 'e7e8q'
};
mockedAxios.post.mockResolvedValueOnce({ data: successResponse });
const secondResult = await makeMove('e7e8', 'queen');
expect(secondResult.status).toBe('success');
expect(secondResult.player_move).toBe('e7e8q');
});
});
});