973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 1 | use std::{ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 2 | net::TcpListener, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 3 | path::PathBuf, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 4 | process::{Child, Command, Stdio}, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 5 | sync::atomic::{AtomicBool, Ordering}, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 6 | time::Duration, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 7 | }; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 8 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 9 | use matrix_sdk::test_utils::mocks::MatrixMockServer; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 10 | use reqwest::{Client, StatusCode, header}; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 11 | use serde_json::{Value, json}; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 12 | use tempfile::TempDir; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 13 | use wiremock::{ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 14 | Mock, ResponseTemplate, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 15 | matchers::{method, path_regex}, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 16 | }; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 17 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 18 | struct Server(Child); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 19 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 20 | impl Drop for Server { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 21 | fn drop(&mut self) { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 22 | let _ = self.0.kill(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 23 | let _ = self.0.wait(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 24 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 25 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 26 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 27 | fn server_binary() -> PathBuf { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 28 | option_env!("CARGO_BIN_EXE_malice") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 29 | .map(PathBuf::from) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 30 | .unwrap_or_else(|| { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 31 | std::env::current_exe() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 32 | .expect("integration test executable") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 33 | .parent() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 34 | .expect("cargo deps directory") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 35 | .parent() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 36 | .expect("cargo target directory") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 37 | .join("malice") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 38 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 39 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 40 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 41 | async fn start(port: u16, data_dir: &TempDir, client: &Client) -> Server { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 42 | let child = Command::new(server_binary()) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 43 | .env("MALICE_BIND", format!("127.0.0.1:{port}")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 44 | .env("MALICE_DATA_DIR", data_dir.path()) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 45 | .env("MALICE_SESSION_COOKIE_SECURE", "false") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 46 | .env("MALICE_ALLOW_HTTP_HOMESERVER", "true") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 47 | .stdout(Stdio::null()) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 48 | .stderr(Stdio::inherit()) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 49 | .spawn() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 50 | .expect("start Malice backend"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 51 | let mut server = Server(child); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 52 | tokio::time::timeout(Duration::from_secs(30), async { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 53 | loop { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 54 | if let Some(status) = server.0.try_wait().expect("poll backend") { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 55 | panic!("Malice exited before health check: {status}"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 56 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 57 | if client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 58 | .get(format!("http://127.0.0.1:{port}/api/health")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 59 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 60 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 61 | .is_ok_and(|response| response.status().is_success()) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 62 | { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 63 | break; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 64 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 65 | tokio::time::sleep(Duration::from_millis(100)).await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 66 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 67 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 68 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 69 | .expect("Malice did not start"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 70 | server |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 71 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 72 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 73 | fn unused_port() -> u16 { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 74 | TcpListener::bind("127.0.0.1:0") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 75 | .expect("bind ephemeral port") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 76 | .local_addr() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 77 | .expect("read ephemeral port") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 78 | .port() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 79 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 80 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 81 | #[tokio::test] |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 82 | async fn login_isolated_cookie_survives_restart_and_logout_revokes_it() { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 83 | let matrix = MatrixMockServer::new().await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 84 | matrix.mock_versions().ok().mount().await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 85 | matrix.mock_login().ok().expect(1).mount().await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 86 | let first_room = AtomicBool::new(false); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 87 | Mock::given(method("POST")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 88 | .and(path_regex("/sync$")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 89 | .respond_with(move |request: &wiremock::Request| { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 90 | let body: Value = serde_json::from_slice(&request.body).unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 91 | let rooms = |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 92 | if body["conn_id"] == "room-list" && !first_room.swap(true, Ordering::Relaxed) { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 93 | json!({"!malice:localhost": { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 94 | "initial": true, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 95 | "name": "Malice test room", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 96 | "joined_count": 1, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 97 | "required_state": [{ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 98 | "type": "m.room.member", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 99 | "state_key": "@cheeky_monkey:matrix.org", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 100 | "sender": "@cheeky_monkey:matrix.org", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 101 | "event_id": "$join:localhost", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 102 | "origin_server_ts": 1, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 103 | "content": {"membership": "join"} |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 104 | }], |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 105 | "timeline": [{ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 106 | "type": "m.room.message", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 107 | "sender": "@cheeky_monkey:matrix.org", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 108 | "event_id": "$recent:localhost", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 109 | "origin_server_ts": 2, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 110 | "content": {"msgtype":"m.text", "body":"recent channel message"} |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 111 | },{ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 112 | "type": "m.room.message", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 113 | "sender": "@cheeky_monkey:matrix.org", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 114 | "event_id": "$image:localhost", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 115 | "origin_server_ts": 3, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 116 | "content": { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 117 | "msgtype": "m.image", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 118 | "body": "image.png", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 119 | "url": "mxc://localhost/avatar", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 120 | "info": {"mimetype": "image/png", "size": 13} |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 121 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 122 | }], |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 123 | "prev_batch": "older-page" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 124 | }}) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 125 | } else { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 126 | json!({}) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 127 | }; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 128 | ResponseTemplate::new(200) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 129 | .set_body_json(json!({"pos":"1","lists":{},"rooms":rooms,"extensions":{}})) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 130 | .set_delay(Duration::from_millis(100)) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 131 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 132 | .mount(matrix.server()) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 133 | .await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 134 | Mock::given(method("GET")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 135 | .and(path_regex("/rooms/.*/messages$")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 136 | .respond_with(ResponseTemplate::new(200).set_body_json(json!({ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 137 | "start": "older-page", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 138 | "chunk": [{ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 139 | "type": "m.room.message", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 140 | "room_id": "!malice:localhost", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 141 | "sender": "@cheeky_monkey:matrix.org", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 142 | "event_id": "$older:localhost", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 143 | "origin_server_ts": 1, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 144 | "content": {"msgtype":"m.text", "body":"older channel message"} |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 145 | }] |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 146 | }))) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 147 | .mount(matrix.server()) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 148 | .await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 149 | matrix |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 150 | .mock_room_state_encryption() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 151 | .expect_access_token("abc123") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 152 | .plain() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 153 | .mount() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 154 | .await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 155 | matrix |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 156 | .mock_room_send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 157 | .expect_access_token("abc123") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 158 | .body_matches_partial_json(json!({"body":"backend room smoke"})) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 159 | .ok(matrix_sdk::ruma::event_id!("$sent:localhost")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 160 | .expect(1) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 161 | .mount() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 162 | .await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 163 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 164 | Mock::given(method("GET")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 165 | .and(path_regex("/download/[^/]+/avatar$")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 166 | .respond_with(ResponseTemplate::new(200).set_body_bytes(b"\x89PNG\r\n\x1a\nimage".to_vec())) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 167 | .mount(matrix.server()) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 168 | .await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 169 | Mock::given(method("GET")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 170 | .and(path_regex("/download/[^/]+/svg$")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 171 | .respond_with( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 172 | ResponseTemplate::new(200).set_body_bytes(b"<svg onload='alert(1)'/>".to_vec()), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 173 | ) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 174 | .mount(matrix.server()) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 175 | .await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 176 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 177 | let data_dir = tempfile::tempdir().expect("temporary user stores"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 178 | let client = Client::new(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 179 | let port = unused_port(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 180 | let base = format!("http://127.0.0.1:{port}"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 181 | let server = start(port, &data_dir, &client).await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 182 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 183 | let guest: Value = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 184 | .get(format!("{base}/api/session")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 185 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 186 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 187 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 188 | .json() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 189 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 190 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 191 | assert_eq!(guest["user_id"], Value::Null); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 192 | assert_eq!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 193 | client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 194 | .get(format!("{base}/api/rooms")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 195 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 196 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 197 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 198 | .status(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 199 | StatusCode::UNAUTHORIZED |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 200 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 201 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 202 | let cross_origin = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 203 | .post(format!("{base}/api/login")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 204 | .header(header::ORIGIN, "https://attacker.example") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 205 | .json(&json!({ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 206 | "homeserver": matrix.uri(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 207 | "username": "alice", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 208 | "password": "test-password" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 209 | })) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 210 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 211 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 212 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 213 | assert_eq!(cross_origin.status(), StatusCode::FORBIDDEN); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 214 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 215 | let cross_port = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 216 | .post(format!("{base}/api/login")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 217 | .header(header::ORIGIN, format!("http://127.0.0.1:{}", port + 1)) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 218 | .json(&json!({ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 219 | "homeserver": matrix.uri(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 220 | "username": "alice", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 221 | "password": "test-password" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 222 | })) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 223 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 224 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 225 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 226 | assert_eq!(cross_port.status(), StatusCode::FORBIDDEN); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 227 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 228 | let login = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 229 | .post(format!("{base}/api/login")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 230 | .header(header::ORIGIN, &base) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 231 | .json(&json!({ |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 232 | "homeserver": matrix.uri(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 233 | "username": "alice", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 234 | "password": "test-password" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 235 | })) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 236 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 237 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 238 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 239 | assert!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 240 | login.status().is_success(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 241 | "login returned {}", |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 242 | login.status() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 243 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 244 | let cookie = login |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 245 | .headers() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 246 | .get(header::SET_COOKIE) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 247 | .expect("opaque session cookie") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 248 | .to_str() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 249 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 250 | .split(';') |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 251 | .next() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 252 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 253 | .to_owned(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 254 | let logged_in: Value = login.json().await.unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 255 | assert!(logged_in["user_id"].as_str().is_some()); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 256 | #[cfg(unix)] |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 257 | { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 258 | use std::os::unix::fs::PermissionsExt; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 259 | let sid = cookie.split_once('=').unwrap().1; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 260 | let mode = |path: std::path::PathBuf| { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 261 | std::fs::metadata(path).unwrap().permissions().mode() & 0o777 |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 262 | }; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 263 | assert_eq!(mode(data_dir.path().join("master.key")), 0o600); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 264 | assert_eq!(mode(data_dir.path().join("sessions")), 0o700); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 265 | assert_eq!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 266 | mode(data_dir.path().join("sessions").join(format!("{sid}.bin"))), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 267 | 0o600 |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 268 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 269 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 270 | assert_eq!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 271 | client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 272 | .get(format!("{base}/api/rooms")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 273 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 274 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 275 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 276 | .status(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 277 | StatusCode::UNAUTHORIZED, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 278 | "one browser must not inherit another browser's Matrix session" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 279 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 280 | assert_eq!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 281 | client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 282 | .get(format!("{base}/api/media")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 283 | .query(&[("mxc", "mxc://localhost/avatar")]) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 284 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 285 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 286 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 287 | .status(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 288 | StatusCode::UNAUTHORIZED, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 289 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 290 | assert_eq!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 291 | client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 292 | .get(format!("{base}/api/media")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 293 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 294 | .query(&[("mxc", "not-a-mxc")]) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 295 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 296 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 297 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 298 | .status(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 299 | StatusCode::BAD_REQUEST, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 300 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 301 | let avatar = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 302 | .get(format!("{base}/api/media")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 303 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 304 | .query(&[("mxc", "mxc://localhost/avatar")]) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 305 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 306 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 307 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 308 | assert_eq!(avatar.status(), StatusCode::OK); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 309 | assert_eq!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 310 | avatar.headers().get(header::CONTENT_TYPE).unwrap(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 311 | "image/png" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 312 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 313 | assert_eq!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 314 | avatar.bytes().await.unwrap().as_ref(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 315 | b"\x89PNG\r\n\x1a\nimage" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 316 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 317 | assert_eq!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 318 | client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 319 | .get(format!("{base}/api/media")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 320 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 321 | .query(&[("mxc", "mxc://localhost/svg")]) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 322 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 323 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 324 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 325 | .status(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 326 | StatusCode::UNSUPPORTED_MEDIA_TYPE, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 327 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 328 | let rooms = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 329 | .get(format!("{base}/api/rooms")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 330 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 331 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 332 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 333 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 334 | assert!(rooms.status().is_success()); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 335 | let rooms: Value = rooms.json().await.unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 336 | assert!(rooms["rooms"].is_array()); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 337 | tokio::time::timeout(Duration::from_secs(5), async { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 338 | loop { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 339 | let response: Value = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 340 | .get(format!("{base}/api/rooms")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 341 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 342 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 343 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 344 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 345 | .json() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 346 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 347 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 348 | if response["rooms"] |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 349 | .as_array() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 350 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 351 | .iter() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 352 | .any(|room| room["room_id"] == "!malice:localhost") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 353 | { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 354 | break; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 355 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 356 | tokio::time::sleep(Duration::from_millis(50)).await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 357 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 358 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 359 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 360 | .expect("joined room appeared in live room list"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 361 | let open = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 362 | .post(format!("{base}/api/rooms/open")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 363 | .header(header::ORIGIN, &base) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 364 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 365 | .json(&json!({"room_id":"!malice:localhost"})) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 366 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 367 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 368 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 369 | assert_eq!(open.status(), StatusCode::NO_CONTENT); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 370 | let media_id = tokio::time::timeout(Duration::from_secs(10), async { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 371 | loop { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 372 | let mut stream = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 373 | .get(format!("{base}/api/events")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 374 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 375 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 376 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 377 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 378 | let frame = stream.chunk().await.unwrap().expect("SSE snapshot"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 379 | let text = std::str::from_utf8(&frame).unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 380 | let payload = text |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 381 | .lines() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 382 | .find_map(|line| line.strip_prefix("data: ")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 383 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 384 | let snapshot: Value = serde_json::from_str(payload).unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 385 | if let Some(id) = snapshot["timelines"]["!malice:localhost"] |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 386 | .as_array() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 387 | .and_then(|items| { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 388 | items.iter().find_map(|item| { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 389 | item["Event"]["content"]["Message"]["media"]["id"].as_str() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 390 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 391 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 392 | { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 393 | break id.to_owned(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 394 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 395 | tokio::time::sleep(Duration::from_millis(50)).await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 396 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 397 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 398 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 399 | .expect("opened room registered image attachment"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 400 | assert_eq!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 401 | client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 402 | .get(format!("{base}/api/message-media")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 403 | .query(&[("media_id", media_id.as_str()), ("size", "Full")]) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 404 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 405 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 406 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 407 | .status(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 408 | StatusCode::UNAUTHORIZED, |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 409 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 410 | let attachment = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 411 | .get(format!("{base}/api/message-media")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 412 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 413 | .query(&[("media_id", media_id.as_str()), ("size", "Full")]) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 414 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 415 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 416 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 417 | assert_eq!(attachment.status(), StatusCode::OK); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 418 | assert_eq!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 419 | attachment.headers().get(header::CONTENT_TYPE).unwrap(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 420 | "image/png" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 421 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 422 | assert_eq!( |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 423 | attachment.bytes().await.unwrap().as_ref(), |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 424 | b"\x89PNG\r\n\x1a\nimage" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 425 | ); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 426 | let sent = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 427 | .post(format!("{base}/api/rooms/send")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 428 | .header(header::ORIGIN, &base) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 429 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 430 | .json(&json!({"room_id":"!malice:localhost", "body":"backend room smoke"})) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 431 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 432 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 433 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 434 | assert_eq!(sent.status(), StatusCode::NO_CONTENT); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 435 | tokio::time::timeout(Duration::from_secs(5), async { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 436 | loop { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 437 | if matrix |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 438 | .received_requests() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 439 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 440 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 441 | .iter() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 442 | .any(|request| request.url.path().contains("/send/m.room.message/")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 443 | { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 444 | break; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 445 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 446 | tokio::time::sleep(Duration::from_millis(50)).await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 447 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 448 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 449 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 450 | .expect("message reached Matrix send endpoint"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 451 | tokio::time::timeout(Duration::from_secs(15), async { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 452 | loop { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 453 | let mut stream = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 454 | .get(format!("{base}/api/events")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 455 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 456 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 457 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 458 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 459 | let frame = stream.chunk().await.unwrap().expect("SSE snapshot"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 460 | let text = std::str::from_utf8(&frame).unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 461 | let payload = text |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 462 | .lines() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 463 | .find_map(|line| line.strip_prefix("data: ")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 464 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 465 | let snapshot: Value = serde_json::from_str(payload).unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 466 | if snapshot["connection"] == "online" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 467 | && snapshot["timelines"]["!malice:localhost"] |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 468 | .as_array() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 469 | .is_some_and(|items| { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 470 | items.iter().any(|item| { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 471 | item["Event"]["content"]["Message"]["body"] == "older channel message" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 472 | && item["Event"]["event_id"] == "$older:localhost" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 473 | }) && items.iter().any(|item| { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 474 | item["Event"]["content"]["Message"]["body"] == "backend room smoke" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 475 | && item["Event"]["event_id"] == "$sent:localhost" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 476 | && item["Event"]["send_state"] == "Sent" |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 477 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 478 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 479 | { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 480 | break; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 481 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 482 | tokio::time::sleep(Duration::from_millis(50)).await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 483 | } |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 484 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 485 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 486 | .expect("server-confirmed message appeared in online timeline SSE snapshot"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 487 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 488 | drop(server); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 489 | let _restored = start(port, &data_dir, &client).await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 490 | let session: Value = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 491 | .get(format!("{base}/api/session")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 492 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 493 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 494 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 495 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 496 | .json() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 497 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 498 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 499 | assert_eq!(session["user_id"], logged_in["user_id"]); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 500 | let mut events = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 501 | .get(format!("{base}/api/events")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 502 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 503 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 504 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 505 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 506 | assert_eq!(events.status(), StatusCode::OK); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 507 | let initial = events.chunk().await.unwrap().expect("initial SSE frame"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 508 | assert!(String::from_utf8_lossy(&initial).contains("\"type\":\"snapshot\"")); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 509 | |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 510 | matrix |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 511 | .mock_logout() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 512 | .expect_access_token("abc123") |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 513 | .ok() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 514 | .expect(1) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 515 | .mount() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 516 | .await; |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 517 | let logout = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 518 | .post(format!("{base}/api/logout")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 519 | .header(header::ORIGIN, &base) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 520 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 521 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 522 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 523 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 524 | assert_eq!(logout.status(), StatusCode::NO_CONTENT); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 525 | tokio::time::timeout(Duration::from_secs(2), async { |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 526 | while events.chunk().await.unwrap().is_some() {} |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 527 | }) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 528 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 529 | .expect("logout must close the authenticated event stream"); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 530 | let after_logout: Value = client |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 531 | .get(format!("{base}/api/session")) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 532 | .header(header::COOKIE, &cookie) |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 533 | .send() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 534 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 535 | .unwrap() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 536 | .json() |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 537 | .await |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 538 | .unwrap(); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 539 | assert_eq!(after_logout["user_id"], Value::Null); |
973f8fbdaf80 | atagen | 2026-09-28 09:10 UTC | 540 | } |