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