An easy-to-host PDS on the ATProtocol, iPhone and MacOS. Maintain control of your keys and data, always.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

test: add missing test coverage for pds_client

- AC3.3 resolve_handle orchestration: test with nonexistent handle that fails both DNS and HTTP
- AC3.3 error serialization: test HandleNotFound serializes with code "HANDLE_NOT_FOUND"
- AC3.7 error serialization: test DidNotFound serializes with code "DID_NOT_FOUND"
- AC3.8 error serialization: test PdsUnreachable serializes with code "PDS_UNREACHABLE" and does NOT include reason field
- XRPC get_recommended_did_credentials error: test 403 error response returns NetworkError

All tests pass with sandbox disabled for httpmock.

authored by

Malpercio and committed by
Tangled
f16cb6d9 70395968

+98
+98
apps/identity-wallet/src-tauri/src/pds_client.rs
··· 1563 1563 assert!(creds.rotation_keys.is_some()); 1564 1564 assert!(creds.also_known_as.is_some()); 1565 1565 } 1566 + 1567 + /// AC3.3 resolve_handle orchestration: handle fails both DNS and HTTP 1568 + /// This test uses a nonexistent .test TLD which DNS will reject, then attempts HTTP 1569 + /// which will fail due to inability to connect. Both failures result in HandleNotFound. 1570 + #[tokio::test] 1571 + async fn test_resolve_handle_orchestration_nonexistent() { 1572 + let client = PdsClient::new(); 1573 + // Use a nonexistent handle on .test TLD (reserved, non-routable domain) 1574 + // DNS will fail (no records found) and HTTP to https://.../.well-known/atproto-did 1575 + // will fail (unable to resolve/connect). Both failures return HandleNotFound. 1576 + let result = client 1577 + .resolve_handle("this-handle-definitely-does-not-exist-12345.test") 1578 + .await; 1579 + 1580 + assert!(result.is_err()); 1581 + // The error could be HandleNotFound if DNS+HTTP both fail, or NetworkError 1582 + // if the HTTP request itself fails before we can determine there's no record. 1583 + // We accept both as evidence that the handle cannot be resolved. 1584 + match result.unwrap_err() { 1585 + PdsClientError::HandleNotFound | PdsClientError::NetworkError { .. } => { 1586 + // Expected: either no handle found or network failure during resolution 1587 + } 1588 + e => panic!("Expected HandleNotFound or NetworkError, got: {:?}", e), 1589 + } 1590 + } 1591 + 1592 + /// AC3.3 error serialization: HandleNotFound serializes with code "HANDLE_NOT_FOUND" 1593 + #[test] 1594 + fn test_pds_client_error_handle_not_found_serialization() { 1595 + let error = PdsClientError::HandleNotFound; 1596 + let json = serde_json::to_string(&error).expect("serialization failed"); 1597 + assert!(json.contains("\"code\":\"HANDLE_NOT_FOUND\"")); 1598 + } 1599 + 1600 + /// AC3.7 error serialization: DidNotFound serializes with code "DID_NOT_FOUND" 1601 + #[test] 1602 + fn test_pds_client_error_did_not_found_serialization() { 1603 + let error = PdsClientError::DidNotFound; 1604 + let json = serde_json::to_string(&error).expect("serialization failed"); 1605 + assert!(json.contains("\"code\":\"DID_NOT_FOUND\"")); 1606 + } 1607 + 1608 + /// AC3.8 error serialization: PdsUnreachable serializes with code "PDS_UNREACHABLE" 1609 + /// and does NOT include "reason" (because it's #[serde(skip)]) 1610 + #[test] 1611 + fn test_pds_client_error_pds_unreachable_serialization() { 1612 + let error = PdsClientError::PdsUnreachable { 1613 + reason: "test".into(), 1614 + }; 1615 + let json = serde_json::to_string(&error).expect("serialization failed"); 1616 + assert!(json.contains("\"code\":\"PDS_UNREACHABLE\"")); 1617 + // Verify "reason" field is NOT serialized (it's #[serde(skip)]) 1618 + assert!(!json.contains("\"reason\"")); 1619 + assert!(!json.contains("test")); 1620 + } 1621 + 1622 + /// XRPC get_recommended_did_credentials error: returns NetworkError on 403 1623 + #[tokio::test] 1624 + async fn test_get_recommended_did_credentials_error() { 1625 + use std::sync::{Arc, Mutex}; 1626 + 1627 + let mock_server = MockServer::start(); 1628 + 1629 + mock_server.mock(|when, then| { 1630 + when.method(httpmock::Method::GET) 1631 + .path("/xrpc/com.atproto.identity.getRecommendedDidCredentials"); 1632 + then.status(403).json_body(serde_json::json!({ 1633 + "error": "Forbidden" 1634 + })); 1635 + }); 1636 + 1637 + let session = Arc::new(Mutex::new(crate::oauth::OAuthSession { 1638 + access_token: "test_access_token".to_string(), 1639 + refresh_token: "test_refresh_token".to_string(), 1640 + expires_at: std::time::SystemTime::now() 1641 + .duration_since(std::time::UNIX_EPOCH) 1642 + .unwrap() 1643 + .as_secs() 1644 + + 3600, 1645 + dpop_nonce: None, 1646 + })); 1647 + 1648 + let keypair = crate::oauth::DPoPKeypair::get_or_create().expect("keypair must exist"); 1649 + let oauth_client = crate::oauth_client::OAuthClient::new_for_test( 1650 + keypair, 1651 + session, 1652 + mock_server.base_url(), 1653 + ); 1654 + 1655 + let result = get_recommended_did_credentials(&oauth_client).await; 1656 + assert!(result.is_err()); 1657 + match result.unwrap_err() { 1658 + PdsClientError::NetworkError { .. } => { 1659 + // Expected 1660 + } 1661 + e => panic!("Expected NetworkError, got: {:?}", e), 1662 + } 1663 + } 1566 1664 }