summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/server_core.rs100
-rw-r--r--tests/version.rs71
2 files changed, 171 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}
diff --git a/tests/version.rs b/tests/version.rs
new file mode 100644
index 0000000..ef58601
--- /dev/null
+++ b/tests/version.rs
@@ -0,0 +1,71 @@
1use zagan_bet::version::{
2 compatible, hello_line, is_newer, parse_hello, Version, VERSION,
3};
4
5#[test]
6fn versions_parse_and_reject_nonsense() {
7 assert_eq!(
8 Version::parse("4.0.0"),
9 Some(Version {
10 major: 4,
11 minor: 0,
12 patch: 0
13 })
14 );
15 assert_eq!(
16 Version::parse("v4.1.2"),
17 Some(Version {
18 major: 4,
19 minor: 1,
20 patch: 2
21 })
22 );
23 assert_eq!(
24 Version::parse("4.0.0-rc1").map(|v| (v.major, v.minor, v.patch)),
25 Some((4, 0, 0))
26 );
27 assert_eq!(
28 Version::parse("4.0.0+build7").map(|v| v.patch),
29 Some(0)
30 );
31
32 for bad in ["", "4", "4.0", "4.0.0.1", "x.0.0", "4.x.0", "4.0.0-"] {
33 assert!(Version::parse(bad).is_none(), "{bad:?} should not parse");
34 }
35
36 assert!(Version::parse(VERSION).is_some(), "the build version must parse");
37}
38
39#[test]
40fn compatibility_is_major_and_minor_only() {
41 assert!(compatible("4.0.0", "4.0.9"));
42 assert!(compatible("4.2.7", "4.2.1"));
43 assert!(!compatible("4.0.0", "4.1.0"));
44 assert!(!compatible("4.0.0", "5.0.0"));
45 assert!(!compatible("nope", "4.0.0"));
46}
47
48#[test]
49fn newer_versions_are_ordered_by_major_minor_patch() {
50 assert!(is_newer("4.0.1", "4.0.0"));
51 assert!(is_newer("4.1.0", "4.0.9"));
52 assert!(is_newer("5.0.0", "4.9.9"));
53 assert!(!is_newer("4.0.0", "4.0.0"));
54 assert!(!is_newer("4.0.0", "4.0.1"));
55 assert!(!is_newer("nonsense", "4.0.0"));
56}
57
58#[test]
59fn the_hello_reply_round_trips() {
60 let line = hello_line("4.0.0", "4.1.2", "https://zagan.example/");
61 let reply = parse_hello(&line).expect("parse a generated reply");
62
63 assert_eq!(reply.version, "4.0.0");
64 assert_eq!(reply.latest, "4.1.2");
65 assert_eq!(reply.url, "https://zagan.example/");
66
67 assert!(parse_hello("HELLO 4.0.0 url=https://x/").is_none(), "latest is required");
68 assert!(parse_hello("HELLO 4.0.0 latest=4.0.0").is_none(), "url is required");
69 assert!(parse_hello("PONG").is_none());
70 assert!(parse_hello("HELLO").is_none());
71}