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