Spaces:
Sleeping
Sleeping
| 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'); | |
| }); | |
| }); | |
| }); |