bitwarden_test/play/
http_client.rs1use reqwest::{Client, RequestBuilder, Response};
4use serde::{Serialize, de::DeserializeOwned};
5use tracing::debug;
6
7use super::{PlayConfig, PlayError, PlayResult};
8
9#[derive(Debug, Clone)]
11pub(crate) struct PlayHttpClient {
12 client: Client,
13 play_id: String,
14 config: PlayConfig,
15}
16
17impl PlayHttpClient {
18 pub(crate) fn new(play_id: String, config: PlayConfig) -> Self {
20 let client = Client::builder()
21 .build()
22 .expect("Failed to build HTTP client");
23
24 Self {
25 client,
26 play_id,
27 config,
28 }
29 }
30
31 pub(crate) fn play_id(&self) -> &str {
33 &self.play_id
34 }
35
36 pub(crate) fn config(&self) -> &PlayConfig {
38 &self.config
39 }
40
41 fn with_play_id(&self, builder: RequestBuilder) -> RequestBuilder {
43 builder.header("x-play-id", &self.play_id)
44 }
45
46 pub(crate) async fn post_seeder<T: Serialize, R: DeserializeOwned>(
48 &self,
49 path: &str,
50 body: &T,
51 ) -> PlayResult<R> {
52 let url = format!("{}{}", self.config.seeder_url, path);
53
54 debug!(
55 method = "POST",
56 url = %url,
57 play_id = %self.play_id,
58 body = ?serde_json::to_string(body).ok(),
59 "Play request"
60 );
61
62 let response = self
63 .with_play_id(self.client.post(&url))
64 .json(body)
65 .send()
66 .await?;
67
68 self.handle_json_response(response).await
69 }
70
71 pub(crate) async fn delete_seeder(&self, path: &str) -> PlayResult<()> {
73 let url = format!("{}{}", self.config.seeder_url, path);
74
75 debug!(
76 method = "DELETE",
77 url = %url,
78 play_id = %self.play_id,
79 "Play request"
80 );
81
82 let response = self.with_play_id(self.client.delete(&url)).send().await?;
83
84 let status = response.status();
85 debug!(status = %status, "Play response");
86
87 if status.is_success() {
88 Ok(())
89 } else {
90 let body = response.text().await.unwrap_or_default();
91 debug!(body = %body, "Play error response body");
92 Err(PlayError::ServerError {
93 status: status.as_u16(),
94 body,
95 })
96 }
97 }
98
99 async fn handle_json_response<R: DeserializeOwned>(&self, response: Response) -> PlayResult<R> {
101 let status = response.status();
102
103 if status.is_success() {
104 let body = response.text().await?;
105 debug!(status = %status, body = %body, "Play response");
106 Ok(serde_json::from_str(&body)?)
107 } else {
108 let body = response.text().await.unwrap_or_default();
109 debug!(status = %status, body = %body, "Play error response");
110 Err(PlayError::ServerError {
111 status: status.as_u16(),
112 body,
113 })
114 }
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use wiremock::{
121 Mock, MockServer, ResponseTemplate,
122 matchers::{header, method, path},
123 };
124
125 use super::*;
126
127 fn create_test_config(seeder_url: &str) -> PlayConfig {
128 PlayConfig::new(
129 "https://api.example.com",
130 "https://identity.example.com",
131 seeder_url,
132 )
133 }
134
135 #[test]
136 fn test_new_stores_play_id_and_config() {
137 let config = create_test_config("http://localhost:5047");
138 let client = PlayHttpClient::new("test-play-id".to_string(), config);
139
140 assert_eq!(client.play_id(), "test-play-id");
141 assert_eq!(client.config().api_url, "https://api.example.com");
142 assert_eq!(client.config().identity_url, "https://identity.example.com");
143 assert_eq!(client.config().seeder_url, "http://localhost:5047");
144 }
145
146 #[tokio::test]
147 async fn test_post_seeder() {
148 let mock_server = MockServer::start().await;
149
150 Mock::given(method("POST"))
151 .and(path("/seed/"))
152 .and(header("x-play-id", "test-play-id"))
153 .respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
154 "id": 42,
155 "name": "test-user"
156 })))
157 .expect(1)
158 .mount(&mock_server)
159 .await;
160
161 let config = create_test_config(&mock_server.uri());
162 let client = PlayHttpClient::new("test-play-id".to_string(), config);
163
164 #[derive(serde::Deserialize, Debug, PartialEq)]
165 struct TestResponse {
166 id: i32,
167 name: String,
168 }
169
170 let result: TestResponse = client
171 .post_seeder("/seed/", &serde_json::json!({}))
172 .await
173 .unwrap();
174
175 assert_eq!(result.id, 42);
176 assert_eq!(result.name, "test-user");
177 }
178
179 #[tokio::test]
180 async fn test_post_seeder_handles_server_error() {
181 let mock_server = MockServer::start().await;
182
183 Mock::given(method("POST"))
184 .and(path("/seed/"))
185 .respond_with(ResponseTemplate::new(500).set_body_string("Internal Server Error"))
186 .mount(&mock_server)
187 .await;
188
189 let config = create_test_config(&mock_server.uri());
190 let client = PlayHttpClient::new("test-id".to_string(), config);
191
192 let result: PlayResult<serde_json::Value> =
193 client.post_seeder("/seed/", &serde_json::json!({})).await;
194
195 match result {
196 Err(PlayError::ServerError { status, body }) => {
197 assert_eq!(status, 500);
198 assert_eq!(body, "Internal Server Error");
199 }
200 _ => panic!("Expected ServerError"),
201 }
202 }
203
204 #[tokio::test]
205 async fn test_delete_seeder_sends_correct_request() {
206 let mock_server = MockServer::start().await;
207
208 Mock::given(method("DELETE"))
209 .and(path("/seed/test-play-id"))
210 .and(header("x-play-id", "test-play-id"))
211 .respond_with(ResponseTemplate::new(200))
212 .expect(1)
213 .mount(&mock_server)
214 .await;
215
216 let config = create_test_config(&mock_server.uri());
217 let client = PlayHttpClient::new("test-play-id".to_string(), config);
218
219 let result = client.delete_seeder("/seed/test-play-id").await;
220
221 assert!(result.is_ok());
222 }
223
224 #[tokio::test]
225 async fn test_delete_seeder_handles_server_error() {
226 let mock_server = MockServer::start().await;
227
228 Mock::given(method("DELETE"))
229 .and(path("/seed/test-id"))
230 .respond_with(ResponseTemplate::new(404).set_body_string("Not found"))
231 .mount(&mock_server)
232 .await;
233
234 let config = create_test_config(&mock_server.uri());
235 let client = PlayHttpClient::new("test-id".to_string(), config);
236
237 let result = client.delete_seeder("/seed/test-id").await;
238
239 match result {
240 Err(PlayError::ServerError { status, body }) => {
241 assert_eq!(status, 404);
242 assert_eq!(body, "Not found");
243 }
244 _ => panic!("Expected ServerError"),
245 }
246 }
247}