1use super::*; 2 3fn record(sent_now: bool) -> Record { 4 Record { response: "{}".into(), request_id: None, attempts: 1, took_ms: 150.0, answered_ms: 1_790_971_860_000.0, sent_now, version: 1, versions: 1 } 5} 6 7#[test] 8fn a_moment_is_written_in_utc() { 9 assert_eq!(when(0.0), "1970-01-01 00:00 UTC"); 10 assert_eq!(when(1_790_971_860_000.0), "2026-10-02 20:11 UTC"); 11 assert_eq!(when(1_790_985_599_999.0), "2026-10-02 23:59 UTC"); 12} 13 14#[test] 15fn only_the_ask_that_sent_a_request_says_so() { 16 assert_eq!(record(true).sent(), Sent::Now); 17 assert_eq!(record(true).kept().sent(), Sent::Before { at_ms: 1_790_971_860_000.0 }); 18 assert_eq!(record(false).sent(), Sent::Before { at_ms: 1_790_971_860_000.0 }); 19} 20 21#[test] 22fn messages_survive_the_trip_to_the_object() { 23 let asks = [ 24 Ask::Jev { input: "is it?".into(), wanted: Wanted::Facts(Vec::new()), request: "{}".into(), pick: Pick::Fresh }, 25 Ask::Llm { model: "@cf/x".into(), request: "{}".into(), pick: Pick::Version(2) }, 26 Ask::Asked { input: "is it?".into(), answers: vec![noul(0.55)], llm: false, listed: true, who: Some(Asker("ab".into())), browser: "0b5f".into() }, 27 Ask::Asked { input: "is it?".into(), answers: vec![noul(0.55)], llm: false, listed: true, who: None, browser: String::new() }, 28 Ask::Rate { input: "is it?".into(), who: Asker("ab".into()), vote: Vote::Down, browser: "0b5f".into() }, 29 Ask::Rating { input: "is it?".into(), who: None }, 30 Ask::Comment { input: "is it?".into(), who: Asker("ab".into()), browser: "0b5f".into(), comment: "why <b>not</b>?".into() }, 31 Ask::Home, 32 Ask::Answer { input: "is it?".into() }, 33 ]; 34 for ask in asks { 35 assert_eq!(serde_json::from_str::<Ask>(&serde_json::to_string(&ask).unwrap()).unwrap(), ask); 36 } 37 let told = Told::Called(Called::Answered(record(true))); 38 assert_eq!(serde_json::from_str::<Told>(&serde_json::to_string(&told).unwrap()).unwrap(), told); 39} 40 41#[test] 42fn the_share_with_no_llm_is_a_whole_percentage_and_nothing_of_nothing() { 43 let stats = Stats { questions: 3, no_llm: 2, ..Stats::default() }; 44 assert_eq!(stats.no_llm_percent(), Some(67)); 45 assert_eq!(Stats::default().no_llm_percent(), None); 46} 47 48fn noul(p_yes: f64) -> Answer { 49 Answer { headline: "Yes?".into(), detail: Detail::Noul { p_yes } } 50} 51 52#[test] 53fn an_answer_says_its_type_and_its_numbers_in_a_line() { 54 assert_eq!(noul(0.55).line(), "Yes? · Noul · p(yes) = 0.55"); 55 let odds = Answer { headline: "28%.".into(), detail: Detail::Chance { p: 0.28 } }; 56 assert_eq!(odds.line(), "28%. · Noul · p = 0.28 · Jev thinks that's unlikely"); 57 let pick = Answer { 58 headline: "Pepperoni.".into(), 59 detail: Detail::Choice { 60 confidence: 0.95, 61 options: vec![("Pepperoni".into(), 0.9), ("Ham".into(), 0.06), ("Olive".into(), 0.03), ("Corn".into(), 0.01)], 62 }, 63 }; 64 assert_eq!(pick.line(), "Pepperoni. · Choice · confidence 0.95 · Pepperoni 90%, Ham 6%, Olive 3%"); 65 let grade = Answer { 66 headline: "Medium.".into(), 67 detail: Detail::Score { 68 score: 1.2, 69 confidence: 0.6, 70 levels: vec![("Mild".into(), 0.1), ("Medium".into(), 0.7), ("Hot".into(), 0.2)], 71 }, 72 }; 73 assert_eq!(grade.line(), "Medium. · Score · 1.2 of 2 · confidence 0.60 · Medium 70%, Hot 20%, Mild 10%"); 74} 75 76#[test] 77fn answers_kept_before_their_numbers_were_are_still_read() { 78 let old = stored(r#"["No?","Python."]"#); 79 assert_eq!(old.len(), 2); 80 assert_eq!(old[0], Answer { headline: "No?".into(), detail: Detail::Unknown }); 81 assert_eq!(old[0].line(), "No?"); 82 let new = serde_json::to_string(&vec![noul(0.55)]).unwrap(); 83 assert_eq!(stored(&new), [noul(0.55)]); 84 assert_eq!(stored("not json"), Vec::<Answer>::new()); 85} 86 87 88#[test] 89fn a_place_is_kept_only_if_it_looks_like_one() { 90 assert_eq!(Place::new(Some("us"), Some(" Austin ")), Place { country: Some("US".into()), city: Some("Austin".into()) }); 91 assert_eq!(Place::new(Some("T1"), None), Place::default()); 92 assert_eq!(Place::new(Some("<b>"), Some("a\nb")), Place::default()); 93 assert_eq!(Place::new(Some("GB"), Some(&"x".repeat(65))).city, None); 94 assert_eq!(Place::new(Some("GB"), None).flag().as_deref(), Some("🇬🇧")); 95 assert_eq!(Place::new(Some("DE"), Some("Zürich")).label(), "Zürich, DE"); 96 assert_eq!(Place::default().label(), "somewhere"); 97} 98 99#[test] 100fn a_place_comes_back_from_its_header_whole() { 101 assert_eq!(percent_decoded("S%C3%A3o+Paulo").as_deref(), Some("São Paulo")); 102 assert_eq!(percent_decoded("bad%2"), None); 103 assert_eq!(percent_decoded("%FF"), None); 104} 105 106#[test] 107fn places_are_counted_most_first() { 108 let at = |country: &str, city: &str| Place::new(Some(country), Some(city)); 109 let counted = places([at("US", "Austin"), at("GB", "London"), at("US", "Austin"), at("DE", "Berlin")]); 110 let labels: Vec<(String, u32)> = counted.iter().map(|(place, n)| (place.label(), *n)).collect(); 111 assert_eq!(labels, [("Austin, US".to_owned(), 2), ("Berlin, DE".to_owned(), 1), ("London, GB".to_owned(), 1)]); 112} 113 114#[test] 115fn an_unplaced_page_is_asked_to_reconnect_once_the_deploy_has_settled() { 116 let unplaced = Seen { place: Place::default(), passed: false, at_ms: 0.0, event: None }; 117 assert!(!Seen::stale(Some(&unplaced), REPLACE_AFTER_MS - 1.0)); 118 assert!(Seen::stale(Some(&unplaced), REPLACE_AFTER_MS)); 119 // Placed, or passed with no place to give: never. 120 assert!(!Seen::stale(Some(&Seen { passed: true, ..unplaced.clone() }), 1e12)); 121 // Accepted by code from before places: at once. 122 assert!(Seen::stale(None, 0.0)); 123} 124 125#[test] 126fn a_call_is_named_by_where_it_went_and_what_was_sent() { 127 let id = call_id("https://api.typesafe.ai/v1/systemone", "{}"); 128 assert_eq!(id.len(), 12); 129 assert_eq!(id, call_id("https://api.typesafe.ai/v1/systemone", "{}")); 130 assert_ne!(id, call_id("@cf/x", "{}")); 131 assert_ne!(id, call_id("https://api.typesafe.ai/v1/systemone", "{ }")); 132} 133 134#[test] 135fn a_record_from_before_versions_is_the_first_of_one() { 136 let old = r#"{"response":"{}","request_id":null,"attempts":1,"took_ms":1.0,"answered_ms":2.0,"sent_now":false}"#; 137 let record: Record = serde_json::from_str(old).unwrap(); 138 assert_eq!((record.version, record.versions), (1, 1)); 139 // An ask from a page from before picks the newest. 140 let ask: Ask = serde_json::from_str(r#"{"Llm":{"model":"@cf/x","request":"{}"}}"#).unwrap(); 141 assert_eq!(ask, Ask::Llm { model: "@cf/x".into(), request: "{}".into(), pick: Pick::Latest }); 142} 143 144#[test] 145fn pins_say_which_version_and_whether_to_send() { 146 let a = "aaaaaaaaaaaa"; 147 let b = "bbbbbbbbbbbb"; 148 let pins = Pins::parse(&format!("{a}:2;junk;{b}:0;<x>:fresh")); 149 assert_eq!(pins.0, vec![(a.to_owned(), Pick::Version(2))]); 150 assert!(pins.browsing()); 151 assert_eq!(pins.pick(a), Pick::Version(2)); 152 // Looking at an old version reads the rest, and sends none of it. 153 assert_eq!(pins.pick(b), Pick::KeptOnly); 154 // Asking again sends the rest as needed. 155 let again = Pins::parse(&format!("{a}:2;{a}:fresh")); 156 assert_eq!(again.pick(a), Pick::Fresh); 157 assert!(!again.browsing()); 158 assert_eq!(again.pick(b), Pick::Latest); 159 assert_eq!(Pins::parse("").pick(a), Pick::Latest); 160} 161 162#[test] 163fn the_admin_door_reads_and_never_writes_through_sql() { 164 for reads in [ 165 "SELECT * FROM events ORDER BY id DESC LIMIT 50", 166 " select name from sqlite_master where type = 'table';", 167 "WITH days AS (SELECT at_ms FROM events) SELECT COUNT(*) FROM days", 168 // What is in quotes is text, whatever it says. 169 "SELECT * FROM asked WHERE input LIKE '%delete; drop table asked%'", 170 "SELECT \"update\" FROM t", 171 // The function, which only reads. 172 "SELECT replace(path, '/', ' ') FROM events", 173 "SELECT REPLACE (path, '/', ' '), replaced FROM events", 174 ] { 175 assert!(reads_only(reads), "{reads}"); 176 } 177 for writes in [ 178 "DELETE FROM asked", 179 "UPDATE asked SET listed = 1", 180 "SELECT 1; DROP TABLE asked", 181 "WITH gone AS (SELECT 1) DELETE FROM asked", 182 // The statement, which writes. 183 "WITH one AS (SELECT 1) REPLACE INTO asked (input) SELECT 'x'", 184 "SELECT 1 replace", 185 "PRAGMA table_info(asked)", 186 "SELECT 'unclosed", 187 "", 188 ] { 189 assert!(!reads_only(writes), "{writes}"); 190 } 191} 192 193#[test] 194fn admin_messages_survive_the_trip() { 195 for ask in [ 196 Admin::Select { sql: "SELECT 1".into(), values: vec![serde_json::json!("x"), serde_json::json!(2)] }, 197 Admin::Moderate { input: "is it?".into(), listed: Some(false) }, 198 Admin::Moderate { input: "is it?".into(), listed: None }, 199 ] { 200 let wire = serde_json::to_string(&ask).unwrap(); 201 assert_eq!(serde_json::from_str::<Admin>(&wire).unwrap(), ask); 202 } 203} 204 205#[test] 206fn what_is_typed_finds_the_questions_it_could_begin() { 207 let entry = |input: &str, times: u32| Entry { input: input.to_owned(), answers: Vec::new(), asked_ms: 0.0, times }; 208 let listed = Listed::new([entry("Is a hot dog a sandwich?", 3), entry("is a slot machine a good retirement plan?", 9), entry("Which is a better pet?", 20), entry("How hot is the sun?", 1)]); 209 let found = |typed: &str| listed.suggest(typed).into_iter().map(|entry| entry.input).collect::<Vec<_>>(); 210 // Those that begin with it come first, the most asked of them first, whatever the case. 211 assert_eq!(found("Is a"), ["is a slot machine a good retirement plan?", "Is a hot dog a sandwich?", "Which is a better pet?"]); 212 // A word that begins there is found too; the middle of a word is not. 213 assert_eq!(found("hot"), ["Is a hot dog a sandwich?", "How hot is the sun?"]); 214 assert!(found("andwich").is_empty()); 215 // Nothing until three characters are typed, and never more than five. 216 assert!(found("is").is_empty() && found(" ").is_empty()); 217 let many = Listed::new((0..9).map(|n| entry(&format!("what is {n}?"), n))); 218 assert_eq!(many.suggest("what ").len(), SUGGESTED); 219 // The message crosses as JSON. 220 let ask = Ask::Suggest { typed: "is a".into() }; 221 assert_eq!(serde_json::from_str::<Ask>(&serde_json::to_string(&ask).unwrap()).unwrap(), ask); 222} 223 224#[test] 225fn a_comment_is_trimmed_and_refused_when_empty_or_too_long() { 226 use crate::{MOST_COMMENT, Unsaid, clean_comment}; 227 assert_eq!(clean_comment(" fair enough \r\n and more\u{7} "), Ok("fair enough \n and more".to_owned())); 228 assert_eq!(clean_comment("<script>alert(1)</script>"), Ok("<script>alert(1)</script>".to_owned()), "kept as written: escaping is the page's job"); 229 assert_eq!(clean_comment(""), Err(Unsaid::Empty)); 230 assert_eq!(clean_comment(" \n\t \u{0}"), Err(Unsaid::Empty)); 231 assert!(clean_comment(&"é".repeat(MOST_COMMENT)).is_ok(), "characters are counted, not bytes"); 232 assert_eq!(clean_comment(&"é".repeat(MOST_COMMENT + 1)), Err(Unsaid::TooLong)); 233 assert!(clean_comment(&format!(" {} ", "a".repeat(MOST_COMMENT))).is_ok(), "the cap is of the trimmed text"); 234} 235 236#[test] 237fn what_became_of_a_comment_survives_the_trip() { 238 for told in [Told::Commented(Ok(())), Told::Commented(Err(crate::Unsaid::NoVote))] { 239 assert_eq!(serde_json::from_str::<Told>(&serde_json::to_string(&told).unwrap()).unwrap(), told); 240 } 241}