this repo has no description
1mod common; 2use common::*; 3use reqwest::StatusCode; 4use serde_json::json; 5use sqlx::PgPool; 6 7#[tokio::test] 8async fn test_request_plc_operation_signature_requires_auth() { 9 let client = client(); 10 let res = client 11 .post(format!( 12 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature", 13 base_url().await 14 )) 15 .send() 16 .await 17 .expect("Request failed"); 18 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 19} 20 21#[tokio::test] 22async fn test_request_plc_operation_signature_success() { 23 let client = client(); 24 let (token, _did) = create_account_and_login(&client).await; 25 let res = client 26 .post(format!( 27 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature", 28 base_url().await 29 )) 30 .bearer_auth(&token) 31 .send() 32 .await 33 .expect("Request failed"); 34 assert_eq!(res.status(), StatusCode::OK); 35} 36 37#[tokio::test] 38async fn test_sign_plc_operation_requires_auth() { 39 let client = client(); 40 let res = client 41 .post(format!( 42 "{}/xrpc/com.atproto.identity.signPlcOperation", 43 base_url().await 44 )) 45 .json(&json!({})) 46 .send() 47 .await 48 .expect("Request failed"); 49 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 50} 51 52#[tokio::test] 53async fn test_sign_plc_operation_requires_token() { 54 let client = client(); 55 let (token, _did) = create_account_and_login(&client).await; 56 let res = client 57 .post(format!( 58 "{}/xrpc/com.atproto.identity.signPlcOperation", 59 base_url().await 60 )) 61 .bearer_auth(&token) 62 .json(&json!({})) 63 .send() 64 .await 65 .expect("Request failed"); 66 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 67 let body: serde_json::Value = res.json().await.unwrap(); 68 assert_eq!(body["error"], "InvalidRequest"); 69} 70 71#[tokio::test] 72async fn test_sign_plc_operation_invalid_token() { 73 let client = client(); 74 let (token, _did) = create_account_and_login(&client).await; 75 let res = client 76 .post(format!( 77 "{}/xrpc/com.atproto.identity.signPlcOperation", 78 base_url().await 79 )) 80 .bearer_auth(&token) 81 .json(&json!({ 82 "token": "invalid-token-12345" 83 })) 84 .send() 85 .await 86 .expect("Request failed"); 87 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 88 let body: serde_json::Value = res.json().await.unwrap(); 89 assert!(body["error"] == "InvalidToken" || body["error"] == "ExpiredToken"); 90} 91 92#[tokio::test] 93async fn test_submit_plc_operation_requires_auth() { 94 let client = client(); 95 let res = client 96 .post(format!( 97 "{}/xrpc/com.atproto.identity.submitPlcOperation", 98 base_url().await 99 )) 100 .json(&json!({ 101 "operation": {} 102 })) 103 .send() 104 .await 105 .expect("Request failed"); 106 assert_eq!(res.status(), StatusCode::UNAUTHORIZED); 107} 108 109#[tokio::test] 110async fn test_submit_plc_operation_invalid_operation() { 111 let client = client(); 112 let (token, _did) = create_account_and_login(&client).await; 113 let res = client 114 .post(format!( 115 "{}/xrpc/com.atproto.identity.submitPlcOperation", 116 base_url().await 117 )) 118 .bearer_auth(&token) 119 .json(&json!({ 120 "operation": { 121 "type": "invalid_type" 122 } 123 })) 124 .send() 125 .await 126 .expect("Request failed"); 127 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 128 let body: serde_json::Value = res.json().await.unwrap(); 129 assert_eq!(body["error"], "InvalidRequest"); 130} 131 132#[tokio::test] 133async fn test_submit_plc_operation_missing_sig() { 134 let client = client(); 135 let (token, _did) = create_account_and_login(&client).await; 136 let res = client 137 .post(format!( 138 "{}/xrpc/com.atproto.identity.submitPlcOperation", 139 base_url().await 140 )) 141 .bearer_auth(&token) 142 .json(&json!({ 143 "operation": { 144 "type": "plc_operation", 145 "rotationKeys": [], 146 "verificationMethods": {}, 147 "alsoKnownAs": [], 148 "services": {}, 149 "prev": null 150 } 151 })) 152 .send() 153 .await 154 .expect("Request failed"); 155 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 156 let body: serde_json::Value = res.json().await.unwrap(); 157 assert_eq!(body["error"], "InvalidRequest"); 158} 159 160#[tokio::test] 161async fn test_submit_plc_operation_wrong_service_endpoint() { 162 let client = client(); 163 let (token, _did) = create_account_and_login(&client).await; 164 let res = client 165 .post(format!( 166 "{}/xrpc/com.atproto.identity.submitPlcOperation", 167 base_url().await 168 )) 169 .bearer_auth(&token) 170 .json(&json!({ 171 "operation": { 172 "type": "plc_operation", 173 "rotationKeys": ["did:key:z123"], 174 "verificationMethods": {"atproto": "did:key:z456"}, 175 "alsoKnownAs": ["at://wrong.handle"], 176 "services": { 177 "atproto_pds": { 178 "type": "AtprotoPersonalDataServer", 179 "endpoint": "https://wrong.example.com" 180 } 181 }, 182 "prev": null, 183 "sig": "fake_signature" 184 } 185 })) 186 .send() 187 .await 188 .expect("Request failed"); 189 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 190} 191 192#[tokio::test] 193async fn test_request_plc_operation_creates_token_in_db() { 194 let client = client(); 195 let (token, did) = create_account_and_login(&client).await; 196 let res = client 197 .post(format!( 198 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature", 199 base_url().await 200 )) 201 .bearer_auth(&token) 202 .send() 203 .await 204 .expect("Request failed"); 205 assert_eq!(res.status(), StatusCode::OK); 206 let db_url = get_db_connection_string().await; 207 let pool = PgPool::connect(&db_url).await.expect("DB connect failed"); 208 let row = sqlx::query!( 209 r#" 210 SELECT t.token, t.expires_at 211 FROM plc_operation_tokens t 212 JOIN users u ON t.user_id = u.id 213 WHERE u.did = $1 214 "#, 215 did 216 ) 217 .fetch_optional(&pool) 218 .await 219 .expect("Query failed"); 220 assert!(row.is_some(), "PLC token should be created in database"); 221 let row = row.unwrap(); 222 assert!( 223 row.token.len() == 11, 224 "Token should be in format xxxxx-xxxxx" 225 ); 226 assert!(row.token.contains('-'), "Token should contain hyphen"); 227 assert!( 228 row.expires_at > chrono::Utc::now(), 229 "Token should not be expired" 230 ); 231} 232 233#[tokio::test] 234async fn test_request_plc_operation_replaces_existing_token() { 235 let client = client(); 236 let (token, did) = create_account_and_login(&client).await; 237 let res1 = client 238 .post(format!( 239 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature", 240 base_url().await 241 )) 242 .bearer_auth(&token) 243 .send() 244 .await 245 .expect("Request 1 failed"); 246 assert_eq!(res1.status(), StatusCode::OK); 247 let db_url = get_db_connection_string().await; 248 let pool = PgPool::connect(&db_url).await.expect("DB connect failed"); 249 let token1 = sqlx::query_scalar!( 250 r#" 251 SELECT t.token 252 FROM plc_operation_tokens t 253 JOIN users u ON t.user_id = u.id 254 WHERE u.did = $1 255 "#, 256 did 257 ) 258 .fetch_one(&pool) 259 .await 260 .expect("Query failed"); 261 let res2 = client 262 .post(format!( 263 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature", 264 base_url().await 265 )) 266 .bearer_auth(&token) 267 .send() 268 .await 269 .expect("Request 2 failed"); 270 assert_eq!(res2.status(), StatusCode::OK); 271 let token2 = sqlx::query_scalar!( 272 r#" 273 SELECT t.token 274 FROM plc_operation_tokens t 275 JOIN users u ON t.user_id = u.id 276 WHERE u.did = $1 277 "#, 278 did 279 ) 280 .fetch_one(&pool) 281 .await 282 .expect("Query failed"); 283 assert_ne!(token1, token2, "Second request should generate a new token"); 284 let count: i64 = sqlx::query_scalar!( 285 r#" 286 SELECT COUNT(*) as "count!" 287 FROM plc_operation_tokens t 288 JOIN users u ON t.user_id = u.id 289 WHERE u.did = $1 290 "#, 291 did 292 ) 293 .fetch_one(&pool) 294 .await 295 .expect("Count query failed"); 296 assert_eq!(count, 1, "Should only have one token per user"); 297} 298 299#[tokio::test] 300async fn test_submit_plc_operation_wrong_verification_method() { 301 let client = client(); 302 let (token, did) = create_account_and_login(&client).await; 303 let hostname = 304 std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| format!("127.0.0.1:{}", app_port())); 305 let handle = did.split(':').last().unwrap_or("user"); 306 let res = client 307 .post(format!( 308 "{}/xrpc/com.atproto.identity.submitPlcOperation", 309 base_url().await 310 )) 311 .bearer_auth(&token) 312 .json(&json!({ 313 "operation": { 314 "type": "plc_operation", 315 "rotationKeys": ["did:key:zWrongRotationKey123"], 316 "verificationMethods": {"atproto": "did:key:zWrongVerificationKey456"}, 317 "alsoKnownAs": [format!("at://{}", handle)], 318 "services": { 319 "atproto_pds": { 320 "type": "AtprotoPersonalDataServer", 321 "endpoint": format!("https://{}", hostname) 322 } 323 }, 324 "prev": null, 325 "sig": "fake_signature" 326 } 327 })) 328 .send() 329 .await 330 .expect("Request failed"); 331 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 332 let body: serde_json::Value = res.json().await.unwrap(); 333 assert_eq!(body["error"], "InvalidRequest"); 334 assert!( 335 body["message"] 336 .as_str() 337 .unwrap_or("") 338 .contains("signing key") 339 || body["message"].as_str().unwrap_or("").contains("rotation"), 340 "Error should mention key mismatch: {:?}", 341 body 342 ); 343} 344 345#[tokio::test] 346async fn test_submit_plc_operation_wrong_handle() { 347 let client = client(); 348 let (token, _did) = create_account_and_login(&client).await; 349 let hostname = 350 std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| format!("127.0.0.1:{}", app_port())); 351 let res = client 352 .post(format!( 353 "{}/xrpc/com.atproto.identity.submitPlcOperation", 354 base_url().await 355 )) 356 .bearer_auth(&token) 357 .json(&json!({ 358 "operation": { 359 "type": "plc_operation", 360 "rotationKeys": ["did:key:z123"], 361 "verificationMethods": {"atproto": "did:key:z456"}, 362 "alsoKnownAs": ["at://totally.wrong.handle"], 363 "services": { 364 "atproto_pds": { 365 "type": "AtprotoPersonalDataServer", 366 "endpoint": format!("https://{}", hostname) 367 } 368 }, 369 "prev": null, 370 "sig": "fake_signature" 371 } 372 })) 373 .send() 374 .await 375 .expect("Request failed"); 376 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 377 let body: serde_json::Value = res.json().await.unwrap(); 378 assert_eq!(body["error"], "InvalidRequest"); 379} 380 381#[tokio::test] 382async fn test_submit_plc_operation_wrong_service_type() { 383 let client = client(); 384 let (token, _did) = create_account_and_login(&client).await; 385 let hostname = 386 std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| format!("127.0.0.1:{}", app_port())); 387 let res = client 388 .post(format!( 389 "{}/xrpc/com.atproto.identity.submitPlcOperation", 390 base_url().await 391 )) 392 .bearer_auth(&token) 393 .json(&json!({ 394 "operation": { 395 "type": "plc_operation", 396 "rotationKeys": ["did:key:z123"], 397 "verificationMethods": {"atproto": "did:key:z456"}, 398 "alsoKnownAs": ["at://user"], 399 "services": { 400 "atproto_pds": { 401 "type": "WrongServiceType", 402 "endpoint": format!("https://{}", hostname) 403 } 404 }, 405 "prev": null, 406 "sig": "fake_signature" 407 } 408 })) 409 .send() 410 .await 411 .expect("Request failed"); 412 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 413 let body: serde_json::Value = res.json().await.unwrap(); 414 assert_eq!(body["error"], "InvalidRequest"); 415} 416 417#[tokio::test] 418async fn test_plc_token_expiry_format() { 419 let client = client(); 420 let (token, did) = create_account_and_login(&client).await; 421 let res = client 422 .post(format!( 423 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature", 424 base_url().await 425 )) 426 .bearer_auth(&token) 427 .send() 428 .await 429 .expect("Request failed"); 430 assert_eq!(res.status(), StatusCode::OK); 431 let db_url = get_db_connection_string().await; 432 let pool = PgPool::connect(&db_url).await.expect("DB connect failed"); 433 let row = sqlx::query!( 434 r#" 435 SELECT t.expires_at 436 FROM plc_operation_tokens t 437 JOIN users u ON t.user_id = u.id 438 WHERE u.did = $1 439 "#, 440 did 441 ) 442 .fetch_one(&pool) 443 .await 444 .expect("Query failed"); 445 let now = chrono::Utc::now(); 446 let expires = row.expires_at; 447 let diff = expires - now; 448 assert!( 449 diff.num_minutes() >= 9, 450 "Token should expire in ~10 minutes, got {} minutes", 451 diff.num_minutes() 452 ); 453 assert!( 454 diff.num_minutes() <= 11, 455 "Token should expire in ~10 minutes, got {} minutes", 456 diff.num_minutes() 457 ); 458}