spikard 0.5.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +674 -674
- data/ext/spikard_rb/Cargo.toml +17 -17
- data/ext/spikard_rb/extconf.rb +13 -10
- data/ext/spikard_rb/src/lib.rs +6 -6
- data/lib/spikard/app.rb +405 -405
- data/lib/spikard/background.rb +27 -27
- data/lib/spikard/config.rb +396 -396
- data/lib/spikard/converters.rb +13 -13
- data/lib/spikard/handler_wrapper.rb +113 -113
- data/lib/spikard/provide.rb +214 -214
- data/lib/spikard/response.rb +173 -173
- data/lib/spikard/schema.rb +243 -243
- data/lib/spikard/sse.rb +111 -111
- data/lib/spikard/streaming_response.rb +44 -44
- data/lib/spikard/testing.rb +256 -256
- data/lib/spikard/upload_file.rb +131 -131
- data/lib/spikard/version.rb +5 -5
- data/lib/spikard/websocket.rb +59 -59
- data/lib/spikard.rb +43 -43
- data/sig/spikard.rbs +366 -366
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -63
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +132 -132
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +752 -752
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +194 -194
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +246 -246
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +401 -401
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +238 -238
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +24 -24
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +292 -292
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +616 -616
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +305 -305
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -248
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +351 -351
- data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +454 -454
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +383 -383
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +280 -280
- data/vendor/crates/spikard-core/Cargo.toml +40 -40
- data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +133 -133
- data/vendor/crates/spikard-core/src/debug.rs +127 -127
- data/vendor/crates/spikard-core/src/di/container.rs +702 -702
- data/vendor/crates/spikard-core/src/di/dependency.rs +273 -273
- data/vendor/crates/spikard-core/src/di/error.rs +118 -118
- data/vendor/crates/spikard-core/src/di/factory.rs +534 -534
- data/vendor/crates/spikard-core/src/di/graph.rs +506 -506
- data/vendor/crates/spikard-core/src/di/mod.rs +192 -192
- data/vendor/crates/spikard-core/src/di/resolved.rs +405 -405
- data/vendor/crates/spikard-core/src/di/value.rs +281 -281
- data/vendor/crates/spikard-core/src/errors.rs +69 -69
- data/vendor/crates/spikard-core/src/http.rs +415 -415
- data/vendor/crates/spikard-core/src/lib.rs +29 -29
- data/vendor/crates/spikard-core/src/lifecycle.rs +1186 -1186
- data/vendor/crates/spikard-core/src/metadata.rs +389 -389
- data/vendor/crates/spikard-core/src/parameters.rs +2525 -2525
- data/vendor/crates/spikard-core/src/problem.rs +344 -344
- data/vendor/crates/spikard-core/src/request_data.rs +1154 -1154
- data/vendor/crates/spikard-core/src/router.rs +510 -510
- data/vendor/crates/spikard-core/src/schema_registry.rs +183 -183
- data/vendor/crates/spikard-core/src/type_hints.rs +304 -304
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +696 -688
- data/vendor/crates/spikard-core/src/validation/mod.rs +457 -457
- data/vendor/crates/spikard-http/Cargo.toml +62 -64
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +148 -148
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +92 -92
- data/vendor/crates/spikard-http/src/auth.rs +296 -296
- data/vendor/crates/spikard-http/src/background.rs +1860 -1860
- data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -3
- data/vendor/crates/spikard-http/src/bindings/response.rs +1 -1
- data/vendor/crates/spikard-http/src/body_metadata.rs +8 -8
- data/vendor/crates/spikard-http/src/cors.rs +1005 -1005
- data/vendor/crates/spikard-http/src/debug.rs +128 -128
- data/vendor/crates/spikard-http/src/di_handler.rs +1668 -1668
- data/vendor/crates/spikard-http/src/handler_response.rs +901 -901
- data/vendor/crates/spikard-http/src/handler_trait.rs +838 -830
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +290 -290
- data/vendor/crates/spikard-http/src/lib.rs +534 -534
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +230 -230
- data/vendor/crates/spikard-http/src/lifecycle.rs +1193 -1193
- data/vendor/crates/spikard-http/src/middleware/mod.rs +560 -540
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +912 -912
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +513 -513
- data/vendor/crates/spikard-http/src/middleware/validation.rs +768 -735
- data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -309
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +535 -535
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +1363 -1363
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +665 -665
- data/vendor/crates/spikard-http/src/query_parser.rs +793 -793
- data/vendor/crates/spikard-http/src/response.rs +720 -720
- data/vendor/crates/spikard-http/src/server/handler.rs +1650 -1650
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +234 -234
- data/vendor/crates/spikard-http/src/server/mod.rs +1593 -1502
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +789 -770
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +629 -599
- data/vendor/crates/spikard-http/src/sse.rs +1409 -1409
- data/vendor/crates/spikard-http/src/testing/form.rs +52 -52
- data/vendor/crates/spikard-http/src/testing/multipart.rs +64 -60
- data/vendor/crates/spikard-http/src/testing/test_client.rs +311 -283
- data/vendor/crates/spikard-http/src/testing.rs +406 -377
- data/vendor/crates/spikard-http/src/websocket.rs +1404 -1375
- data/vendor/crates/spikard-http/tests/background_behavior.rs +832 -832
- data/vendor/crates/spikard-http/tests/common/handlers.rs +309 -309
- data/vendor/crates/spikard-http/tests/common/mod.rs +26 -26
- data/vendor/crates/spikard-http/tests/di_integration.rs +192 -192
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +5 -5
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1093 -1093
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +656 -656
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +314 -314
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +620 -620
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +663 -663
- data/vendor/crates/spikard-rb/Cargo.toml +48 -48
- data/vendor/crates/spikard-rb/build.rs +199 -199
- data/vendor/crates/spikard-rb/src/background.rs +63 -63
- data/vendor/crates/spikard-rb/src/config/mod.rs +5 -5
- data/vendor/crates/spikard-rb/src/config/server_config.rs +285 -285
- data/vendor/crates/spikard-rb/src/conversion.rs +554 -554
- data/vendor/crates/spikard-rb/src/di/builder.rs +100 -100
- data/vendor/crates/spikard-rb/src/di/mod.rs +375 -375
- data/vendor/crates/spikard-rb/src/handler.rs +618 -618
- data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -3
- data/vendor/crates/spikard-rb/src/lib.rs +1806 -1810
- data/vendor/crates/spikard-rb/src/lifecycle.rs +275 -275
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -5
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +442 -447
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -5
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +324 -324
- data/vendor/crates/spikard-rb/src/server.rs +305 -308
- data/vendor/crates/spikard-rb/src/sse.rs +231 -231
- data/vendor/crates/spikard-rb/src/testing/client.rs +538 -551
- data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -7
- data/vendor/crates/spikard-rb/src/testing/sse.rs +143 -143
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +608 -635
- data/vendor/crates/spikard-rb/src/websocket.rs +377 -374
- metadata +15 -1
|
@@ -1,248 +1,248 @@
|
|
|
1
|
-
//! Shared test client infrastructure
|
|
2
|
-
|
|
3
|
-
use std::collections::HashMap;
|
|
4
|
-
|
|
5
|
-
/// Base configuration for test clients across bindings
|
|
6
|
-
pub struct TestClientConfig {
|
|
7
|
-
/// The base URL for the test server
|
|
8
|
-
pub base_url: String,
|
|
9
|
-
/// Request timeout in milliseconds
|
|
10
|
-
pub timeout_ms: u64,
|
|
11
|
-
/// Whether to follow redirects
|
|
12
|
-
pub follow_redirects: bool,
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
impl TestClientConfig {
|
|
16
|
-
/// Create a new test client configuration with custom base URL
|
|
17
|
-
pub fn new(base_url: impl Into<String>) -> Self {
|
|
18
|
-
Self {
|
|
19
|
-
base_url: base_url.into(),
|
|
20
|
-
timeout_ms: 30000,
|
|
21
|
-
follow_redirects: true,
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/// Set the timeout in milliseconds
|
|
26
|
-
pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
|
|
27
|
-
self.timeout_ms = timeout_ms;
|
|
28
|
-
self
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/// Set whether to follow redirects
|
|
32
|
-
pub fn with_follow_redirects(mut self, follow_redirects: bool) -> Self {
|
|
33
|
-
self.follow_redirects = follow_redirects;
|
|
34
|
-
self
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
impl Default for TestClientConfig {
|
|
39
|
-
fn default() -> Self {
|
|
40
|
-
Self {
|
|
41
|
-
base_url: "http://localhost:3000".to_string(),
|
|
42
|
-
timeout_ms: 30000,
|
|
43
|
-
follow_redirects: true,
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/// Common test response metadata
|
|
49
|
-
#[derive(Debug, Clone)]
|
|
50
|
-
pub struct TestResponseMetadata {
|
|
51
|
-
/// HTTP status code
|
|
52
|
-
pub status_code: u16,
|
|
53
|
-
/// Response headers
|
|
54
|
-
pub headers: HashMap<String, String>,
|
|
55
|
-
/// Response body size in bytes
|
|
56
|
-
pub body_size: usize,
|
|
57
|
-
/// Response time in milliseconds
|
|
58
|
-
pub response_time_ms: u64,
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
impl TestResponseMetadata {
|
|
62
|
-
/// Create a new test response metadata
|
|
63
|
-
pub fn new(status_code: u16, headers: HashMap<String, String>, body_size: usize, response_time_ms: u64) -> Self {
|
|
64
|
-
Self {
|
|
65
|
-
status_code,
|
|
66
|
-
headers,
|
|
67
|
-
body_size,
|
|
68
|
-
response_time_ms,
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/// Get a header value by name (case-insensitive)
|
|
73
|
-
pub fn get_header(&self, name: &str) -> Option<&String> {
|
|
74
|
-
let lower_name = name.to_lowercase();
|
|
75
|
-
self.headers
|
|
76
|
-
.iter()
|
|
77
|
-
.find(|(k, _)| k.to_lowercase() == lower_name)
|
|
78
|
-
.map(|(_, v)| v)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/// Check if response was successful (2xx status code)
|
|
82
|
-
pub fn is_success(&self) -> bool {
|
|
83
|
-
self.status_code >= 200 && self.status_code < 300
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/// Check if response was a client error (4xx status code)
|
|
87
|
-
pub fn is_client_error(&self) -> bool {
|
|
88
|
-
self.status_code >= 400 && self.status_code < 500
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/// Check if response was a server error (5xx status code)
|
|
92
|
-
pub fn is_server_error(&self) -> bool {
|
|
93
|
-
self.status_code >= 500 && self.status_code < 600
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
#[cfg(test)]
|
|
98
|
-
mod tests {
|
|
99
|
-
use super::*;
|
|
100
|
-
|
|
101
|
-
#[test]
|
|
102
|
-
fn test_test_client_config_default() {
|
|
103
|
-
let config = TestClientConfig::default();
|
|
104
|
-
assert_eq!(config.base_url, "http://localhost:3000");
|
|
105
|
-
assert_eq!(config.timeout_ms, 30000);
|
|
106
|
-
assert!(config.follow_redirects);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
#[test]
|
|
110
|
-
fn test_test_client_config_new() {
|
|
111
|
-
let config = TestClientConfig::new("http://example.com:8080");
|
|
112
|
-
assert_eq!(config.base_url, "http://example.com:8080");
|
|
113
|
-
assert_eq!(config.timeout_ms, 30000);
|
|
114
|
-
assert!(config.follow_redirects);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
#[test]
|
|
118
|
-
fn test_test_client_config_with_timeout() {
|
|
119
|
-
let config = TestClientConfig::default().with_timeout(5000);
|
|
120
|
-
assert_eq!(config.timeout_ms, 5000);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
#[test]
|
|
124
|
-
fn test_test_client_config_with_follow_redirects_false() {
|
|
125
|
-
let config = TestClientConfig::default().with_follow_redirects(false);
|
|
126
|
-
assert!(!config.follow_redirects);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
#[test]
|
|
130
|
-
fn test_test_client_config_chaining() {
|
|
131
|
-
let config = TestClientConfig::new("http://api.example.com")
|
|
132
|
-
.with_timeout(10000)
|
|
133
|
-
.with_follow_redirects(false);
|
|
134
|
-
|
|
135
|
-
assert_eq!(config.base_url, "http://api.example.com");
|
|
136
|
-
assert_eq!(config.timeout_ms, 10000);
|
|
137
|
-
assert!(!config.follow_redirects);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
#[test]
|
|
141
|
-
fn test_response_metadata_new() {
|
|
142
|
-
let mut headers = HashMap::new();
|
|
143
|
-
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
|
144
|
-
|
|
145
|
-
let metadata = TestResponseMetadata::new(200, headers.clone(), 256, 100);
|
|
146
|
-
|
|
147
|
-
assert_eq!(metadata.status_code, 200);
|
|
148
|
-
assert_eq!(metadata.headers, headers);
|
|
149
|
-
assert_eq!(metadata.body_size, 256);
|
|
150
|
-
assert_eq!(metadata.response_time_ms, 100);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
#[test]
|
|
154
|
-
fn test_response_metadata_clone() {
|
|
155
|
-
let mut headers = HashMap::new();
|
|
156
|
-
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
|
157
|
-
|
|
158
|
-
let metadata1 = TestResponseMetadata::new(201, headers, 512, 200);
|
|
159
|
-
let metadata2 = metadata1.clone();
|
|
160
|
-
|
|
161
|
-
assert_eq!(metadata1.status_code, metadata2.status_code);
|
|
162
|
-
assert_eq!(metadata1.body_size, metadata2.body_size);
|
|
163
|
-
assert_eq!(metadata1.response_time_ms, metadata2.response_time_ms);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
#[test]
|
|
167
|
-
fn test_get_header_case_insensitive() {
|
|
168
|
-
let mut headers = HashMap::new();
|
|
169
|
-
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
|
170
|
-
headers.insert("Authorization".to_string(), "Bearer token123".to_string());
|
|
171
|
-
|
|
172
|
-
let metadata = TestResponseMetadata::new(200, headers, 100, 50);
|
|
173
|
-
|
|
174
|
-
assert_eq!(
|
|
175
|
-
metadata.get_header("Content-Type"),
|
|
176
|
-
Some(&"application/json".to_string())
|
|
177
|
-
);
|
|
178
|
-
assert_eq!(
|
|
179
|
-
metadata.get_header("content-type"),
|
|
180
|
-
Some(&"application/json".to_string())
|
|
181
|
-
);
|
|
182
|
-
assert_eq!(
|
|
183
|
-
metadata.get_header("CONTENT-TYPE"),
|
|
184
|
-
Some(&"application/json".to_string())
|
|
185
|
-
);
|
|
186
|
-
assert_eq!(metadata.get_header("Missing"), None);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
#[test]
|
|
190
|
-
fn test_is_success() {
|
|
191
|
-
let headers = HashMap::new();
|
|
192
|
-
let metadata_200 = TestResponseMetadata::new(200, headers.clone(), 100, 50);
|
|
193
|
-
let metadata_201 = TestResponseMetadata::new(201, headers.clone(), 100, 50);
|
|
194
|
-
let metadata_204 = TestResponseMetadata::new(204, headers.clone(), 0, 50);
|
|
195
|
-
let metadata_299 = TestResponseMetadata::new(299, headers.clone(), 100, 50);
|
|
196
|
-
let metadata_300 = TestResponseMetadata::new(300, headers.clone(), 100, 50);
|
|
197
|
-
let metadata_400 = TestResponseMetadata::new(400, headers.clone(), 100, 50);
|
|
198
|
-
|
|
199
|
-
assert!(metadata_200.is_success());
|
|
200
|
-
assert!(metadata_201.is_success());
|
|
201
|
-
assert!(metadata_204.is_success());
|
|
202
|
-
assert!(metadata_299.is_success());
|
|
203
|
-
assert!(!metadata_300.is_success());
|
|
204
|
-
assert!(!metadata_400.is_success());
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
#[test]
|
|
208
|
-
fn test_is_client_error() {
|
|
209
|
-
let headers = HashMap::new();
|
|
210
|
-
let metadata_399 = TestResponseMetadata::new(399, headers.clone(), 100, 50);
|
|
211
|
-
let metadata_400 = TestResponseMetadata::new(400, headers.clone(), 100, 50);
|
|
212
|
-
let metadata_404 = TestResponseMetadata::new(404, headers.clone(), 100, 50);
|
|
213
|
-
let metadata_499 = TestResponseMetadata::new(499, headers.clone(), 100, 50);
|
|
214
|
-
let metadata_500 = TestResponseMetadata::new(500, headers.clone(), 100, 50);
|
|
215
|
-
|
|
216
|
-
assert!(!metadata_399.is_client_error());
|
|
217
|
-
assert!(metadata_400.is_client_error());
|
|
218
|
-
assert!(metadata_404.is_client_error());
|
|
219
|
-
assert!(metadata_499.is_client_error());
|
|
220
|
-
assert!(!metadata_500.is_client_error());
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
#[test]
|
|
224
|
-
fn test_is_server_error() {
|
|
225
|
-
let headers = HashMap::new();
|
|
226
|
-
let metadata_499 = TestResponseMetadata::new(499, headers.clone(), 100, 50);
|
|
227
|
-
let metadata_500 = TestResponseMetadata::new(500, headers.clone(), 100, 50);
|
|
228
|
-
let metadata_502 = TestResponseMetadata::new(502, headers.clone(), 100, 50);
|
|
229
|
-
let metadata_599 = TestResponseMetadata::new(599, headers.clone(), 100, 50);
|
|
230
|
-
let metadata_600 = TestResponseMetadata::new(600, headers.clone(), 100, 50);
|
|
231
|
-
|
|
232
|
-
assert!(!metadata_499.is_server_error());
|
|
233
|
-
assert!(metadata_500.is_server_error());
|
|
234
|
-
assert!(metadata_502.is_server_error());
|
|
235
|
-
assert!(metadata_599.is_server_error());
|
|
236
|
-
assert!(!metadata_600.is_server_error());
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
#[test]
|
|
240
|
-
fn test_response_metadata_debug() {
|
|
241
|
-
let headers = HashMap::new();
|
|
242
|
-
let metadata = TestResponseMetadata::new(200, headers, 100, 50);
|
|
243
|
-
let debug_str = format!("{:?}", metadata);
|
|
244
|
-
assert!(debug_str.contains("200"));
|
|
245
|
-
assert!(debug_str.contains("100"));
|
|
246
|
-
assert!(debug_str.contains("50"));
|
|
247
|
-
}
|
|
248
|
-
}
|
|
1
|
+
//! Shared test client infrastructure
|
|
2
|
+
|
|
3
|
+
use std::collections::HashMap;
|
|
4
|
+
|
|
5
|
+
/// Base configuration for test clients across bindings
|
|
6
|
+
pub struct TestClientConfig {
|
|
7
|
+
/// The base URL for the test server
|
|
8
|
+
pub base_url: String,
|
|
9
|
+
/// Request timeout in milliseconds
|
|
10
|
+
pub timeout_ms: u64,
|
|
11
|
+
/// Whether to follow redirects
|
|
12
|
+
pub follow_redirects: bool,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
impl TestClientConfig {
|
|
16
|
+
/// Create a new test client configuration with custom base URL
|
|
17
|
+
pub fn new(base_url: impl Into<String>) -> Self {
|
|
18
|
+
Self {
|
|
19
|
+
base_url: base_url.into(),
|
|
20
|
+
timeout_ms: 30000,
|
|
21
|
+
follow_redirects: true,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/// Set the timeout in milliseconds
|
|
26
|
+
pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
|
|
27
|
+
self.timeout_ms = timeout_ms;
|
|
28
|
+
self
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Set whether to follow redirects
|
|
32
|
+
pub fn with_follow_redirects(mut self, follow_redirects: bool) -> Self {
|
|
33
|
+
self.follow_redirects = follow_redirects;
|
|
34
|
+
self
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl Default for TestClientConfig {
|
|
39
|
+
fn default() -> Self {
|
|
40
|
+
Self {
|
|
41
|
+
base_url: "http://localhost:3000".to_string(),
|
|
42
|
+
timeout_ms: 30000,
|
|
43
|
+
follow_redirects: true,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Common test response metadata
|
|
49
|
+
#[derive(Debug, Clone)]
|
|
50
|
+
pub struct TestResponseMetadata {
|
|
51
|
+
/// HTTP status code
|
|
52
|
+
pub status_code: u16,
|
|
53
|
+
/// Response headers
|
|
54
|
+
pub headers: HashMap<String, String>,
|
|
55
|
+
/// Response body size in bytes
|
|
56
|
+
pub body_size: usize,
|
|
57
|
+
/// Response time in milliseconds
|
|
58
|
+
pub response_time_ms: u64,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl TestResponseMetadata {
|
|
62
|
+
/// Create a new test response metadata
|
|
63
|
+
pub fn new(status_code: u16, headers: HashMap<String, String>, body_size: usize, response_time_ms: u64) -> Self {
|
|
64
|
+
Self {
|
|
65
|
+
status_code,
|
|
66
|
+
headers,
|
|
67
|
+
body_size,
|
|
68
|
+
response_time_ms,
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Get a header value by name (case-insensitive)
|
|
73
|
+
pub fn get_header(&self, name: &str) -> Option<&String> {
|
|
74
|
+
let lower_name = name.to_lowercase();
|
|
75
|
+
self.headers
|
|
76
|
+
.iter()
|
|
77
|
+
.find(|(k, _)| k.to_lowercase() == lower_name)
|
|
78
|
+
.map(|(_, v)| v)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Check if response was successful (2xx status code)
|
|
82
|
+
pub fn is_success(&self) -> bool {
|
|
83
|
+
self.status_code >= 200 && self.status_code < 300
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Check if response was a client error (4xx status code)
|
|
87
|
+
pub fn is_client_error(&self) -> bool {
|
|
88
|
+
self.status_code >= 400 && self.status_code < 500
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Check if response was a server error (5xx status code)
|
|
92
|
+
pub fn is_server_error(&self) -> bool {
|
|
93
|
+
self.status_code >= 500 && self.status_code < 600
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#[cfg(test)]
|
|
98
|
+
mod tests {
|
|
99
|
+
use super::*;
|
|
100
|
+
|
|
101
|
+
#[test]
|
|
102
|
+
fn test_test_client_config_default() {
|
|
103
|
+
let config = TestClientConfig::default();
|
|
104
|
+
assert_eq!(config.base_url, "http://localhost:3000");
|
|
105
|
+
assert_eq!(config.timeout_ms, 30000);
|
|
106
|
+
assert!(config.follow_redirects);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#[test]
|
|
110
|
+
fn test_test_client_config_new() {
|
|
111
|
+
let config = TestClientConfig::new("http://example.com:8080");
|
|
112
|
+
assert_eq!(config.base_url, "http://example.com:8080");
|
|
113
|
+
assert_eq!(config.timeout_ms, 30000);
|
|
114
|
+
assert!(config.follow_redirects);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
#[test]
|
|
118
|
+
fn test_test_client_config_with_timeout() {
|
|
119
|
+
let config = TestClientConfig::default().with_timeout(5000);
|
|
120
|
+
assert_eq!(config.timeout_ms, 5000);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#[test]
|
|
124
|
+
fn test_test_client_config_with_follow_redirects_false() {
|
|
125
|
+
let config = TestClientConfig::default().with_follow_redirects(false);
|
|
126
|
+
assert!(!config.follow_redirects);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[test]
|
|
130
|
+
fn test_test_client_config_chaining() {
|
|
131
|
+
let config = TestClientConfig::new("http://api.example.com")
|
|
132
|
+
.with_timeout(10000)
|
|
133
|
+
.with_follow_redirects(false);
|
|
134
|
+
|
|
135
|
+
assert_eq!(config.base_url, "http://api.example.com");
|
|
136
|
+
assert_eq!(config.timeout_ms, 10000);
|
|
137
|
+
assert!(!config.follow_redirects);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[test]
|
|
141
|
+
fn test_response_metadata_new() {
|
|
142
|
+
let mut headers = HashMap::new();
|
|
143
|
+
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
|
144
|
+
|
|
145
|
+
let metadata = TestResponseMetadata::new(200, headers.clone(), 256, 100);
|
|
146
|
+
|
|
147
|
+
assert_eq!(metadata.status_code, 200);
|
|
148
|
+
assert_eq!(metadata.headers, headers);
|
|
149
|
+
assert_eq!(metadata.body_size, 256);
|
|
150
|
+
assert_eq!(metadata.response_time_ms, 100);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[test]
|
|
154
|
+
fn test_response_metadata_clone() {
|
|
155
|
+
let mut headers = HashMap::new();
|
|
156
|
+
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
|
157
|
+
|
|
158
|
+
let metadata1 = TestResponseMetadata::new(201, headers, 512, 200);
|
|
159
|
+
let metadata2 = metadata1.clone();
|
|
160
|
+
|
|
161
|
+
assert_eq!(metadata1.status_code, metadata2.status_code);
|
|
162
|
+
assert_eq!(metadata1.body_size, metadata2.body_size);
|
|
163
|
+
assert_eq!(metadata1.response_time_ms, metadata2.response_time_ms);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#[test]
|
|
167
|
+
fn test_get_header_case_insensitive() {
|
|
168
|
+
let mut headers = HashMap::new();
|
|
169
|
+
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
|
170
|
+
headers.insert("Authorization".to_string(), "Bearer token123".to_string());
|
|
171
|
+
|
|
172
|
+
let metadata = TestResponseMetadata::new(200, headers, 100, 50);
|
|
173
|
+
|
|
174
|
+
assert_eq!(
|
|
175
|
+
metadata.get_header("Content-Type"),
|
|
176
|
+
Some(&"application/json".to_string())
|
|
177
|
+
);
|
|
178
|
+
assert_eq!(
|
|
179
|
+
metadata.get_header("content-type"),
|
|
180
|
+
Some(&"application/json".to_string())
|
|
181
|
+
);
|
|
182
|
+
assert_eq!(
|
|
183
|
+
metadata.get_header("CONTENT-TYPE"),
|
|
184
|
+
Some(&"application/json".to_string())
|
|
185
|
+
);
|
|
186
|
+
assert_eq!(metadata.get_header("Missing"), None);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#[test]
|
|
190
|
+
fn test_is_success() {
|
|
191
|
+
let headers = HashMap::new();
|
|
192
|
+
let metadata_200 = TestResponseMetadata::new(200, headers.clone(), 100, 50);
|
|
193
|
+
let metadata_201 = TestResponseMetadata::new(201, headers.clone(), 100, 50);
|
|
194
|
+
let metadata_204 = TestResponseMetadata::new(204, headers.clone(), 0, 50);
|
|
195
|
+
let metadata_299 = TestResponseMetadata::new(299, headers.clone(), 100, 50);
|
|
196
|
+
let metadata_300 = TestResponseMetadata::new(300, headers.clone(), 100, 50);
|
|
197
|
+
let metadata_400 = TestResponseMetadata::new(400, headers.clone(), 100, 50);
|
|
198
|
+
|
|
199
|
+
assert!(metadata_200.is_success());
|
|
200
|
+
assert!(metadata_201.is_success());
|
|
201
|
+
assert!(metadata_204.is_success());
|
|
202
|
+
assert!(metadata_299.is_success());
|
|
203
|
+
assert!(!metadata_300.is_success());
|
|
204
|
+
assert!(!metadata_400.is_success());
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#[test]
|
|
208
|
+
fn test_is_client_error() {
|
|
209
|
+
let headers = HashMap::new();
|
|
210
|
+
let metadata_399 = TestResponseMetadata::new(399, headers.clone(), 100, 50);
|
|
211
|
+
let metadata_400 = TestResponseMetadata::new(400, headers.clone(), 100, 50);
|
|
212
|
+
let metadata_404 = TestResponseMetadata::new(404, headers.clone(), 100, 50);
|
|
213
|
+
let metadata_499 = TestResponseMetadata::new(499, headers.clone(), 100, 50);
|
|
214
|
+
let metadata_500 = TestResponseMetadata::new(500, headers.clone(), 100, 50);
|
|
215
|
+
|
|
216
|
+
assert!(!metadata_399.is_client_error());
|
|
217
|
+
assert!(metadata_400.is_client_error());
|
|
218
|
+
assert!(metadata_404.is_client_error());
|
|
219
|
+
assert!(metadata_499.is_client_error());
|
|
220
|
+
assert!(!metadata_500.is_client_error());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#[test]
|
|
224
|
+
fn test_is_server_error() {
|
|
225
|
+
let headers = HashMap::new();
|
|
226
|
+
let metadata_499 = TestResponseMetadata::new(499, headers.clone(), 100, 50);
|
|
227
|
+
let metadata_500 = TestResponseMetadata::new(500, headers.clone(), 100, 50);
|
|
228
|
+
let metadata_502 = TestResponseMetadata::new(502, headers.clone(), 100, 50);
|
|
229
|
+
let metadata_599 = TestResponseMetadata::new(599, headers.clone(), 100, 50);
|
|
230
|
+
let metadata_600 = TestResponseMetadata::new(600, headers.clone(), 100, 50);
|
|
231
|
+
|
|
232
|
+
assert!(!metadata_499.is_server_error());
|
|
233
|
+
assert!(metadata_500.is_server_error());
|
|
234
|
+
assert!(metadata_502.is_server_error());
|
|
235
|
+
assert!(metadata_599.is_server_error());
|
|
236
|
+
assert!(!metadata_600.is_server_error());
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
#[test]
|
|
240
|
+
fn test_response_metadata_debug() {
|
|
241
|
+
let headers = HashMap::new();
|
|
242
|
+
let metadata = TestResponseMetadata::new(200, headers, 100, 50);
|
|
243
|
+
let debug_str = format!("{:?}", metadata);
|
|
244
|
+
assert!(debug_str.contains("200"));
|
|
245
|
+
assert!(debug_str.contains("100"));
|
|
246
|
+
assert!(debug_str.contains("50"));
|
|
247
|
+
}
|
|
248
|
+
}
|