I've been saying "PDSes seem easy enough, they're what, some CRUD to a db? I can do that in my sleep". well i'm sleeping rn so let's go
at main 12 kB view raw
1mod common; 2mod helpers; 3use common::*; 4use helpers::*; 5use reqwest::StatusCode; 6use serde_json::Value; 7 8#[tokio::test] 9async fn test_get_head_comprehensive() { 10 let client = client(); 11 let (did, jwt) = setup_new_user("gethead").await; 12 let res = client 13 .get(format!( 14 "{}/xrpc/com.atproto.sync.getHead", 15 base_url().await 16 )) 17 .query(&[("did", did.as_str())]) 18 .send() 19 .await 20 .expect("Failed to send request"); 21 assert_eq!(res.status(), StatusCode::OK); 22 let body: Value = res.json().await.expect("Response was not valid JSON"); 23 assert!(body["root"].is_string()); 24 let root1 = body["root"].as_str().unwrap().to_string(); 25 assert!(root1.starts_with("bafy"), "Root CID should be a CID"); 26 let latest_res = client 27 .get(format!( 28 "{}/xrpc/com.atproto.sync.getLatestCommit", 29 base_url().await 30 )) 31 .query(&[("did", did.as_str())]) 32 .send() 33 .await 34 .expect("Failed to get latest commit"); 35 let latest_body: Value = latest_res.json().await.unwrap(); 36 let latest_cid = latest_body["cid"].as_str().unwrap(); 37 assert_eq!( 38 root1, latest_cid, 39 "getHead root should match getLatestCommit cid" 40 ); 41 create_post(&client, &did, &jwt, "Post to change head").await; 42 let res2 = client 43 .get(format!( 44 "{}/xrpc/com.atproto.sync.getHead", 45 base_url().await 46 )) 47 .query(&[("did", did.as_str())]) 48 .send() 49 .await 50 .expect("Failed to get head after record"); 51 let body2: Value = res2.json().await.unwrap(); 52 let root2 = body2["root"].as_str().unwrap().to_string(); 53 assert_ne!(root1, root2, "Head CID should change after record creation"); 54 let not_found_res = client 55 .get(format!( 56 "{}/xrpc/com.atproto.sync.getHead", 57 base_url().await 58 )) 59 .query(&[("did", "did:plc:nonexistent12345")]) 60 .send() 61 .await 62 .expect("Failed to send request"); 63 assert_eq!(not_found_res.status(), StatusCode::BAD_REQUEST); 64 let error_body: Value = not_found_res.json().await.unwrap(); 65 assert_eq!(error_body["error"], "RepoNotFound"); 66 let missing_res = client 67 .get(format!( 68 "{}/xrpc/com.atproto.sync.getHead", 69 base_url().await 70 )) 71 .send() 72 .await 73 .expect("Failed to send request"); 74 assert_eq!(missing_res.status(), StatusCode::BAD_REQUEST); 75 let empty_res = client 76 .get(format!( 77 "{}/xrpc/com.atproto.sync.getHead", 78 base_url().await 79 )) 80 .query(&[("did", "")]) 81 .send() 82 .await 83 .expect("Failed to send request"); 84 assert_eq!(empty_res.status(), StatusCode::BAD_REQUEST); 85 let whitespace_res = client 86 .get(format!( 87 "{}/xrpc/com.atproto.sync.getHead", 88 base_url().await 89 )) 90 .query(&[("did", " ")]) 91 .send() 92 .await 93 .expect("Failed to send request"); 94 assert_eq!(whitespace_res.status(), StatusCode::BAD_REQUEST); 95} 96 97#[tokio::test] 98async fn test_get_checkout_comprehensive() { 99 let client = client(); 100 let (did, jwt) = setup_new_user("getcheckout").await; 101 let empty_res = client 102 .get(format!( 103 "{}/xrpc/com.atproto.sync.getCheckout", 104 base_url().await 105 )) 106 .query(&[("did", did.as_str())]) 107 .send() 108 .await 109 .expect("Failed to send request"); 110 assert_eq!(empty_res.status(), StatusCode::OK); 111 let empty_body = empty_res.bytes().await.expect("Failed to get body"); 112 assert!( 113 !empty_body.is_empty(), 114 "Even empty repo should return CAR header" 115 ); 116 create_post(&client, &did, &jwt, "Post for checkout test").await; 117 let res = client 118 .get(format!( 119 "{}/xrpc/com.atproto.sync.getCheckout", 120 base_url().await 121 )) 122 .query(&[("did", did.as_str())]) 123 .send() 124 .await 125 .expect("Failed to send request"); 126 assert_eq!(res.status(), StatusCode::OK); 127 assert_eq!( 128 res.headers() 129 .get("content-type") 130 .and_then(|h| h.to_str().ok()), 131 Some("application/vnd.ipld.car") 132 ); 133 let body = res.bytes().await.expect("Failed to get body"); 134 assert!(!body.is_empty(), "CAR file should not be empty"); 135 assert!(body.len() > 50, "CAR file should contain actual data"); 136 assert!( 137 body.len() >= 2, 138 "CAR file should have at least header length" 139 ); 140 for i in 0..4 { 141 tokio::time::sleep(std::time::Duration::from_millis(100)).await; 142 create_post(&client, &did, &jwt, &format!("Checkout post {}", i)).await; 143 } 144 let multi_res = client 145 .get(format!( 146 "{}/xrpc/com.atproto.sync.getCheckout", 147 base_url().await 148 )) 149 .query(&[("did", did.as_str())]) 150 .send() 151 .await 152 .expect("Failed to send request"); 153 assert_eq!(multi_res.status(), StatusCode::OK); 154 let multi_body = multi_res.bytes().await.expect("Failed to get body"); 155 assert!( 156 multi_body.len() > 500, 157 "CAR file with 5 records should be larger" 158 ); 159 let not_found_res = client 160 .get(format!( 161 "{}/xrpc/com.atproto.sync.getCheckout", 162 base_url().await 163 )) 164 .query(&[("did", "did:plc:nonexistent12345")]) 165 .send() 166 .await 167 .expect("Failed to send request"); 168 assert_eq!(not_found_res.status(), StatusCode::BAD_REQUEST); 169 let error_body: Value = not_found_res.json().await.unwrap(); 170 assert_eq!(error_body["error"], "RepoNotFound"); 171 let missing_res = client 172 .get(format!( 173 "{}/xrpc/com.atproto.sync.getCheckout", 174 base_url().await 175 )) 176 .send() 177 .await 178 .expect("Failed to send request"); 179 assert_eq!(missing_res.status(), StatusCode::BAD_REQUEST); 180 let empty_did_res = client 181 .get(format!( 182 "{}/xrpc/com.atproto.sync.getCheckout", 183 base_url().await 184 )) 185 .query(&[("did", "")]) 186 .send() 187 .await 188 .expect("Failed to send request"); 189 assert_eq!(empty_did_res.status(), StatusCode::BAD_REQUEST); 190} 191 192#[tokio::test] 193async fn test_get_head_deactivated_account_returns_error() { 194 let client = client(); 195 let base = base_url().await; 196 let (did, jwt) = setup_new_user("deactheadtest").await; 197 let res = client 198 .get(format!("{}/xrpc/com.atproto.sync.getHead", base)) 199 .query(&[("did", did.as_str())]) 200 .send() 201 .await 202 .unwrap(); 203 assert_eq!(res.status(), StatusCode::OK); 204 client 205 .post(format!( 206 "{}/xrpc/com.atproto.server.deactivateAccount", 207 base 208 )) 209 .bearer_auth(&jwt) 210 .json(&serde_json::json!({})) 211 .send() 212 .await 213 .unwrap(); 214 let deact_res = client 215 .get(format!("{}/xrpc/com.atproto.sync.getHead", base)) 216 .query(&[("did", did.as_str())]) 217 .send() 218 .await 219 .unwrap(); 220 assert_eq!(deact_res.status(), StatusCode::BAD_REQUEST); 221 let body: Value = deact_res.json().await.unwrap(); 222 assert_eq!(body["error"], "RepoDeactivated"); 223} 224 225#[tokio::test] 226async fn test_get_head_takendown_account_returns_error() { 227 let client = client(); 228 let base = base_url().await; 229 let (admin_jwt, _) = create_admin_account_and_login(&client).await; 230 let (_, target_did) = create_account_and_login(&client).await; 231 let res = client 232 .get(format!("{}/xrpc/com.atproto.sync.getHead", base)) 233 .query(&[("did", target_did.as_str())]) 234 .send() 235 .await 236 .unwrap(); 237 assert_eq!(res.status(), StatusCode::OK); 238 client 239 .post(format!( 240 "{}/xrpc/com.atproto.admin.updateSubjectStatus", 241 base 242 )) 243 .bearer_auth(&admin_jwt) 244 .json(&serde_json::json!({ 245 "subject": { 246 "$type": "com.atproto.admin.defs#repoRef", 247 "did": target_did 248 }, 249 "takedown": { 250 "applied": true, 251 "ref": "test-takedown" 252 } 253 })) 254 .send() 255 .await 256 .unwrap(); 257 let takedown_res = client 258 .get(format!("{}/xrpc/com.atproto.sync.getHead", base)) 259 .query(&[("did", target_did.as_str())]) 260 .send() 261 .await 262 .unwrap(); 263 assert_eq!(takedown_res.status(), StatusCode::BAD_REQUEST); 264 let body: Value = takedown_res.json().await.unwrap(); 265 assert_eq!(body["error"], "RepoTakendown"); 266} 267 268#[tokio::test] 269async fn test_get_head_admin_can_access_deactivated() { 270 let client = client(); 271 let base = base_url().await; 272 let (admin_jwt, _) = create_admin_account_and_login(&client).await; 273 let (user_jwt, did) = create_account_and_login(&client).await; 274 client 275 .post(format!( 276 "{}/xrpc/com.atproto.server.deactivateAccount", 277 base 278 )) 279 .bearer_auth(&user_jwt) 280 .json(&serde_json::json!({})) 281 .send() 282 .await 283 .unwrap(); 284 let res = client 285 .get(format!("{}/xrpc/com.atproto.sync.getHead", base)) 286 .bearer_auth(&admin_jwt) 287 .query(&[("did", did.as_str())]) 288 .send() 289 .await 290 .unwrap(); 291 assert_eq!(res.status(), StatusCode::OK); 292} 293 294#[tokio::test] 295async fn test_get_checkout_deactivated_account_returns_error() { 296 let client = client(); 297 let base = base_url().await; 298 let (did, jwt) = setup_new_user("deactcheckouttest").await; 299 let res = client 300 .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base)) 301 .query(&[("did", did.as_str())]) 302 .send() 303 .await 304 .unwrap(); 305 assert_eq!(res.status(), StatusCode::OK); 306 client 307 .post(format!( 308 "{}/xrpc/com.atproto.server.deactivateAccount", 309 base 310 )) 311 .bearer_auth(&jwt) 312 .json(&serde_json::json!({})) 313 .send() 314 .await 315 .unwrap(); 316 let deact_res = client 317 .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base)) 318 .query(&[("did", did.as_str())]) 319 .send() 320 .await 321 .unwrap(); 322 assert_eq!(deact_res.status(), StatusCode::BAD_REQUEST); 323 let body: Value = deact_res.json().await.unwrap(); 324 assert_eq!(body["error"], "RepoDeactivated"); 325} 326 327#[tokio::test] 328async fn test_get_checkout_takendown_account_returns_error() { 329 let client = client(); 330 let base = base_url().await; 331 let (admin_jwt, _) = create_admin_account_and_login(&client).await; 332 let (_, target_did) = create_account_and_login(&client).await; 333 let res = client 334 .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base)) 335 .query(&[("did", target_did.as_str())]) 336 .send() 337 .await 338 .unwrap(); 339 assert_eq!(res.status(), StatusCode::OK); 340 client 341 .post(format!( 342 "{}/xrpc/com.atproto.admin.updateSubjectStatus", 343 base 344 )) 345 .bearer_auth(&admin_jwt) 346 .json(&serde_json::json!({ 347 "subject": { 348 "$type": "com.atproto.admin.defs#repoRef", 349 "did": target_did 350 }, 351 "takedown": { 352 "applied": true, 353 "ref": "test-takedown" 354 } 355 })) 356 .send() 357 .await 358 .unwrap(); 359 let takedown_res = client 360 .get(format!("{}/xrpc/com.atproto.sync.getCheckout", base)) 361 .query(&[("did", target_did.as_str())]) 362 .send() 363 .await 364 .unwrap(); 365 assert_eq!(takedown_res.status(), StatusCode::BAD_REQUEST); 366 let body: Value = takedown_res.json().await.unwrap(); 367 assert_eq!(body["error"], "RepoTakendown"); 368}