From 47d6c82a4943b1e804d062780b8e05f1e42f27b0 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 28 Oct 2026 22:46:47 +0100 Subject: v0.3.1: clean up dependencies, stale tests and document verification --- Cargo.toml | 9 +---- docs/TESTING.md | 52 +++++++++++++++++++++++++++ docs/adr/0001-testable-client-server-cores.md | 48 +++++++++++++++++++++++++ src/bin/client/connection.rs | 6 ---- src/bin/client/tls.rs | 5 --- tests/slots.rs | 8 ++--- tests/tofu.rs | 4 --- 7 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 docs/TESTING.md create mode 100644 docs/adr/0001-testable-client-server-cores.md diff --git a/Cargo.toml b/Cargo.toml index 6fd0b3e..5e50626 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,24 +1,17 @@ [package] name = "zagan-bet" -version = "0.3.0" +version = "0.3.1" edition = "2024" [dependencies] rusqlite = { version = "0.29", features = ["bundled"] } -windows-sys = { version = "0.61.2", features = ["Win32_System_Console"] } crossterm = "0.27" bcrypt = "0.15" -aes-gcm = "0.10" -pbkdf2 = "0.12" rand = "0.8" -once_cell = "1.18" parking_lot = "0.12" -hmac = "0.12" sha2 = "0.10" -base64 = "0.21" ctrlc = "3.4" serde = { version = "1.0", features = ["derive"] } uuid = { version = "1.0", features = ["v4"] } native-tls = "0.2" rustls = { version = "0.21", features = ["dangerous_configuration"] } -rustls-pemfile = "1.0" \ No newline at end of file diff --git a/docs/TESTING.md b/docs/TESTING.md new file mode 100644 index 0000000..6cac6fa --- /dev/null +++ b/docs/TESTING.md @@ -0,0 +1,52 @@ +# Testing + +## Automated suite + +```sh +cargo test +``` + +The suite is deterministic and headless: no network, no PTY, no sleeps, no +shared global database. Each test owns its state. + +| File | Covers | +| --- | --- | +| `tests/client_core.rs` | Client command/game flows via a headless screen applying logical effects. | +| `tests/client_ui.rs` | Help, history, completion, bet parsing, display width, cursor math. | +| `tests/protocol.rs` | Newline buffering and timeout/error classification. | +| `tests/server_core.rs` | Handler, bcrypt migration, SQL injection, transaction rollback, `plays` audit, sessions. | +| `tests/slots.rs` | Server slots payout rules and overflow handling. | +| `tests/storage.rs` | Client/server SQLite storage helpers. | +| `tests/tofu.rs` | SPKI fingerprint and TOFU decisions. | + +`tests/tofu.rs` shells out to `openssl`, so run it on a machine where the +`openssl` CLI is available. + +## Manual TUI checklist + +There is no CI. Run this on Linux and on a Windows VM before a release. +Use an 80-column terminal because several output lines are width-sensitive. + +1. Start the server with a valid `identity.p12`, then start the client. +2. First contact prints three lines and the 64-character fingerprint does not + wrap at 80 columns. `Connected to ...` shows a one-decimal ping. +3. `register` / `login`: backspace removes one `*` per key, Esc cancels the + prompt, Ctrl+C exits and restores the terminal. +4. `help` fits 80 columns. Tab completion wraps within 76 columns and the + cursor stays visible. +5. `play slots 50` shows `Bet: 50 PLN` and the fetched balance. +6. Space shows `Spinning...`, then the reels, win text, and new balance inside + the same 7-line board. The controls line is never overwritten. +7. `+` / `-` after a spin changes only the bet; no stale result lines appear. +8. `q` clears the board. Ctrl+C in a game clears the board and exits. +9. `info`, `ping`, `send hello`, and `hosts` print readable output; the host + fingerprint fits 80 columns. +10. Windows: repeat 2-9 and confirm keys are not duplicated or printed twice. + +## Manual server checks + +- Register, log out, log in again (bcrypt path). +- Log in with a pre-0.3 user row (legacy SHA-256) and confirm it still works; + the row is upgraded to bcrypt. +- Spin several times and confirm `plays` grows by one row per spin while + `users.balance` changes by `winnings - bet`. diff --git a/docs/adr/0001-testable-client-server-cores.md b/docs/adr/0001-testable-client-server-cores.md new file mode 100644 index 0000000..2e3cd1e --- /dev/null +++ b/docs/adr/0001-testable-client-server-cores.md @@ -0,0 +1,48 @@ +# 0001: Testable client and server cores + +Status: Accepted (2026-10-28) + +## Context + +The client and server started as binaries with their game logic and I/O mixed +together. That made the TUI hard to reason about: a spin appended results below +the board, a later bet change redrew at the wrong line, and none of it could be +tested without a terminal. The server handler had the same problem: it owned +static database and session state, so its security-sensitive paths could not be +exercised from `tests/`. + +## Decision + +- The library (`src/lib.rs`) owns shared logic. `src/bin/client` and + `src/bin/server` are thin adapters for crossterm, TLS, environment variables, + and interactive prompts. +- `client::core::ClientCore` owns input, history, commands, game state, and + pending replies. It returns `Effect`s (`Print`, `ReplaceRegion`, `ClearRegion`, + `SetPrompt`, `SendLine`, `Interactive`, ...). The binary applies them; the + tests apply them to a headless screen. +- `server::core::ServerCore` owns message handling and receives an injected + `Connection`, `GameRegistry`, and bcrypt cost. The binary only owns the TLS + listener and per-connection thread. +- Tests live in `tests/` and exercise the library. Pure UI math is tested + directly; terminal ANSI output is checked manually with `docs/TESTING.md`. + There are no PTY tests because they cannot cover Windows and `RULES.md` + requires identical behaviour on all platforms. +- The client keeps sending its SHA-256 credential. The server stores + `bcrypt(credential)` and upgrades legacy SHA-256 rows on the first successful + login. This protects a stolen server database without changing the wire + protocol, but the client-side credential remains replayable if `client.db` + leaks. +- Each settled spin writes a durable `plays` row in the same transaction as the + balance updates. The in-memory session is a transient guard and is removed + as soon as the spin settles or fails. + +## Consequences + +- Client flows and server security rules are testable without a terminal or a + network. A visual bug becomes an assertion about the headless screen. +- The binaries have an extra translation layer (`Effect` application), and the + server opens a SQLite connection per client. +- Terminal rendering still needs a manual Linux/Windows pass; the checklist is + in `docs/TESTING.md`. +- Password storage is stronger, but not a full PAKE. A future MAJOR protocol + change can replace the replayable credential. diff --git a/src/bin/client/connection.rs b/src/bin/client/connection.rs index 49695dd..6459de5 100644 --- a/src/bin/client/connection.rs +++ b/src/bin/client/connection.rs @@ -131,12 +131,6 @@ impl TlsStream { ) -> std::io::Result<()> { self.inner.set_read_timeout(duration) } - - pub fn try_clone(&self) -> std::io::Result { - Ok(TlsStream { - inner: self.inner.try_clone()?, - }) - } } impl Read for TlsStream { diff --git a/src/bin/client/tls.rs b/src/bin/client/tls.rs index b118f63..8c052f7 100644 --- a/src/bin/client/tls.rs +++ b/src/bin/client/tls.rs @@ -204,11 +204,6 @@ impl TlsStream { ) -> std::io::Result<()> { self.session.lock().sock.set_read_timeout(duration) } - - /// Clones share the same underlying TLS session. - pub fn try_clone(&self) -> std::io::Result { - Ok(self.clone()) - } } impl Read for TlsStream { diff --git a/tests/slots.rs b/tests/slots.rs index df4af28..3035800 100644 --- a/tests/slots.rs +++ b/tests/slots.rs @@ -11,9 +11,9 @@ fn make_game() -> SlotsGame { #[test] fn three_of_a_kind_pays() { let game = make_game(); - // CCC = 5x + // CCC = 2x assert_eq!(game.calculate_payout(&['C', 'C', 'C'], 100), 200); - // SSS = 100x + // SSS = 28x assert_eq!(game.calculate_payout(&['S', 'S', 'S'], 10), 280); } @@ -21,9 +21,9 @@ fn three_of_a_kind_pays() { #[test] fn two_adjacent_pays() { let game = make_game(); - // CC? = 2x + // CC? = 1x assert_eq!(game.calculate_payout(&['C', 'C', 'D'], 100), 100); - // ?LL = 3x + // ?LL = 1x assert_eq!(game.calculate_payout(&['D', 'L', 'L'], 50), 50); } diff --git a/tests/tofu.rs b/tests/tofu.rs index 359c192..4f59d6d 100644 --- a/tests/tofu.rs +++ b/tests/tofu.rs @@ -1,8 +1,4 @@ //! TOFU trust tests. -//! -//! These pin the not-yet-implemented TOFU behavior: every test here is -//! expected to FAIL until the corresponding feature lands in `src/tofu.rs` -//! and the client storage layer. use std::path::PathBuf; use std::process::Command; -- cgit v1.2.3