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!(row.token.len() == 11, "Token should be in format xxxxx-xxxxx"); 223 assert!(row.token.contains('-'), "Token should contain hyphen"); 224 assert!(row.expires_at > chrono::Utc::now(), "Token should not be expired"); 225} 226 227#[tokio::test] 228async fn test_request_plc_operation_replaces_existing_token() { 229 let client = client(); 230 let (token, did) = create_account_and_login(&client).await; 231 let res1 = client 232 .post(format!( 233 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature", 234 base_url().await 235 )) 236 .bearer_auth(&token) 237 .send() 238 .await 239 .expect("Request 1 failed"); 240 assert_eq!(res1.status(), StatusCode::OK); 241 let db_url = get_db_connection_string().await; 242 let pool = PgPool::connect(&db_url).await.expect("DB connect failed"); 243 let token1 = sqlx::query_scalar!( 244 r#" 245 SELECT t.token 246 FROM plc_operation_tokens t 247 JOIN users u ON t.user_id = u.id 248 WHERE u.did = $1 249 "#, 250 did 251 ) 252 .fetch_one(&pool) 253 .await 254 .expect("Query failed"); 255 let res2 = client 256 .post(format!( 257 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature", 258 base_url().await 259 )) 260 .bearer_auth(&token) 261 .send() 262 .await 263 .expect("Request 2 failed"); 264 assert_eq!(res2.status(), StatusCode::OK); 265 let token2 = sqlx::query_scalar!( 266 r#" 267 SELECT t.token 268 FROM plc_operation_tokens t 269 JOIN users u ON t.user_id = u.id 270 WHERE u.did = $1 271 "#, 272 did 273 ) 274 .fetch_one(&pool) 275 .await 276 .expect("Query failed"); 277 assert_ne!(token1, token2, "Second request should generate a new token"); 278 let count: i64 = sqlx::query_scalar!( 279 r#" 280 SELECT COUNT(*) as "count!" 281 FROM plc_operation_tokens t 282 JOIN users u ON t.user_id = u.id 283 WHERE u.did = $1 284 "#, 285 did 286 ) 287 .fetch_one(&pool) 288 .await 289 .expect("Count query failed"); 290 assert_eq!(count, 1, "Should only have one token per user"); 291} 292 293#[tokio::test] 294async fn test_submit_plc_operation_wrong_verification_method() { 295 let client = client(); 296 let (token, did) = create_account_and_login(&client).await; 297 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| { 298 format!("127.0.0.1:{}", app_port()) 299 }); 300 let handle = did.split(':').last().unwrap_or("user"); 301 let res = client 302 .post(format!( 303 "{}/xrpc/com.atproto.identity.submitPlcOperation", 304 base_url().await 305 )) 306 .bearer_auth(&token) 307 .json(&json!({ 308 "operation": { 309 "type": "plc_operation", 310 "rotationKeys": ["did:key:zWrongRotationKey123"], 311 "verificationMethods": {"atproto": "did:key:zWrongVerificationKey456"}, 312 "alsoKnownAs": [format!("at://{}", handle)], 313 "services": { 314 "atproto_pds": { 315 "type": "AtprotoPersonalDataServer", 316 "endpoint": format!("https://{}", hostname) 317 } 318 }, 319 "prev": null, 320 "sig": "fake_signature" 321 } 322 })) 323 .send() 324 .await 325 .expect("Request failed"); 326 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 327 let body: serde_json::Value = res.json().await.unwrap(); 328 assert_eq!(body["error"], "InvalidRequest"); 329 assert!( 330 body["message"].as_str().unwrap_or("").contains("signing key") || 331 body["message"].as_str().unwrap_or("").contains("rotation"), 332 "Error should mention key mismatch: {:?}", 333 body 334 ); 335} 336 337#[tokio::test] 338async fn test_submit_plc_operation_wrong_handle() { 339 let client = client(); 340 let (token, _did) = create_account_and_login(&client).await; 341 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| { 342 format!("127.0.0.1:{}", app_port()) 343 }); 344 let res = client 345 .post(format!( 346 "{}/xrpc/com.atproto.identity.submitPlcOperation", 347 base_url().await 348 )) 349 .bearer_auth(&token) 350 .json(&json!({ 351 "operation": { 352 "type": "plc_operation", 353 "rotationKeys": ["did:key:z123"], 354 "verificationMethods": {"atproto": "did:key:z456"}, 355 "alsoKnownAs": ["at://totally.wrong.handle"], 356 "services": { 357 "atproto_pds": { 358 "type": "AtprotoPersonalDataServer", 359 "endpoint": format!("https://{}", hostname) 360 } 361 }, 362 "prev": null, 363 "sig": "fake_signature" 364 } 365 })) 366 .send() 367 .await 368 .expect("Request failed"); 369 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 370 let body: serde_json::Value = res.json().await.unwrap(); 371 assert_eq!(body["error"], "InvalidRequest"); 372} 373 374#[tokio::test] 375async fn test_submit_plc_operation_wrong_service_type() { 376 let client = client(); 377 let (token, _did) = create_account_and_login(&client).await; 378 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| { 379 format!("127.0.0.1:{}", app_port()) 380 }); 381 let res = client 382 .post(format!( 383 "{}/xrpc/com.atproto.identity.submitPlcOperation", 384 base_url().await 385 )) 386 .bearer_auth(&token) 387 .json(&json!({ 388 "operation": { 389 "type": "plc_operation", 390 "rotationKeys": ["did:key:z123"], 391 "verificationMethods": {"atproto": "did:key:z456"}, 392 "alsoKnownAs": ["at://user"], 393 "services": { 394 "atproto_pds": { 395 "type": "WrongServiceType", 396 "endpoint": format!("https://{}", hostname) 397 } 398 }, 399 "prev": null, 400 "sig": "fake_signature" 401 } 402 })) 403 .send() 404 .await 405 .expect("Request failed"); 406 assert_eq!(res.status(), StatusCode::BAD_REQUEST); 407 let body: serde_json::Value = res.json().await.unwrap(); 408 assert_eq!(body["error"], "InvalidRequest"); 409} 410 411#[tokio::test] 412async fn test_plc_token_expiry_format() { 413 let client = client(); 414 let (token, did) = create_account_and_login(&client).await; 415 let res = client 416 .post(format!( 417 "{}/xrpc/com.atproto.identity.requestPlcOperationSignature", 418 base_url().await 419 )) 420 .bearer_auth(&token) 421 .send() 422 .await 423 .expect("Request failed"); 424 assert_eq!(res.status(), StatusCode::OK); 425 let db_url = get_db_connection_string().await; 426 let pool = PgPool::connect(&db_url).await.expect("DB connect failed"); 427 let row = sqlx::query!( 428 r#" 429 SELECT t.expires_at 430 FROM plc_operation_tokens t 431 JOIN users u ON t.user_id = u.id 432 WHERE u.did = $1 433 "#, 434 did 435 ) 436 .fetch_one(&pool) 437 .await 438 .expect("Query failed"); 439 let now = chrono::Utc::now(); 440 let expires = row.expires_at; 441 let diff = expires - now; 442 assert!(diff.num_minutes() >= 9, "Token should expire in ~10 minutes, got {} minutes", diff.num_minutes()); 443 assert!(diff.num_minutes() <= 11, "Token should expire in ~10 minutes, got {} minutes", diff.num_minutes()); 444}