mod common; use common::*; use reqwest::StatusCode; use serde_json::{Value, json}; use wiremock::matchers::{method, path}; use wiremock::{Mock, MockServer, ResponseTemplate}; #[tokio::test] async fn test_resolve_handle_success() { let client = client(); let short_handle = format!("resolvetest_{}", uuid::Uuid::new_v4()); let payload = json!({ "handle": short_handle, "email": format!("{}@example.com", short_handle), "password": "Testpass123!" }); let res = client .post(format!( "{}/xrpc/com.atproto.server.createAccount", base_url().await )) .json(&payload) .send() .await .expect("Failed to create account"); assert_eq!(res.status(), StatusCode::OK); let body: Value = res.json().await.expect("Invalid JSON"); let did = body["did"].as_str().expect("No DID").to_string(); let full_handle = body["handle"] .as_str() .expect("No handle in response") .to_string(); let params = [("handle", full_handle.as_str())]; let res = client .get(format!( "{}/xrpc/com.atproto.identity.resolveHandle", base_url().await )) .query(¶ms) .send() .await .expect("Failed to send request"); assert_eq!(res.status(), StatusCode::OK); let body: Value = res.json().await.expect("Response was not valid JSON"); assert_eq!(body["did"], did); } #[tokio::test] async fn test_resolve_handle_not_found() { let client = client(); let params = [("handle", "nonexistent_handle_12345")]; let res = client .get(format!( "{}/xrpc/com.atproto.identity.resolveHandle", base_url().await )) .query(¶ms) .send() .await .expect("Failed to send request"); assert_eq!(res.status(), StatusCode::NOT_FOUND); let body: Value = res.json().await.expect("Response was not valid JSON"); assert_eq!(body["error"], "HandleNotFound"); } #[tokio::test] async fn test_resolve_handle_missing_param() { let client = client(); let res = client .get(format!( "{}/xrpc/com.atproto.identity.resolveHandle", base_url().await )) .send() .await .expect("Failed to send request"); assert_eq!(res.status(), StatusCode::BAD_REQUEST); } #[tokio::test] async fn test_well_known_did() { let client = client(); let res = client .get(format!("{}/.well-known/did.json", base_url().await)) .send() .await .expect("Failed to send request"); assert_eq!(res.status(), StatusCode::OK); let body: Value = res.json().await.expect("Response was not valid JSON"); assert!(body["id"].as_str().unwrap().starts_with("did:web:")); assert_eq!(body["service"][0]["type"], "AtprotoPersonalDataServer"); } #[tokio::test] async fn test_create_did_web_account_and_resolve() { let client = client(); let mock_server = MockServer::start().await; let mock_uri = mock_server.uri(); let mock_addr = mock_uri.trim_start_matches("http://"); let did = format!("did:web:{}", mock_addr.replace(":", "%3A")); let handle = format!("webuser_{}", uuid::Uuid::new_v4()); let pds_endpoint = base_url().await.replace("http://", "https://"); let reserve_res = client .post(format!( "{}/xrpc/com.atproto.server.reserveSigningKey", base_url().await )) .json(&json!({ "did": did })) .send() .await .expect("Failed to reserve signing key"); assert_eq!(reserve_res.status(), StatusCode::OK); let reserve_body: Value = reserve_res.json().await.expect("Response was not JSON"); let signing_key = reserve_body["signingKey"] .as_str() .expect("No signingKey returned"); let public_key_multibase = signing_key .strip_prefix("did:key:") .expect("signingKey should start with did:key:"); let did_doc = json!({ "@context": ["https://www.w3.org/ns/did/v1"], "id": did, "verificationMethod": [{ "id": format!("{}#atproto", did), "type": "Multikey", "controller": did, "publicKeyMultibase": public_key_multibase }], "service": [{ "id": "#atproto_pds", "type": "AtprotoPersonalDataServer", "serviceEndpoint": pds_endpoint }] }); Mock::given(method("GET")) .and(path("/.well-known/did.json")) .respond_with(ResponseTemplate::new(200).set_body_json(did_doc)) .mount(&mock_server) .await; let payload = json!({ "handle": handle, "email": format!("{}@example.com", handle), "password": "Testpass123!", "did": did, "signingKey": signing_key }); let res = client .post(format!( "{}/xrpc/com.atproto.server.createAccount", base_url().await )) .json(&payload) .send() .await .expect("Failed to send request"); if res.status() != StatusCode::OK { let status = res.status(); let body: Value = res .json() .await .unwrap_or(json!({"error": "could not parse body"})); panic!("createAccount failed with status {}: {:?}", status, body); } let body: Value = res .json() .await .expect("createAccount response was not JSON"); assert_eq!(body["did"], did); let res = client .get(format!("{}/u/{}/did.json", base_url().await, handle)) .send() .await .expect("Failed to fetch DID doc"); assert_eq!( res.status(), StatusCode::NOT_FOUND, "External did:web should not have DID doc served by PDS (user hosts their own)" ); } #[tokio::test] async fn test_create_account_duplicate_handle() { let client = client(); let handle = format!("dupe_{}", uuid::Uuid::new_v4()); let email = format!("{}@example.com", handle); let payload = json!({ "handle": handle, "email": email, "password": "Testpass123!" }); let res = client .post(format!( "{}/xrpc/com.atproto.server.createAccount", base_url().await )) .json(&payload) .send() .await .expect("Failed to send request"); assert_eq!(res.status(), StatusCode::OK); let res = client .post(format!( "{}/xrpc/com.atproto.server.createAccount", base_url().await )) .json(&payload) .send() .await .expect("Failed to send request"); assert_eq!(res.status(), StatusCode::BAD_REQUEST); let body: Value = res.json().await.expect("Response was not JSON"); assert_eq!(body["error"], "HandleTaken"); } #[tokio::test] async fn test_did_web_lifecycle() { let client = client(); let mock_server = MockServer::start().await; let mock_uri = mock_server.uri(); let mock_addr = mock_uri.trim_start_matches("http://"); let handle = format!("lifecycle_{}", uuid::Uuid::new_v4()); let did = format!("did:web:{}:u:{}", mock_addr.replace(":", "%3A"), handle); let email = format!("{}@test.com", handle); let pds_endpoint = base_url().await.replace("http://", "https://"); let reserve_res = client .post(format!( "{}/xrpc/com.atproto.server.reserveSigningKey", base_url().await )) .json(&json!({ "did": did })) .send() .await .expect("Failed to reserve signing key"); assert_eq!(reserve_res.status(), StatusCode::OK); let reserve_body: Value = reserve_res.json().await.expect("Response was not JSON"); let signing_key = reserve_body["signingKey"] .as_str() .expect("No signingKey returned"); let public_key_multibase = signing_key .strip_prefix("did:key:") .expect("signingKey should start with did:key:"); let did_doc = json!({ "@context": ["https://www.w3.org/ns/did/v1"], "id": did, "verificationMethod": [{ "id": format!("{}#atproto", did), "type": "Multikey", "controller": did, "publicKeyMultibase": public_key_multibase }], "service": [{ "id": "#atproto_pds", "type": "AtprotoPersonalDataServer", "serviceEndpoint": pds_endpoint }] }); Mock::given(method("GET")) .and(path(format!("/u/{}/did.json", handle))) .respond_with(ResponseTemplate::new(200).set_body_json(did_doc)) .mount(&mock_server) .await; let create_payload = json!({ "handle": handle, "email": email, "password": "Testpass123!", "did": did, "signingKey": signing_key }); let res = client .post(format!( "{}/xrpc/com.atproto.server.createAccount", base_url().await )) .json(&create_payload) .send() .await .expect("Failed createAccount"); if res.status() != StatusCode::OK { let body: Value = res.json().await.unwrap(); println!("createAccount failed: {:?}", body); panic!("createAccount returned non-200"); } assert_eq!(res.status(), StatusCode::OK); let create_body: Value = res.json().await.expect("Not JSON"); assert_eq!(create_body["did"], did); let _jwt = verify_new_account(&client, &did).await; /* let profile_payload = json!({ "repo": did, "collection": "app.bsky.actor.profile", "rkey": "self", "record": { "$type": "app.bsky.actor.profile", "displayName": "DID Web User", "description": "Testing lifecycle" } }); let res = client.post(format!("{}/xrpc/com.atproto.repo.putRecord", base_url().await)) .bearer_auth(_jwt) .json(&profile_payload) .send() .await .expect("Failed putRecord"); if res.status() != StatusCode::OK { let body: Value = res.json().await.unwrap(); println!("putRecord failed: {:?}", body); panic!("putRecord returned non-200"); } assert_eq!(res.status(), StatusCode::OK); let res = client.get(format!("{}/xrpc/com.atproto.repo.getRecord", base_url().await)) .query(&[ ("repo", &handle), ("collection", &"app.bsky.actor.profile".to_string()), ("rkey", &"self".to_string()) ]) .send() .await .expect("Failed getRecord"); if res.status() != StatusCode::OK { let body: Value = res.json().await.unwrap(); println!("getRecord failed: {:?}", body); panic!("getRecord returned non-200"); } let record_body: Value = res.json().await.expect("Not JSON"); assert_eq!(record_body["value"]["displayName"], "DID Web User"); */ } #[tokio::test] async fn test_get_recommended_did_credentials_success() { let client = client(); let (access_jwt, _) = create_account_and_login(&client).await; let res = client .get(format!( "{}/xrpc/com.atproto.identity.getRecommendedDidCredentials", base_url().await )) .bearer_auth(&access_jwt) .send() .await .expect("Failed to send request"); assert_eq!(res.status(), StatusCode::OK); let body: Value = res.json().await.expect("Response was not valid JSON"); assert!(body["rotationKeys"].is_array()); assert!(body["alsoKnownAs"].is_array()); assert!(body["verificationMethods"].is_object()); assert!(body["services"].is_object()); let rotation_keys = body["rotationKeys"].as_array().unwrap(); assert!(!rotation_keys.is_empty()); assert!(rotation_keys[0].as_str().unwrap().starts_with("did:key:")); let also_known_as = body["alsoKnownAs"].as_array().unwrap(); assert!(!also_known_as.is_empty()); assert!(also_known_as[0].as_str().unwrap().starts_with("at://")); assert!(body["verificationMethods"]["atproto"].is_string()); assert_eq!( body["services"]["atproto_pds"]["type"], "AtprotoPersonalDataServer" ); assert!(body["services"]["atproto_pds"]["endpoint"].is_string()); } #[tokio::test] async fn test_get_recommended_did_credentials_no_auth() { let client = client(); let res = client .get(format!( "{}/xrpc/com.atproto.identity.getRecommendedDidCredentials", base_url().await )) .send() .await .expect("Failed to send request"); assert_eq!(res.status(), StatusCode::UNAUTHORIZED); let body: Value = res.json().await.expect("Response was not valid JSON"); assert_eq!(body["error"], "AuthenticationRequired"); }