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}