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_plc_operation_auth() {
9 let client = client();
10 let res = client.post(format!("{}/xrpc/com.atproto.identity.requestPlcOperationSignature", base_url().await))
11 .send().await.unwrap();
12 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
13 let res = client.post(format!("{}/xrpc/com.atproto.identity.signPlcOperation", base_url().await))
14 .json(&json!({})).send().await.unwrap();
15 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
16 let res = client.post(format!("{}/xrpc/com.atproto.identity.submitPlcOperation", base_url().await))
17 .json(&json!({ "operation": {} })).send().await.unwrap();
18 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
19 let (token, _) = create_account_and_login(&client).await;
20 let res = client.post(format!("{}/xrpc/com.atproto.identity.requestPlcOperationSignature", base_url().await))
21 .bearer_auth(&token).send().await.unwrap();
22 assert_eq!(res.status(), StatusCode::OK);
23}
24
25#[tokio::test]
26async fn test_sign_plc_operation_validation() {
27 let client = client();
28 let (token, _) = create_account_and_login(&client).await;
29 let res = client.post(format!("{}/xrpc/com.atproto.identity.signPlcOperation", base_url().await))
30 .bearer_auth(&token).json(&json!({})).send().await.unwrap();
31 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
32 let body: serde_json::Value = res.json().await.unwrap();
33 assert_eq!(body["error"], "InvalidRequest");
34 let res = client.post(format!("{}/xrpc/com.atproto.identity.signPlcOperation", base_url().await))
35 .bearer_auth(&token).json(&json!({ "token": "invalid-token-12345" })).send().await.unwrap();
36 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
37 let body: serde_json::Value = res.json().await.unwrap();
38 assert!(body["error"] == "InvalidToken" || body["error"] == "ExpiredToken");
39}
40
41#[tokio::test]
42async fn test_submit_plc_operation_validation() {
43 let client = client();
44 let (token, did) = create_account_and_login(&client).await;
45 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| format!("127.0.0.1:{}", app_port()));
46 let res = client.post(format!("{}/xrpc/com.atproto.identity.submitPlcOperation", base_url().await))
47 .bearer_auth(&token).json(&json!({ "operation": { "type": "invalid_type" } })).send().await.unwrap();
48 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
49 let body: serde_json::Value = res.json().await.unwrap();
50 assert_eq!(body["error"], "InvalidRequest");
51 let res = client.post(format!("{}/xrpc/com.atproto.identity.submitPlcOperation", base_url().await))
52 .bearer_auth(&token).json(&json!({
53 "operation": { "type": "plc_operation", "rotationKeys": [], "verificationMethods": {},
54 "alsoKnownAs": [], "services": {}, "prev": null }
55 })).send().await.unwrap();
56 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
57 let handle = did.split(':').last().unwrap_or("user");
58 let res = client.post(format!("{}/xrpc/com.atproto.identity.submitPlcOperation", base_url().await))
59 .bearer_auth(&token).json(&json!({
60 "operation": { "type": "plc_operation", "rotationKeys": ["did:key:z123"],
61 "verificationMethods": { "atproto": "did:key:z456" },
62 "alsoKnownAs": [format!("at://{}", handle)],
63 "services": { "atproto_pds": { "type": "AtprotoPersonalDataServer", "endpoint": "https://wrong.example.com" } },
64 "prev": null, "sig": "fake_signature" }
65 })).send().await.unwrap();
66 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
67 let res = client.post(format!("{}/xrpc/com.atproto.identity.submitPlcOperation", base_url().await))
68 .bearer_auth(&token).json(&json!({
69 "operation": { "type": "plc_operation", "rotationKeys": ["did:key:zWrongRotationKey123"],
70 "verificationMethods": { "atproto": "did:key:zWrongVerificationKey456" },
71 "alsoKnownAs": [format!("at://{}", handle)],
72 "services": { "atproto_pds": { "type": "AtprotoPersonalDataServer", "endpoint": format!("https://{}", hostname) } },
73 "prev": null, "sig": "fake_signature" }
74 })).send().await.unwrap();
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 assert!(body["message"].as_str().unwrap_or("").contains("signing key") || body["message"].as_str().unwrap_or("").contains("rotation"));
79 let res = client.post(format!("{}/xrpc/com.atproto.identity.submitPlcOperation", base_url().await))
80 .bearer_auth(&token).json(&json!({
81 "operation": { "type": "plc_operation", "rotationKeys": ["did:key:z123"],
82 "verificationMethods": { "atproto": "did:key:z456" },
83 "alsoKnownAs": ["at://totally.wrong.handle"],
84 "services": { "atproto_pds": { "type": "AtprotoPersonalDataServer", "endpoint": format!("https://{}", hostname) } },
85 "prev": null, "sig": "fake_signature" }
86 })).send().await.unwrap();
87 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
88 let res = client.post(format!("{}/xrpc/com.atproto.identity.submitPlcOperation", base_url().await))
89 .bearer_auth(&token).json(&json!({
90 "operation": { "type": "plc_operation", "rotationKeys": ["did:key:z123"],
91 "verificationMethods": { "atproto": "did:key:z456" },
92 "alsoKnownAs": ["at://user"],
93 "services": { "atproto_pds": { "type": "WrongServiceType", "endpoint": format!("https://{}", hostname) } },
94 "prev": null, "sig": "fake_signature" }
95 })).send().await.unwrap();
96 assert_eq!(res.status(), StatusCode::BAD_REQUEST);
97}
98
99#[tokio::test]
100async fn test_plc_token_lifecycle() {
101 let client = client();
102 let (token, did) = create_account_and_login(&client).await;
103 let res = client.post(format!("{}/xrpc/com.atproto.identity.requestPlcOperationSignature", base_url().await))
104 .bearer_auth(&token).send().await.unwrap();
105 assert_eq!(res.status(), StatusCode::OK);
106 let db_url = get_db_connection_string().await;
107 let pool = PgPool::connect(&db_url).await.unwrap();
108 let row = sqlx::query!(
109 "SELECT t.token, t.expires_at FROM plc_operation_tokens t JOIN users u ON t.user_id = u.id WHERE u.did = $1",
110 did
111 ).fetch_optional(&pool).await.unwrap();
112 assert!(row.is_some(), "PLC token should be created in database");
113 let row = row.unwrap();
114 assert_eq!(row.token.len(), 11, "Token should be in format xxxxx-xxxxx");
115 assert!(row.token.contains('-'), "Token should contain hyphen");
116 assert!(row.expires_at > chrono::Utc::now(), "Token should not be expired");
117 let diff = row.expires_at - chrono::Utc::now();
118 assert!(diff.num_minutes() >= 9 && diff.num_minutes() <= 11, "Token should expire in ~10 minutes");
119 let token1 = row.token.clone();
120 let res = client.post(format!("{}/xrpc/com.atproto.identity.requestPlcOperationSignature", base_url().await))
121 .bearer_auth(&token).send().await.unwrap();
122 assert_eq!(res.status(), StatusCode::OK);
123 let token2 = sqlx::query_scalar!(
124 "SELECT t.token FROM plc_operation_tokens t JOIN users u ON t.user_id = u.id WHERE u.did = $1", did
125 ).fetch_one(&pool).await.unwrap();
126 assert_ne!(token1, token2, "Second request should generate a new token");
127 let count: i64 = sqlx::query_scalar!(
128 "SELECT COUNT(*) as \"count!\" FROM plc_operation_tokens t JOIN users u ON t.user_id = u.id WHERE u.did = $1", did
129 ).fetch_one(&pool).await.unwrap();
130 assert_eq!(count, 1, "Should only have one token per user");
131}