summaryrefslogtreecommitdiffstats
path: root/tests/server_core.rs
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-01 17:49:08 +0100
committersalmonik <salmonik@tuta.io>2026-11-01 17:49:08 +0100
commit2fe2e8cdbc3bb520994ff0944188f2fa657c546a (patch)
tree10f29b4dd62c2b8127d20f0bffd4325b3ca0bfd2 /tests/server_core.rs
parentdocs: add the phased GUI plan (diff)
downloadzagan-bet-2fe2e8cdbc3bb520994ff0944188f2fa657c546a.tar.gz
zagan-bet-2fe2e8cdbc3bb520994ff0944188f2fa657c546a.zip
v4.0.0: require HELLO MAJOR.MINOR lockstep before auth
Diffstat (limited to 'tests/server_core.rs')
-rw-r--r--tests/server_core.rs100
1 files changed, 100 insertions, 0 deletions
diff --git a/tests/server_core.rs b/tests/server_core.rs
index 6d6cffd..e2600e4 100644
--- a/tests/server_core.rs
+++ b/tests/server_core.rs
@@ -100,7 +100,16 @@ fn crash_state(response: &str) -> CrashState {
100 serde_json::from_str(raw).expect("valid crash state") 100 serde_json::from_str(raw).expect("valid crash state")
101} 101}
102 102
103fn hello(core: &mut ServerCore, state: &mut ConnectionState) {
104 let response = core.handle_message(
105 state,
106 &format!("HELLO {}", zagan_bet::version::VERSION),
107 );
108 assert!(response.starts_with("HELLO "), "handshake failed: {response}");
109}
110
103fn register(core: &mut ServerCore, state: &mut ConnectionState, user: &str) { 111fn register(core: &mut ServerCore, state: &mut ConnectionState, user: &str) {
112 hello(core, state);
104 assert_eq!( 113 assert_eq!(
105 core.handle_message(state, &format!("REGISTER {user} credential")), 114 core.handle_message(state, &format!("REGISTER {user} credential")),
106 format!("REGISTERED {user}") 115 format!("REGISTERED {user}")
@@ -108,6 +117,7 @@ fn register(core: &mut ServerCore, state: &mut ConnectionState, user: &str) {
108} 117}
109 118
110fn login(core: &mut ServerCore, state: &mut ConnectionState, user: &str) { 119fn login(core: &mut ServerCore, state: &mut ConnectionState, user: &str) {
120 hello(core, state);
111 assert_eq!( 121 assert_eq!(
112 core.handle_message(state, &format!("AUTH {user} credential")), 122 core.handle_message(state, &format!("AUTH {user} credential")),
113 format!("OK {user}") 123 format!("OK {user}")
@@ -155,6 +165,7 @@ fn legacy_sha256_rows_are_upgraded_on_login() {
155 database::add_user(&conn, "legacy", "deadbeef").unwrap(); 165 database::add_user(&conn, "legacy", "deadbeef").unwrap();
156 166
157 let mut state = ConnectionState::default(); 167 let mut state = ConnectionState::default();
168 hello(&mut core, &mut state);
158 assert_eq!( 169 assert_eq!(
159 core.handle_message(&mut state, "AUTH legacy deadbeef"), 170 core.handle_message(&mut state, "AUTH legacy deadbeef"),
160 "OK legacy" 171 "OK legacy"
@@ -165,6 +176,7 @@ fn legacy_sha256_rows_are_upgraded_on_login() {
165 assert!(bcrypt::verify("deadbeef", &stored).unwrap()); 176 assert!(bcrypt::verify("deadbeef", &stored).unwrap());
166 177
167 let mut other = ConnectionState::default(); 178 let mut other = ConnectionState::default();
179 hello(&mut core, &mut other);
168 assert_eq!( 180 assert_eq!(
169 core.handle_message(&mut other, "AUTH legacy wrong"), 181 core.handle_message(&mut other, "AUTH legacy wrong"),
170 "ERROR Invalid credentials" 182 "ERROR Invalid credentials"
@@ -876,6 +888,7 @@ fn an_insufficient_bet_leaves_no_reservation() {
876fn history_requires_login_and_valid_counts() { 888fn history_requires_login_and_valid_counts() {
877 let (mut core, _path) = setup("history-guards", WIN3); 889 let (mut core, _path) = setup("history-guards", WIN3);
878 let mut state = ConnectionState::default(); 890 let mut state = ConnectionState::default();
891 hello(&mut core, &mut state);
879 892
880 assert_eq!( 893 assert_eq!(
881 core.handle_message(&mut state, "HISTORY"), 894 core.handle_message(&mut state, "HISTORY"),
@@ -928,3 +941,90 @@ fn history_lists_recent_plays_newest_first() {
928 // Newest first: the timestamps must not increase across the list. 941 // Newest first: the timestamps must not increase across the list.
929 assert!(records[0].at >= records[1].at); 942 assert!(records[0].at >= records[1].at);
930} 943}
944
945#[test]
946fn a_fresh_connection_must_greet_before_anything_else() {
947 let (mut core, _path) = setup("hello-gate", WIN3);
948 let mut state = ConnectionState::default();
949
950 assert_eq!(
951 core.handle_message(&mut state, "PING"),
952 "ERROR HELLO required"
953 );
954 assert_eq!(
955 core.handle_message(&mut state, "AUTH alice x"),
956 "ERROR HELLO required"
957 );
958 assert_eq!(
959 core.handle_message(&mut state, "PLAY_START slots 10"),
960 "ERROR HELLO required"
961 );
962 assert!(!state.greeted());
963}
964
965#[test]
966fn hello_reports_the_version_and_download_url() {
967 let (mut core, _path) = setup("hello-ok", WIN3);
968 let mut state = ConnectionState::default();
969
970 let response = core.handle_message(
971 &mut state,
972 &format!("HELLO {}", zagan_bet::version::VERSION),
973 );
974 let hello = zagan_bet::version::parse_hello(&response).expect("hello reply");
975
976 assert_eq!(hello.version, zagan_bet::version::VERSION);
977 assert_eq!(hello.latest, zagan_bet::version::VERSION);
978 assert!(hello.url.starts_with("https://"), "url: {}", hello.url);
979 assert!(state.greeted());
980
981 // The gate is open now.
982 assert_eq!(core.handle_message(&mut state, "PING"), "PONG");
983}
984
985#[test]
986fn hello_refuses_a_different_major_or_minor() {
987 let (mut core, _path) = setup("hello-mismatch", WIN3);
988 let server = zagan_bet::version::Version::parse(zagan_bet::version::VERSION).unwrap();
989 let versions = [
990 format!("{}.{}.{}", server.major, server.minor + 1, server.patch),
991 format!("{}.{}.{}", server.major + 1, server.minor, server.patch),
992 ];
993
994 for version in versions {
995 let mut state = ConnectionState::default();
996 let response = core.handle_message(&mut state, &format!("HELLO {version}"));
997
998 assert!(
999 response.starts_with("ERROR Version mismatch"),
1000 "response: {response}"
1001 );
1002 assert!(!state.greeted(), "a refused handshake must not open the gate");
1003 assert_eq!(
1004 core.handle_message(&mut state, "PING"),
1005 "ERROR HELLO required"
1006 );
1007 }
1008}
1009
1010#[test]
1011fn hello_accepts_a_patch_difference_and_rejects_bad_input() {
1012 let (mut core, _path) = setup("hello-patch", WIN3);
1013 let server = zagan_bet::version::Version::parse(zagan_bet::version::VERSION).unwrap();
1014 let patch = format!("{}.{}.{}", server.major, server.minor, server.patch + 1);
1015
1016 let mut state = ConnectionState::default();
1017 assert!(core
1018 .handle_message(&mut state, &format!("HELLO {patch}"))
1019 .starts_with("HELLO "));
1020
1021 let mut other = ConnectionState::default();
1022 assert_eq!(
1023 core.handle_message(&mut other, "HELLO"),
1024 "ERROR Invalid HELLO format. Usage: HELLO <version>"
1025 );
1026 assert_eq!(
1027 core.handle_message(&mut other, "HELLO nope"),
1028 "ERROR Invalid version"
1029 );
1030}