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}