c9effeac326a | atagen | 2026-10-25 10:32 UTC | 1 | use serde_json::json; |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 2 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 3 | use super::*; |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 4 | use crate::search::index::KeywordIndex; |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 5 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 6 | const ROOM: &str = "!room:x"; |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 7 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 8 | fn message(id: &str, sender: &str, ts: u64, body: &str) -> Value { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 9 | json!({ |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 10 | "type": "m.room.message", |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 11 | "event_id": id, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 12 | "sender": sender, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 13 | "origin_server_ts": ts, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 14 | "content": { "msgtype": "m.text", "body": body }, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 15 | }) |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 16 | } |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 17 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 18 | fn edit(id: &str, sender: &str, ts: u64, target: &str, body: &str) -> Value { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 19 | json!({ |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 20 | "type": "m.room.message", |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 21 | "event_id": id, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 22 | "sender": sender, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 23 | "origin_server_ts": ts, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 24 | "content": { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 25 | "msgtype": "m.text", |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 26 | "body": format!("* {body}"), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 27 | "m.new_content": { "msgtype": "m.text", "body": body }, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 28 | "m.relates_to": { "rel_type": "m.replace", "event_id": target }, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 29 | }, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 30 | }) |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 31 | } |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 32 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 33 | fn redaction(id: &str, target: &str) -> Value { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 34 | json!({ |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 35 | "type": "m.room.redaction", |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 36 | "event_id": id, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 37 | "sender": "@a:x", |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 38 | "origin_server_ts": 99, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 39 | "redacts": target, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 40 | "content": {}, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 41 | }) |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 42 | } |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 43 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 44 | fn feed(index: &mut KeywordIndex, redactions: &mut Redactions, value: Value) -> Effect { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 45 | apply(index, redactions, ROOM, observe(&value)) |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 46 | } |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 47 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 48 | fn keys(index: &mut KeywordIndex, query: &str) -> Vec<String> { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 49 | let (hits, _) = index.search(query, None, None, 10).unwrap(); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 50 | hits.into_iter().map(|hit| hit.key).collect() |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 51 | } |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 52 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 53 | /// backfill walks newest first, so an edit routinely lands before what it edits |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 54 | #[test] |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 55 | fn an_edit_before_its_original_keeps_the_edit_and_takes_the_original_timestamp() { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 56 | let mut index = KeywordIndex::new(false); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 57 | let mut redactions = Redactions::default(); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 58 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 59 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 60 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 61 | edit("$e", "@a:x", 20, "$o", "fixed words"), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 62 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 63 | let provisional = index.get("$o").unwrap(); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 64 | assert!(provisional.provisional); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 65 | assert_eq!(provisional.ts, 20); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 66 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 67 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 68 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 69 | message("$o", "@a:x", 10, "typo wrds"), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 70 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 71 | let doc = index.get("$o").unwrap(); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 72 | assert!(!doc.provisional); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 73 | assert_eq!((doc.ts, doc.content.as_str()), (10, "$e")); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 74 | assert_eq!(keys(&mut index, "fixed"), vec!["$o"]); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 75 | assert!(keys(&mut index, "wrds").is_empty()); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 76 | } |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 77 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 78 | #[test] |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 79 | fn edits_from_someone_else_and_older_edits_lose() { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 80 | let mut index = KeywordIndex::new(false); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 81 | let mut redactions = Redactions::default(); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 82 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 83 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 84 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 85 | message("$o", "@a:x", 10, "original"), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 86 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 87 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 88 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 89 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 90 | edit("$e2", "@a:x", 30, "$o", "newest"), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 91 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 92 | assert_eq!( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 93 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 94 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 95 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 96 | edit("$e1", "@a:x", 20, "$o", "older") |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 97 | ), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 98 | Effect::Nothing |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 99 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 100 | assert_eq!( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 101 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 102 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 103 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 104 | edit("$h", "@b:x", 40, "$o", "hijack") |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 105 | ), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 106 | Effect::Nothing |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 107 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 108 | assert_eq!(keys(&mut index, "newest"), vec!["$o"]); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 109 | assert!(keys(&mut index, "hijack").is_empty()); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 110 | assert!(keys(&mut index, "original").is_empty()); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 111 | } |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 112 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 113 | #[test] |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 114 | fn a_provisional_edit_from_someone_else_gives_way_to_the_original() { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 115 | let mut index = KeywordIndex::new(false); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 116 | let mut redactions = Redactions::default(); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 117 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 118 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 119 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 120 | edit("$h", "@b:x", 20, "$o", "hijack"), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 121 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 122 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 123 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 124 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 125 | message("$o", "@a:x", 10, "genuine"), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 126 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 127 | assert_eq!(keys(&mut index, "genuine"), vec!["$o"]); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 128 | assert!(keys(&mut index, "hijack").is_empty()); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 129 | } |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 130 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 131 | #[test] |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 132 | fn a_redaction_removes_the_message_and_a_stale_copy_cannot_bring_it_back() { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 133 | let mut index = KeywordIndex::new(false); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 134 | let mut redactions = Redactions::default(); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 135 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 136 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 137 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 138 | message("$o", "@a:x", 10, "secret"), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 139 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 140 | assert_eq!( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 141 | feed(&mut index, &mut redactions, redaction("$r", "$o")), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 142 | Effect::Removed |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 143 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 144 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 145 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 146 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 147 | message("$o", "@a:x", 10, "secret"), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 148 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 149 | assert!(keys(&mut index, "secret").is_empty()); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 150 | } |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 151 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 152 | #[test] |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 153 | fn redacting_the_latest_edit_asks_for_the_original_again() { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 154 | let mut index = KeywordIndex::new(false); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 155 | let mut redactions = Redactions::default(); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 156 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 157 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 158 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 159 | message("$o", "@a:x", 10, "first"), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 160 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 161 | feed( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 162 | &mut index, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 163 | &mut redactions, |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 164 | edit("$e", "@a:x", 20, "$o", "second"), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 165 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 166 | assert_eq!( |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 167 | feed(&mut index, &mut redactions, redaction("$r", "$e")), |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 168 | Effect::Refetch("$o".to_owned()) |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 169 | ); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 170 | let mut original = message("$o", "@a:x", 10, "first"); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 171 | original["unsigned"] = |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 172 | json!({ "m.relations": { "m.replace": edit("$e", "@a:x", 20, "$o", "second") } }); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 173 | feed(&mut index, &mut redactions, original); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 174 | assert_eq!(keys(&mut index, "first"), vec!["$o"]); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 175 | assert!(keys(&mut index, "second").is_empty()); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 176 | } |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 177 | |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 178 | #[test] |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 179 | fn reply_fallbacks_are_not_indexed() { |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 180 | let mut index = KeywordIndex::new(false); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 181 | let mut redactions = Redactions::default(); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 182 | let mut reply = message("$r", "@a:x", 10, "> <@b:x> quoted stuff\n\nactual answer"); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 183 | reply["content"]["m.relates_to"] = json!({ "m.in_reply_to": { "event_id": "$q" } }); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 184 | feed(&mut index, &mut redactions, reply); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 185 | assert_eq!(keys(&mut index, "answer"), vec!["$r"]); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 186 | assert!(keys(&mut index, "quoted").is_empty()); |
c9effeac326a | atagen | 2026-10-25 10:32 UTC | 187 | } |