1use axum::{Json, Router, response::IntoResponse};
2use axum_test::TestServer;
3use jacquard_axum::{ExtractXrpc, IntoRouter};
4use jacquard_common::types::string::Did;
5use serde::{Deserialize, Serialize};
6use std::collections::BTreeMap;
7
8// Mock XRPC endpoint for testing
9#[derive(Debug, Clone, Serialize, Deserialize)]
10struct TestQueryRequest<'a> {
11 #[serde(borrow)]
12 did: Did<'a>,
13 #[serde(default)]
14 limit: Option<u32>,
15}
16
17impl jacquard::IntoStatic for TestQueryRequest<'_> {
18 type Output = TestQueryRequest<'static>;
19
20 fn into_static(self) -> Self::Output {
21 TestQueryRequest {
22 did: self.did.into_static(),
23 limit: self.limit,
24 }
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
29struct TestQueryResponse<'a> {
30 #[serde(borrow)]
31 did: Did<'a>,
32 #[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
33 extra_data: BTreeMap<String, serde_json::Value>,
34}
35
36impl jacquard::IntoStatic for TestQueryResponse<'_> {
37 type Output = TestQueryResponse<'static>;
38
39 fn into_static(self) -> Self::Output {
40 TestQueryResponse {
41 did: self.did.into_static(),
42 extra_data: self.extra_data,
43 }
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, thiserror::Error)]
48#[error("test error")]
49struct TestError;
50
51impl jacquard::IntoStatic for TestError {
52 type Output = TestError;
53
54 fn into_static(self) -> Self::Output {
55 self
56 }
57}
58
59impl jacquard::xrpc::XrpcResp for TestQueryResponse<'_> {
60 const NSID: &'static str = "com.example.test.query";
61 const ENCODING: &'static str = "application/json";
62 type Output<'a> = TestQueryResponse<'a>;
63 type Err<'a> = TestError;
64}
65
66impl jacquard::xrpc::XrpcRequest for TestQueryRequest<'_> {
67 const NSID: &'static str = "com.example.test.query";
68 const METHOD: jacquard::xrpc::XrpcMethod = jacquard::xrpc::XrpcMethod::Query;
69 type Response = TestQueryResponse<'static>;
70}
71
72impl jacquard::xrpc::XrpcEndpoint for TestQueryRequest<'_> {
73 const PATH: &'static str = "/xrpc/com.example.test.query";
74 const METHOD: jacquard::xrpc::XrpcMethod = jacquard::xrpc::XrpcMethod::Query;
75 type Request<'a> = TestQueryRequest<'a>;
76 type Response = TestQueryResponse<'static>;
77}
78
79async fn test_handler(ExtractXrpc(req): ExtractXrpc<TestQueryRequest<'_>>) -> impl IntoResponse {
80 Json(TestQueryResponse {
81 did: req.did,
82 extra_data: BTreeMap::new(),
83 })
84}
85
86#[tokio::test]
87async fn test_url_encoded_did_in_query_params() {
88 let app = Router::new().merge(TestQueryRequest::into_router(test_handler));
89
90 let server = TestServer::new(app).unwrap();
91
92 // Test with URL-encoded DID (colons should be encoded as %3A)
93 let response = server
94 .get("/xrpc/com.example.test.query?did=did%3Aplc%3A123abc")
95 .await;
96
97 response.assert_status_ok();
98
99 let body_text = response.text();
100 println!("URL-encoded test response: {}", body_text);
101 let body: TestQueryResponse = serde_json::from_str(&body_text).unwrap();
102 println!("Parsed DID: {}", body.did.as_str());
103 assert_eq!(body.did.as_str(), "did:plc:123abc");
104}
105
106#[tokio::test]
107async fn test_unencoded_did_in_query_params() {
108 let app = Router::new().merge(TestQueryRequest::into_router(test_handler));
109
110 let server = TestServer::new(app).unwrap();
111
112 // Test with unencoded DID (some clients might send it unencoded)
113 let response = server
114 .get("/xrpc/com.example.test.query?did=did:plc:123abc")
115 .await;
116
117 response.assert_status_ok();
118
119 let body_text = response.text();
120 println!("Unencoded test response: {}", body_text);
121 let body: TestQueryResponse = serde_json::from_str(&body_text).unwrap();
122 println!("Parsed DID: {}", body.did.as_str());
123 assert_eq!(body.did.as_str(), "did:plc:123abc");
124}
125
126#[tokio::test]
127async fn test_multiple_params_with_encoded_did() {
128 let app = Router::new().merge(TestQueryRequest::into_router(test_handler));
129
130 let server = TestServer::new(app).unwrap();
131
132 // Test with multiple params including URL-encoded DID
133 let response = server
134 .get("/xrpc/com.example.test.query?did=did%3Aweb%3Aexample.com&limit=50")
135 .await;
136
137 response.assert_status_ok();
138
139 let body_text = response.text();
140 println!("Multiple params test response: {}", body_text);
141 let body: TestQueryResponse = serde_json::from_str(&body_text).unwrap();
142 println!("Parsed DID: {}", body.did.as_str());
143 assert_eq!(body.did.as_str(), "did:web:example.com");
144}