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.

fix: address PR review feedback for atproto-did

- DB error now returns ApiError envelope (consistent with all other routes)
- Add closed_db_pool_returns_500 test for the error path
- Add post_returns_405 test
- Move seed_handle helper to test_utils; remove duplicate from resolve_handle
- Add atproto_did.rs entry to relay CLAUDE.md routes table

authored by

Malpercio and committed by
Tangled
10f953e7 a16efffa

+56 -41
+1
crates/relay/CLAUDE.md
··· 61 61 | `oauth_authorize.rs` | `GET/POST /oauth/authorize` | 62 62 | `oauth_par.rs` | `POST /oauth/par` | 63 63 | `oauth_token.rs` | `POST /oauth/token` | 64 + | `atproto_did.rs` | `GET /.well-known/atproto-did` | 64 65 | `oauth_server_metadata.rs` | `GET /.well-known/oauth-authorization-server` | 65 66 | `oauth_jwks.rs` | `GET /oauth/jwks` | 66 67 | `oauth_templates.rs` | Pure HTML rendering helpers (Functional Core, no handler) |
+33 -22
crates/relay/src/routes/atproto_did.rs
··· 9 9 http::{header, StatusCode}, 10 10 response::{IntoResponse, Response}, 11 11 }; 12 + use common::{ApiError, ErrorCode}; 12 13 13 14 use crate::app::AppState; 14 15 ··· 28 29 Ok(row) => row, 29 30 Err(e) => { 30 31 tracing::error!(error = %e, handle = %handle, "DB error in well-known atproto-did"); 31 - return StatusCode::INTERNAL_SERVER_ERROR.into_response(); 32 + return ApiError::new(ErrorCode::InternalError, "handle lookup failed") 33 + .into_response(); 32 34 } 33 35 }; 34 36 ··· 52 54 use tower::ServiceExt; 53 55 54 56 use crate::app::{app, test_state}; 55 - 56 - async fn seed_handle(db: &sqlx::SqlitePool, handle: &str, did: &str) { 57 - sqlx::query( 58 - "INSERT INTO accounts (did, email, password_hash, created_at, updated_at) \ 59 - VALUES (?, ?, NULL, datetime('now'), datetime('now'))", 60 - ) 61 - .bind(did) 62 - .bind(format!("{did}@test.example.com")) 63 - .execute(db) 64 - .await 65 - .expect("insert account"); 66 - 67 - sqlx::query( 68 - "INSERT INTO handles (handle, did, created_at) VALUES (?, ?, datetime('now'))", 69 - ) 70 - .bind(handle) 71 - .bind(did) 72 - .execute(db) 73 - .await 74 - .expect("insert handle"); 75 - } 57 + use crate::routes::test_utils::seed_handle; 76 58 77 59 fn well_known_request(host: &str) -> Request<Body> { 78 60 Request::builder() ··· 145 127 .await 146 128 .unwrap(); 147 129 assert_eq!(std::str::from_utf8(&body).unwrap(), did); 130 + } 131 + 132 + #[tokio::test] 133 + async fn closed_db_pool_returns_500() { 134 + let state = test_state().await; 135 + state.db.close().await; 136 + 137 + let response = app(state) 138 + .oneshot(well_known_request("alice.example.com")) 139 + .await 140 + .unwrap(); 141 + 142 + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); 143 + } 144 + 145 + #[tokio::test] 146 + async fn post_returns_405() { 147 + let response = app(test_state().await) 148 + .oneshot( 149 + Request::builder() 150 + .method("POST") 151 + .uri("/.well-known/atproto-did") 152 + .body(Body::empty()) 153 + .unwrap(), 154 + ) 155 + .await 156 + .unwrap(); 157 + 158 + assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED); 148 159 } 149 160 }
+1 -19
crates/relay/src/routes/resolve_handle.rs
··· 92 92 93 93 use crate::app::{app, test_state, AppState}; 94 94 use crate::dns::{DnsError, TxtResolver}; 95 + use crate::routes::test_utils::seed_handle; 95 96 use crate::well_known::{WellKnownError, WellKnownResolver}; 96 97 97 98 // ── Test doubles ────────────────────────────────────────────────────────── ··· 180 181 )) 181 182 .body(Body::empty()) 182 183 .unwrap() 183 - } 184 - 185 - async fn seed_handle(db: &sqlx::SqlitePool, handle: &str, did: &str) { 186 - sqlx::query( 187 - "INSERT INTO accounts (did, email, password_hash, created_at, updated_at) \ 188 - VALUES (?, ?, NULL, datetime('now'), datetime('now'))", 189 - ) 190 - .bind(did) 191 - .bind(format!("{did}@test.example.com")) 192 - .execute(db) 193 - .await 194 - .expect("insert account"); 195 - 196 - sqlx::query("INSERT INTO handles (handle, did, created_at) VALUES (?, ?, datetime('now'))") 197 - .bind(handle) 198 - .bind(did) 199 - .execute(db) 200 - .await 201 - .expect("insert handle"); 202 184 } 203 185 204 186 // ── Local DB lookup ───────────────────────────────────────────────────────
+21
crates/relay/src/routes/test_utils.rs
··· 67 67 .unwrap(); 68 68 } 69 69 70 + /// Insert an account with NULL password_hash and a handle. Used for mobile/handle-only accounts 71 + /// in tests that don't exercise password authentication. 72 + pub async fn seed_handle(db: &sqlx::SqlitePool, handle: &str, did: &str) { 73 + sqlx::query( 74 + "INSERT INTO accounts (did, email, password_hash, created_at, updated_at) \ 75 + VALUES (?, ?, NULL, datetime('now'), datetime('now'))", 76 + ) 77 + .bind(did) 78 + .bind(format!("{did}@test.example.com")) 79 + .execute(db) 80 + .await 81 + .expect("insert account"); 82 + 83 + sqlx::query("INSERT INTO handles (handle, did, created_at) VALUES (?, ?, datetime('now'))") 84 + .bind(handle) 85 + .bind(did) 86 + .execute(db) 87 + .await 88 + .expect("insert handle"); 89 + } 90 + 70 91 /// Deserialise a response body as `serde_json::Value`, consuming the response. 71 92 pub async fn body_json(response: axum::response::Response) -> serde_json::Value { 72 93 let bytes = axum::body::to_bytes(response.into_body(), usize::MAX)