feat: implement safe state presentation and bounded serialization

This commit is contained in:
2026-08-28 23:04:04 +03:00
parent a691783f58
commit 97bebfd8fb
6 changed files with 205 additions and 5 deletions
+6 -2
View File
@@ -1,7 +1,7 @@
CC ?= cc
CFLAGS ?= -std=c11 -Wall -Wextra -Werror -I../../include
all: test_command_queue test_game_domain test_bot_player test_game_lifecycle
all: test_command_queue test_game_domain test_bot_player test_game_lifecycle test_state_presenter
test_command_queue: test_command_queue.c ../../src/command_queue.c ../../src/application.c
$(CC) $(CFLAGS) $^ -o $@
@@ -11,6 +11,7 @@ run: all
./test_game_domain
./test_bot_player
./test_game_lifecycle
./test_state_presenter
test_game_domain: test_game_domain.c ../../src/fleet_generator.c ../../src/game_engine.c
$(CC) $(CFLAGS) $^ -o $@
@@ -21,5 +22,8 @@ test_bot_player: test_bot_player.c ../../src/bot_player.c ../../src/fleet_genera
test_game_lifecycle: test_game_lifecycle.c ../../src/session_manager.c ../../src/game_lifecycle.c ../../src/fleet_generator.c ../../src/game_engine.c
$(CC) $(CFLAGS) $^ -o $@
test_state_presenter: test_state_presenter.c ../../src/state_presenter.c
$(CC) $(CFLAGS) $^ -o $@
clean:
rm -f test_command_queue test_game_domain test_bot_player test_game_lifecycle
rm -f test_command_queue test_game_domain test_bot_player test_game_lifecycle test_state_presenter
+92
View File
@@ -0,0 +1,92 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "state_presenter.h"
static void set_board(board_t *board) {
memset(board, 0, sizeof(*board));
board->cells[0] = CELL_SHIP;
board->cells[1] = CELL_MISS;
board->cells[2] = CELL_HIT;
board->cells[99] = CELL_SHIP;
}
static const char *board_start(const char *json, uint8_t board) {
const char *first = strstr(json, "\"boards\":[\"");
assert(first != NULL);
first += strlen("\"boards\":[\"");
return board == 0U ? first : first + kBoardCellCount + 3U;
}
static void assert_hidden(const char *json, uint8_t board) {
const char *cells = board_start(json, board);
for (uint8_t index = 0; index < kBoardCellCount; ++index) assert(cells[index] != '1');
}
static void test_role_views_and_golden_schema(void) {
game_state_t state = {0};
state.game_id = 42U;
state.version = 9U;
state.phase = PHASE_IN_PROGRESS;
state.mode = MODE_HUMAN;
state.current_player = 1U;
set_board(&state.boards[0]);
set_board(&state.boards[1]);
char output[kStateMessageCapacity] = {0};
size_t written = 0;
assert(state_presenter_write(&state, ROLE_PLAYER_1, output, sizeof(output), &written));
assert(written == strlen(output));
assert(strstr(output, "{\"type\":\"state\",\"version\":9,\"gameId\":42,\"phase\":\"in_progress\",\"mode\":\"human\",\"viewer\":\"player1\",\"turn\":\"player2\",\"boards\":[\"") == output);
assert(board_start(output, 0)[0] == '1' && board_start(output, 0)[1] == '2' && board_start(output, 0)[2] == '3');
assert_hidden(output, 1);
assert(state_presenter_write(&state, ROLE_PLAYER_2, output, sizeof(output), &written));
assert_hidden(output, 0);
assert(board_start(output, 1)[0] == '1');
assert(state_presenter_write(&state, ROLE_SPECTATOR, output, sizeof(output), &written));
assert_hidden(output, 0);
assert_hidden(output, 1);
assert(board_start(output, 0)[1] == '2' && board_start(output, 1)[2] == '3');
}
static void test_finished_and_failure_are_safe(void) {
game_state_t state = {0};
state.game_id = UINT32_MAX;
state.version = UINT32_MAX;
state.phase = PHASE_FINISHED;
state.mode = MODE_BOT;
state.current_player = 0U;
set_board(&state.boards[0]);
set_board(&state.boards[1]);
char output[kStateMessageCapacity] = {0};
size_t written = 0;
assert(state_presenter_write(&state, ROLE_SPECTATOR, output, sizeof(output), &written));
assert(written < sizeof(output));
assert(board_start(output, 0)[0] == '1' && board_start(output, 1)[99] == '1');
game_lifecycle_t lifecycle = {0};
lifecycle.game.state = state;
lifecycle.game.state.phase = PHASE_REMATCH_WAIT;
lifecycle.game.state.mode = MODE_HUMAN;
lifecycle.cumulative[0].wins = UINT16_MAX;
lifecycle.cumulative[1].wins = UINT16_MAX;
assert(state_presenter_write_lifecycle(&lifecycle, ROLE_SPECTATOR, output, sizeof(output), &written));
assert(written == 371U);
char unchanged[16];
memset(unchanged, 'X', sizeof(unchanged));
size_t failure_written = 99U;
assert(!state_presenter_write(&state, ROLE_PLAYER_1, unchanged, sizeof(unchanged), &failure_written));
assert(failure_written == 0U);
for (size_t index = 0; index < sizeof(unchanged); ++index) assert(unchanged[index] == 'X');
}
int main(void) {
test_role_views_and_golden_schema();
test_finished_and_failure_are_safe();
puts("state presenter tests passed");
return 0;
}