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