From 49df948de25d28601469e9ca993f33530989ebd0 Mon Sep 17 00:00:00 2001 From: salmonik Date: Thu, 29 Oct 2026 22:55:49 +0100 Subject: v3.0.0: cursor-driven roulette felt with splits and corners; drop schema migrations; require 80x24 --- tests/client_core.rs | 86 +++++++------ tests/client_ui.rs | 52 +++++++- tests/roulette.rs | 334 ++++++++++++++++++++++++++++++++++++++------------- tests/server_core.rs | 10 +- tests/storage.rs | 49 -------- 5 files changed, 363 insertions(+), 168 deletions(-) (limited to 'tests') diff --git a/tests/client_core.rs b/tests/client_core.rs index 176791e..dea6808 100644 --- a/tests/client_core.rs +++ b/tests/client_core.rs @@ -591,26 +591,34 @@ fn rejects_a_grid_with_the_wrong_size() { } #[test] -fn roulette_split_bets_spin_and_show_the_result() { +fn roulette_cursor_places_bets_and_spins() { let mut h = Harness::new(); h.enter_roulette(); - assert_eq!(h.board().len(), 8); + assert_eq!(h.board().len(), 12); assert_eq!(h.board()[0], "=== ROULETTE ==="); assert!(h.board()[1].contains("Balance: 1000 PLN")); assert!(h.board()[1].contains("Chip: 10 PLN")); - assert!(h.board()[2].contains("Bets: (none)")); + assert!(h.board()[9].contains("Bets: (none)")); + // The cursor starts on number 1, the bottom-left corner. + assert!(h.board()[5].contains("[ 1 ]")); + + // Up twice: straight 2, then the split between 2 and 3. + h.key(Key::Up); + h.key(Key::Up); + assert!(h.board()[4].contains("[ 2 ]")); + assert!(h.board()[3].contains("[ 3 ]")); - h.key(Key::Char('r')); - h.key(Key::Char('e')); - h.key(Key::Char('1')); - h.key(Key::Char('7')); h.key(Key::Enter); + assert!(h.board()[9].contains("2/3 10")); + assert!(h.board()[9].contains("Total: 10 PLN")); - assert!(h.board()[2].contains("RED 10")); - assert!(h.board()[2].contains("EVEN 10")); - assert!(h.board()[2].contains("#17 10")); - assert!(h.board()[2].contains("Total: 30 PLN")); + // Back down to number 1 and place a straight. + h.key(Key::Down); + h.key(Key::Down); + h.key(Key::Enter); + assert!(h.board()[9].contains("1 10")); + assert!(h.board()[9].contains("Total: 20 PLN")); h.key(Key::Char(' ')); let request = h @@ -620,18 +628,41 @@ fn roulette_split_bets_spin_and_show_the_result() { .expect("roulette request"); assert_eq!( request, - "PLAY_START roulette 30 {\"red\":10,\"black\":0,\"even\":10,\"odd\":0,\"numbers\":[{\"number\":17,\"amount\":10}]}" + "PLAY_START roulette 20 {\"chips\":[{\"target\":{\"bet\":\"split\",\"a\":2,\"b\":3},\"amount\":10},{\"target\":{\"bet\":\"straight\",\"number\":1},\"amount\":10}]}" ); - let bets = r#"{"red":10,"black":0,"even":10,"odd":0,"numbers":[{"number":17,"amount":10}]}"#; + let bets = r#"{"chips":[{"target":{"bet":"split","a":2,"b":3},"amount":10},{"target":{"bet":"straight","number":1},"amount":10}]}"#; h.reply(&format!( - "ROULETTE_RESULT session-1 360 30 1330 17 black {bets}" + "ROULETTE_RESULT session-1 180 20 1160 3 red {bets}" )); - assert_eq!(h.board().len(), 8); - assert!(h.board()[1].contains("Balance: 1330 PLN")); - assert!(h.board()[4].contains("Winning: 17 BLACK ODD")); - assert!(h.board()[5].contains("Won 360 PLN")); + assert_eq!(h.board().len(), 12); + assert!(h.board()[1].contains("Balance: 1160 PLN")); + assert!(h.board()[10].contains("Won 180 PLN at 3 RED ODD")); + assert!(h.board()[10].contains("+160")); +} + +#[test] +fn roulette_cursor_reaches_the_outside_rows() { + let mut h = Harness::new(); + h.enter_roulette(); + + h.key(Key::Down); + assert!(h.board()[6].starts_with("[") && h.board()[6].contains("1st 12")); + h.key(Key::Down); + assert!(h.board()[7].starts_with("[") && h.board()[7].contains("2:1")); + h.key(Key::Down); + assert!(h.board()[8].starts_with("[") && h.board()[8].contains("1-18")); + + h.key(Key::Enter); + assert!(h.board()[9].contains("1-18 10")); + + // Back up to the felt: the bottom edge of the first dozen is the 1/4 split. + h.key(Key::Up); + h.key(Key::Up); + h.key(Key::Up); + assert!(h.board()[5].contains("[ 1 ]")); + assert!(h.board()[5].contains("[ 4 ]")); } #[test] @@ -640,7 +671,7 @@ fn roulette_rejects_an_empty_spin_and_accepts_the_equals_chip_key() { h.enter_roulette(); h.key(Key::Char(' ')); - assert!(h.board()[4].contains("ERROR: Place at least one bet")); + assert!(h.board()[10].contains("ERROR: Place at least one bet")); assert!(!h .sent .iter() @@ -648,22 +679,9 @@ fn roulette_rejects_an_empty_spin_and_accepts_the_equals_chip_key() { h.key(Key::Char('=')); assert!(h.board()[1].contains("Chip: 20 PLN")); - h.key(Key::Char('r')); - assert!(h.board()[2].contains("RED 20")); - assert!(h.board()[2].contains("Total: 20 PLN")); -} - -#[test] -fn roulette_rejects_an_out_of_range_number() { - let mut h = Harness::new(); - h.enter_roulette(); - - h.key(Key::Char('9')); - h.key(Key::Char('9')); h.key(Key::Enter); - - assert!(h.board()[4].contains("ERROR: Number must be 0-36")); - assert!(h.board()[2].contains("Bets: (none)")); + assert!(h.board()[9].contains("1 20")); + assert!(h.board()[9].contains("Total: 20 PLN")); } fn bj_state( diff --git a/tests/client_ui.rs b/tests/client_ui.rs index 44febde..7969139 100644 --- a/tests/client_ui.rs +++ b/tests/client_ui.rs @@ -2,7 +2,7 @@ use std::time::Duration; use zagan_bet::client::ui::{ command_names, completion_lines, display_width, first_contact_lines, format_pong, help_lines, host_lines, input_cursor_column, lines_to_move_up, parse_bet_arg, record_history, - visual_line_count, + terminal_too_small, visual_line_count, MIN_COLUMNS, MIN_ROWS, }; #[test] @@ -255,3 +255,53 @@ fn hosts_without_a_fingerprint_are_never_merged() { 2 ); } + +#[test] +fn a_terminal_smaller_than_every_game_needs_is_refused() { + assert!(terminal_too_small(80, 24).is_none()); + assert!(terminal_too_small(120, 40).is_none()); + + assert!(terminal_too_small(79, 24).is_some()); + assert!(terminal_too_small(80, 23).is_some()); + + let message = terminal_too_small(40, 10).unwrap(); + assert!(message.contains("80x24")); + assert!(message.contains("40x10")); +} + +#[test] +fn the_roulette_felt_fits_the_minimum_terminal() { + use zagan_bet::client::games::roulette::{Cursor, RouletteGame, ROULETTE_BOARD_LINES}; + use zagan_bet::client::games::traits::{Game, GamePhase, GameState}; + use zagan_bet::roulette::Bets; + use std::collections::HashMap; + + let mut data = HashMap::new(); + data.insert("balance".to_string(), "1000".to_string()); + data.insert("chip".to_string(), "10".to_string()); + data.insert("status".to_string(), "place".to_string()); + data.insert("bets".to_string(), Bets::default().to_json()); + data.insert("cursor".to_string(), Cursor::Zero.encoded()); + let state = GameState { + game_type: "roulette".to_string(), + bet_amount: 0, + phase: GamePhase::WaitingToStart, + data, + }; + + let board = RouletteGame::new().render(&state); + let lines: Vec<&str> = board.split("\r\n").collect(); + assert_eq!(lines.len(), ROULETTE_BOARD_LINES); + assert_eq!(RouletteGame::new().render_lines(&state), ROULETTE_BOARD_LINES); + for line in lines { + assert!( + display_width(line) <= MIN_COLUMNS as usize, + "roulette line is {} wide: {line:?}", + display_width(line) + ); + } + assert!( + ROULETTE_BOARD_LINES + 4 <= MIN_ROWS as usize, + "the board must leave room for the prompt and a few transcript lines" + ); +} diff --git a/tests/roulette.rs b/tests/roulette.rs index 932bace..8f9155c 100644 --- a/tests/roulette.rs +++ b/tests/roulette.rs @@ -1,4 +1,7 @@ -use zagan_bet::roulette::{color, is_black, is_even, is_odd, is_red, rtp, Bets, NumberBet, WHEEL}; +use zagan_bet::client::games::roulette::{Cursor, Direction}; +use zagan_bet::roulette::{ + color, is_black, is_even, is_odd, is_red, rtp, BetTarget, Bets, Outside, WHEEL, +}; use zagan_bet::server::games::roulette::RouletteGame; use zagan_bet::server::games::traits::Game; @@ -31,102 +34,184 @@ fn european_rtp_is_36_over_37() { } #[test] -fn bets_total_and_winning_number_payouts() { - let bets = Bets { - red: 10, - black: 10, - even: 10, - odd: 10, - numbers: vec![NumberBet { - number: 17, - amount: 5, - }], +fn inside_bets_pay_their_true_odds() { + let straight = BetTarget::Straight { number: 17 }; + assert_eq!(straight.return_multiplier(17), 36); + assert_eq!(straight.return_multiplier(18), 0); + + let split = BetTarget::Split { a: 1, b: 2 }; + assert_eq!(split.return_multiplier(1), 18); + assert_eq!(split.return_multiplier(2), 18); + assert_eq!(split.return_multiplier(3), 0); + assert_eq!(split.numbers(), vec![1, 2]); + + let corner = BetTarget::Corner { + numbers: [1, 2, 4, 5], }; + for number in [1, 2, 4, 5] { + assert_eq!(corner.return_multiplier(number), 9); + } + assert_eq!(corner.return_multiplier(3), 0); + assert_eq!(corner.numbers(), vec![1, 2, 4, 5]); +} + +#[test] +fn dozens_and_columns_pay_three_for_one_and_zero_loses() { + let first_dozen = BetTarget::Dozen { index: 0 }; + assert_eq!(first_dozen.return_multiplier(1), 3); + assert_eq!(first_dozen.return_multiplier(12), 3); + assert_eq!(first_dozen.return_multiplier(13), 0); + assert_eq!(first_dozen.return_multiplier(0), 0); + + let second_dozen = BetTarget::Dozen { index: 1 }; + assert_eq!(second_dozen.return_multiplier(13), 3); + assert_eq!(second_dozen.return_multiplier(24), 3); + + let first_column = BetTarget::Column { index: 0 }; + assert_eq!(first_column.return_multiplier(1), 3); + assert_eq!(first_column.return_multiplier(34), 3); + assert_eq!(first_column.return_multiplier(2), 0); + + let third_column = BetTarget::Column { index: 2 }; + assert_eq!(third_column.return_multiplier(36), 3); + assert_eq!(third_column.return_multiplier(0), 0, "0 is not in a column"); - assert_eq!(bets.total(), 45); - // 17 is black and odd: 10*2 + 10*2 + 5*36. - assert_eq!(bets.winnings(17), 220); - // 18 is red and even. - assert_eq!(bets.winnings(18), 40); + for index in 0..3 { + let dozen = BetTarget::Dozen { index }; + let winning = index as u8 * 12 + 1; + assert_eq!(dozen.return_multiplier(winning), 3); + } } #[test] -fn zero_loses_all_outside_bets_but_pays_a_straight_zero() { - let outside = Bets { - red: 10, - black: 10, - even: 10, - odd: 10, - numbers: vec![], - }; - assert_eq!(outside.winnings(0), 0); - - let straight = Bets { - red: 10, - black: 10, - even: 10, - odd: 10, - numbers: vec![NumberBet { - number: 0, - amount: 5, - }], - }; - assert_eq!(straight.total(), 45); - assert_eq!(straight.winnings(0), 180); +fn outside_bets_pay_even_money_and_zero_loses_everything() { + let cases = [ + (Outside::Low, 18u8, 19u8), + (Outside::High, 19, 18), + (Outside::Even, 2, 3), + (Outside::Odd, 3, 2), + (Outside::Red, 1, 2), + (Outside::Black, 2, 1), + ]; + for (kind, winning, losing) in cases { + let bet = BetTarget::Outside { kind }; + assert_eq!(bet.return_multiplier(winning), 2, "{kind:?} should win"); + assert_eq!(bet.return_multiplier(losing), 0, "{kind:?} should lose"); + assert_eq!(bet.return_multiplier(0), 0, "0 loses every outside bet"); + } +} + +#[test] +fn chips_on_the_same_target_merge() { + let mut bets = Bets::default(); + assert!(bets.is_empty()); + + bets.add(BetTarget::Straight { number: 17 }, 10); + bets.add(BetTarget::Straight { number: 17 }, 5); + bets.add(BetTarget::Outside { kind: Outside::Red }, 10); + + assert_eq!(bets.chips.len(), 2); + assert_eq!( + bets.amount_on(&BetTarget::Straight { number: 17 }), + 15 + ); + assert_eq!(bets.total(), 25); + assert_eq!(bets.winnings(17), 15 * 36); } #[test] -fn bets_validate_empty_balance_and_invalid_numbers() { +fn bets_settle_every_chip_independently() { + let mut bets = Bets::default(); + bets.add(BetTarget::Split { a: 1, b: 2 }, 10); + bets.add(BetTarget::Outside { kind: Outside::Red }, 10); + bets.add(BetTarget::Straight { number: 1 }, 5); + + // 2 is black, so the red chip loses; the split pays 18x. + assert_eq!(bets.winnings(2), 180); + // 3 is red, but the straight is on 1, so only the red chip pays. + assert_eq!(bets.winnings(3), 20); + // 1 is red, matches the straight, and is half of the split. + assert_eq!(bets.winnings(1), 5 * 36 + 10 * 2 + 10 * 18); +} + +#[test] +fn a_straight_zero_is_paid_while_outside_bets_lose() { + let mut bets = Bets::default(); + bets.add(BetTarget::Straight { number: 0 }, 5); + bets.add(BetTarget::Outside { kind: Outside::Red }, 10); + bets.add(BetTarget::Dozen { index: 0 }, 10); + + assert_eq!(bets.total(), 25); + assert_eq!(bets.winnings(0), 180); +} + +#[test] +fn bets_validate_balance_and_targets() { assert!(Bets::default().validate(1000).is_err()); - let too_big = Bets { - red: 2000, - ..Bets::default() - }; + let mut too_big = Bets::default(); + too_big.add(BetTarget::Outside { kind: Outside::Black }, 2000); assert!(too_big.validate(1000).is_err()); let bad_number = Bets { - numbers: vec![NumberBet { - number: 37, + chips: vec![zagan_bet::roulette::Chip { + target: BetTarget::Straight { number: 37 }, amount: 10, }], - ..Bets::default() }; assert!(bad_number.validate(1000).is_err()); -} -#[test] -fn the_server_settles_a_split_bet() { - let bets = Bets { - red: 10, - black: 10, - even: 10, - odd: 10, - numbers: vec![NumberBet { - number: 17, - amount: 5, + let bad_split = Bets { + chips: vec![zagan_bet::roulette::Chip { + target: BetTarget::Split { a: 4, b: 4 }, + amount: 10, }], }; - let payload = serde_json::to_string(&bets).unwrap(); - let game = RouletteGame::with_result(17); - let state = game.prepare_state("alice", bets.total()); + assert!(bad_split.validate(1000).is_err()); + + let bad_corner = Bets { + chips: vec![zagan_bet::roulette::Chip { + target: BetTarget::Corner { + numbers: [1, 2, 3, 3], + }, + amount: 10, + }], + }; + assert!(bad_corner.validate(1000).is_err()); + let bad_dozen = Bets { + chips: vec![zagan_bet::roulette::Chip { + target: BetTarget::Dozen { index: 3 }, + amount: 10, + }], + }; + assert!(bad_dozen.validate(1000).is_err()); +} + +#[test] +fn the_server_settles_split_and_outside_chips() { + let mut bets = Bets::default(); + bets.add(BetTarget::Split { a: 1, b: 2 }, 10); + bets.add(BetTarget::Outside { kind: Outside::Even }, 10); + let payload = bets.to_json(); + + let game = RouletteGame::with_result(2); + let state = game.prepare_state("alice", bets.total()); let state = game.process_action(&state, "spin", &[&payload]).unwrap(); let result = game.calculate_result(&state).unwrap(); - assert_eq!(result.winnings, 220); - assert_eq!(result.result_data.get("number").unwrap(), "17"); + // 2 is even: 10*18 (split) + 10*2 (even money). + assert_eq!(result.winnings, 200); + assert_eq!(result.result_data.get("number").unwrap(), "2"); assert_eq!(result.result_data.get("color").unwrap(), "black"); assert_eq!(result.result_data.get("bets").unwrap(), &payload); } #[test] fn the_server_rejects_a_total_that_does_not_match_the_bets() { - let payload = serde_json::to_string(&Bets { - red: 10, - ..Bets::default() - }) - .unwrap(); + let mut bets = Bets::default(); + bets.add(BetTarget::Outside { kind: Outside::Red }, 10); + let payload = bets.to_json(); let game = RouletteGame::with_result(17); let state = game.prepare_state("alice", 99); @@ -134,31 +219,116 @@ fn the_server_rejects_a_total_that_does_not_match_the_bets() { } #[test] -fn the_server_rejects_an_invalid_number() { - let payload = serde_json::to_string(&Bets { - numbers: vec![NumberBet { - number: 37, - amount: 10, - }], - ..Bets::default() - }) - .unwrap(); +fn the_server_rejects_an_invalid_target() { + let payload = r#"{"chips":[{"target":{"bet":"straight","number":37},"amount":10}]}"#; let game = RouletteGame::with_result(17); let state = game.prepare_state("alice", 10); - assert!(game.process_action(&state, "spin", &[&payload]).is_err()); + assert!(game.process_action(&state, "spin", &[payload]).is_err()); +} + +// --- betting cursor ------------------------------------------------------- + +fn target_at(x: usize, y: usize) -> BetTarget { + Cursor::Lattice { x, y }.target().unwrap() +} + +#[test] +fn lattice_points_become_straights_splits_and_corners() { + assert_eq!( + target_at(0, 3), + BetTarget::Straight { number: 1 }, + "bottom-left corner is a straight" + ); + assert_eq!(target_at(0, 0), BetTarget::Straight { number: 3 }); + assert_eq!(target_at(12, 0), BetTarget::Straight { number: 36 }); + assert_eq!(target_at(12, 3), BetTarget::Straight { number: 34 }); + + assert_eq!(target_at(0, 1), BetTarget::Split { a: 2, b: 3 }); + assert_eq!(target_at(12, 1), BetTarget::Split { a: 35, b: 36 }); + assert_eq!(target_at(1, 0), BetTarget::Split { a: 3, b: 6 }); + + assert_eq!( + target_at(1, 1), + BetTarget::Corner { + numbers: [2, 3, 5, 6] + } + ); + assert_eq!( + target_at(6, 2), + BetTarget::Corner { + numbers: [16, 17, 19, 20] + } + ); +} + +#[test] +fn the_cursor_walks_the_felt_and_the_outside_rows() { + let bottom = Cursor::Lattice { x: 0, y: 3 }; + assert_eq!(bottom.moved(Direction::Down), Cursor::Dozen(0)); + assert_eq!( + Cursor::Lattice { x: 12, y: 3 }.moved(Direction::Down), + Cursor::Dozen(2) + ); + assert_eq!( + Cursor::Lattice { x: 0, y: 0 }.moved(Direction::Up), + Cursor::Zero + ); + assert_eq!(Cursor::Zero.moved(Direction::Down), Cursor::Lattice { x: 0, y: 0 }); + + assert_eq!(Cursor::Dozen(0).moved(Direction::Up), Cursor::Lattice { x: 1, y: 3 }); + assert_eq!(Cursor::Dozen(0).moved(Direction::Down), Cursor::Column(0)); + assert_eq!(Cursor::Column(2).moved(Direction::Up), Cursor::Dozen(2)); + assert_eq!(Cursor::Column(1).moved(Direction::Down), Cursor::Outside(2)); + assert_eq!(Cursor::Outside(3).moved(Direction::Up), Cursor::Column(1)); + + // Edges stay put instead of wrapping. + assert_eq!(Cursor::Dozen(0).moved(Direction::Left), Cursor::Dozen(0)); + assert_eq!(Cursor::Outside(5).moved(Direction::Right), Cursor::Outside(5)); + assert_eq!( + Cursor::Lattice { x: 0, y: 2 }.moved(Direction::Left), + Cursor::Lattice { x: 0, y: 2 } + ); +} + +#[test] +fn every_cursor_round_trips_through_its_encoded_form() { + let cursors = [ + Cursor::Zero, + Cursor::Lattice { x: 7, y: 2 }, + Cursor::Dozen(1), + Cursor::Column(2), + Cursor::Outside(4), + ]; + for cursor in cursors { + assert_eq!(Cursor::parse(&cursor.encoded()), cursor); + } + assert_eq!(Cursor::parse("nonsense"), Cursor::default()); + assert_eq!(Cursor::parse("lattice:99:0"), Cursor::default()); + assert_eq!(Cursor::parse("dozen:7"), Cursor::default()); +} + +#[test] +fn the_outside_cursor_targets_match_the_labels() { + let labels: Vec = (0..6) + .map(|index| { + let target = Cursor::Outside(index).target().unwrap(); + target.label() + }) + .collect(); + assert_eq!(labels, vec!["1-18", "EVEN", "RED", "BLACK", "ODD", "19-36"]); } #[test] fn parses_a_roulette_result() { - let bets = r#"{"red":10,"black":0,"even":10,"odd":0,"numbers":[{"number":17,"amount":10}]}"#; - let response = format!("ROULETTE_RESULT session-1 360 30 1330 17 black {bets}"); + let bets = r#"{"chips":[{"target":{"bet":"outside","kind":"red"},"amount":30}]}"#; + let response = format!("ROULETTE_RESULT session-1 60 30 1030 17 black {bets}"); let result = zagan_bet::client::games::response::parse_roulette_response(&response).unwrap(); assert_eq!(result.session_id, "session-1"); - assert_eq!(result.winnings, 360); + assert_eq!(result.winnings, 60); assert_eq!(result.bet_amount, 30); - assert_eq!(result.balance, 1330); + assert_eq!(result.balance, 1030); assert_eq!(result.number, 17); assert_eq!(result.color, "black"); assert_eq!(result.bets, bets); @@ -168,7 +338,7 @@ fn parses_a_roulette_result() { fn rejects_bad_roulette_results() { use zagan_bet::client::games::response::parse_roulette_response; - let valid_bets = r#"{"red":10}"#; + let valid_bets = r#"{"chips":[]}"#; assert!( parse_roulette_response(&format!("ROULETTE_RESULT s 1 1 1 37 green {valid_bets}")).is_err() ); diff --git a/tests/server_core.rs b/tests/server_core.rs index 6b7b415..47e80e7 100644 --- a/tests/server_core.rs +++ b/tests/server_core.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use rusqlite::Connection; use zagan_bet::blackjack::BlackjackState; use zagan_bet::crash::CrashState; -use zagan_bet::roulette::Bets; +use zagan_bet::roulette::{BetTarget, Bets, Outside}; use zagan_bet::server::core::{ConnectionState, ServerCore}; use zagan_bet::server::database; use zagan_bet::server::games::crash::ManualClock; @@ -244,7 +244,13 @@ fn roulette_settles_split_bets_and_records_details() { register(&mut core, &mut state, "alice"); login(&mut core, &mut state, "alice"); - let bets = r#"{"red":10,"black":10,"even":10,"odd":10,"numbers":[{"number":17,"amount":5}]}"#; + let mut bets = Bets::default(); + bets.add(BetTarget::Outside { kind: Outside::Red }, 10); + bets.add(BetTarget::Outside { kind: Outside::Black }, 10); + bets.add(BetTarget::Outside { kind: Outside::Even }, 10); + bets.add(BetTarget::Outside { kind: Outside::Odd }, 10); + bets.add(BetTarget::Straight { number: 17 }, 5); + let bets = bets.to_json(); let response = core.handle_message(&mut state, &format!("PLAY_START roulette 45 {bets}")); assert!( diff --git a/tests/storage.rs b/tests/storage.rs index 19936c2..e6ad2d1 100644 --- a/tests/storage.rs +++ b/tests/storage.rs @@ -88,55 +88,6 @@ fn forgetting_one_address_keeps_the_shared_credentials() { assert_eq!(client_database::get_credentials(&conn, "server-a"), None); } -#[test] -fn legacy_host_keyed_credentials_are_rekeyed_by_fingerprint() { - let (_, conn) = tmp_db("creds-migrate"); - conn.execute_batch( - "CREATE TABLE hosts ( - host TEXT PRIMARY KEY, - last_used DATETIME DEFAULT CURRENT_TIMESTAMP, - fingerprint TEXT - ); - CREATE TABLE credentials ( - host TEXT PRIMARY KEY, - username TEXT NOT NULL, - password TEXT NOT NULL - ); - INSERT INTO hosts (host, last_used, fingerprint) - VALUES ('a:1', '2020-01-01 00:00:00', 'server-a'); - INSERT INTO hosts (host, last_used, fingerprint) - VALUES ('b:2', '2021-01-01 00:00:00', 'server-a'); - INSERT INTO hosts (host, last_used, fingerprint) - VALUES ('c:3', '2022-01-01 00:00:00', NULL); - INSERT INTO credentials VALUES ('a:1', 'alice', 'old'); - INSERT INTO credentials VALUES ('b:2', 'alice', 'recent'); - INSERT INTO credentials VALUES ('c:3', 'carol', 'untrusted');", - ) - .unwrap(); - - client_database::init_db(&conn); - - assert_eq!( - client_database::get_credentials(&conn, "server-a"), - Some(("alice".to_string(), "recent".to_string())), - "the most recently used address wins when legacy rows disagree" - ); - assert_eq!( - client_database::get_credentials(&conn, "carol"), - None, - "a host with no trusted key cannot be re-keyed" - ); - - // The old table is gone: the column set is the fingerprint schema. - let mut statement = conn.prepare("PRAGMA table_info(credentials)").unwrap(); - let columns: Vec = statement - .query_map([], |row| row.get(1)) - .unwrap() - .filter_map(|row| row.ok()) - .collect(); - assert_eq!(columns, vec!["fingerprint", "username", "password"]); -} - #[test] fn client_last_host_tracking() { let (_, conn) = tmp_db("hosts"); -- cgit v1.2.3