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}