spikard 0.6.2 → 0.7.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/README.md +90 -508
- data/ext/spikard_rb/Cargo.lock +3287 -0
- data/ext/spikard_rb/Cargo.toml +1 -1
- data/ext/spikard_rb/extconf.rb +3 -3
- data/lib/spikard/app.rb +72 -49
- data/lib/spikard/background.rb +38 -7
- data/lib/spikard/testing.rb +42 -4
- data/lib/spikard/version.rb +1 -1
- data/sig/spikard.rbs +4 -0
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +1 -1
- data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +191 -0
- data/vendor/crates/spikard-core/Cargo.toml +1 -1
- data/vendor/crates/spikard-core/src/http.rs +1 -0
- data/vendor/crates/spikard-core/src/lifecycle.rs +63 -0
- data/vendor/crates/spikard-core/tests/bindings_response_tests.rs +136 -0
- data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +37 -0
- data/vendor/crates/spikard-core/tests/error_mapper.rs +761 -0
- data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +106 -0
- data/vendor/crates/spikard-core/tests/parameters_full.rs +701 -0
- data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +301 -0
- data/vendor/crates/spikard-core/tests/request_data_roundtrip.rs +67 -0
- data/vendor/crates/spikard-core/tests/validation_coverage.rs +250 -0
- data/vendor/crates/spikard-core/tests/validation_error_paths.rs +45 -0
- data/vendor/crates/spikard-http/Cargo.toml +1 -1
- data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +502 -0
- data/vendor/crates/spikard-http/src/jsonrpc/method_registry.rs +648 -0
- data/vendor/crates/spikard-http/src/jsonrpc/mod.rs +58 -0
- data/vendor/crates/spikard-http/src/jsonrpc/protocol.rs +1207 -0
- data/vendor/crates/spikard-http/src/jsonrpc/router.rs +2262 -0
- data/vendor/crates/spikard-http/src/testing/test_client.rs +155 -2
- data/vendor/crates/spikard-http/src/testing.rs +171 -0
- data/vendor/crates/spikard-http/src/websocket.rs +79 -6
- data/vendor/crates/spikard-http/tests/auth_integration.rs +647 -0
- data/vendor/crates/spikard-http/tests/common/test_builders.rs +633 -0
- data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +162 -0
- data/vendor/crates/spikard-http/tests/middleware_stack_integration.rs +389 -0
- data/vendor/crates/spikard-http/tests/request_extraction_full.rs +513 -0
- data/vendor/crates/spikard-http/tests/server_auth_middleware_behavior.rs +244 -0
- data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +200 -0
- data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +82 -0
- data/vendor/crates/spikard-http/tests/server_handler_wrappers.rs +464 -0
- data/vendor/crates/spikard-http/tests/server_method_router_additional_behavior.rs +286 -0
- data/vendor/crates/spikard-http/tests/server_method_router_coverage.rs +118 -0
- data/vendor/crates/spikard-http/tests/server_middleware_behavior.rs +99 -0
- data/vendor/crates/spikard-http/tests/server_middleware_branches.rs +206 -0
- data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +281 -0
- data/vendor/crates/spikard-http/tests/server_router_behavior.rs +121 -0
- data/vendor/crates/spikard-http/tests/sse_full_behavior.rs +584 -0
- data/vendor/crates/spikard-http/tests/sse_handler_behavior.rs +130 -0
- data/vendor/crates/spikard-http/tests/test_client_requests.rs +167 -0
- data/vendor/crates/spikard-http/tests/testing_helpers.rs +87 -0
- data/vendor/crates/spikard-http/tests/testing_module_coverage.rs +156 -0
- data/vendor/crates/spikard-http/tests/urlencoded_content_type.rs +82 -0
- data/vendor/crates/spikard-http/tests/websocket_full_behavior.rs +440 -0
- data/vendor/crates/spikard-http/tests/websocket_integration.rs +152 -0
- data/vendor/crates/spikard-rb/Cargo.toml +1 -1
- data/vendor/crates/spikard-rb/src/gvl.rs +80 -0
- data/vendor/crates/spikard-rb/src/handler.rs +12 -9
- data/vendor/crates/spikard-rb/src/lib.rs +137 -124
- data/vendor/crates/spikard-rb/src/request.rs +342 -0
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +1 -8
- data/vendor/crates/spikard-rb/src/server.rs +1 -8
- data/vendor/crates/spikard-rb/src/testing/client.rs +168 -9
- data/vendor/crates/spikard-rb/src/websocket.rs +119 -30
- data/vendor/crates/spikard-rb-macros/Cargo.toml +14 -0
- data/vendor/crates/spikard-rb-macros/src/lib.rs +52 -0
- metadata +44 -1
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
#![allow(clippy::pedantic, clippy::nursery, clippy::all)]
|
|
2
|
+
//! Integration tests for server handler wrappers (ValidatingHandler, DependencyInjectingHandler)
|
|
3
|
+
//!
|
|
4
|
+
//! These tests verify critical validation and dependency injection behavior across
|
|
5
|
+
//! the HTTP request pipeline. They test observable behaviors like status codes,
|
|
6
|
+
//! error messages, and request enrichment rather than internal validation logic.
|
|
7
|
+
|
|
8
|
+
mod common;
|
|
9
|
+
|
|
10
|
+
use axum::http::StatusCode;
|
|
11
|
+
use common::test_builders::{HandlerBuilder, RequestBuilder, assert_status};
|
|
12
|
+
use serde_json::json;
|
|
13
|
+
use spikard_core::Route;
|
|
14
|
+
use spikard_http::{Handler, server::handler::ValidatingHandler};
|
|
15
|
+
use std::sync::Arc;
|
|
16
|
+
|
|
17
|
+
mod validating_handler {
|
|
18
|
+
use super::*;
|
|
19
|
+
|
|
20
|
+
/// Test 1: Valid request passes validation and calls handler
|
|
21
|
+
#[tokio::test]
|
|
22
|
+
async fn test_validating_handler_allows_valid_request() {
|
|
23
|
+
let schema = json!({
|
|
24
|
+
"type": "object",
|
|
25
|
+
"properties": {
|
|
26
|
+
"name": {"type": "string"},
|
|
27
|
+
"email": {"type": "string"}
|
|
28
|
+
},
|
|
29
|
+
"required": ["name", "email"]
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
let validator = Arc::new(spikard_core::SchemaValidator::new(schema).unwrap());
|
|
33
|
+
|
|
34
|
+
let route = Route {
|
|
35
|
+
method: spikard_core::http::Method::Post,
|
|
36
|
+
path: "/users".to_string(),
|
|
37
|
+
handler_name: "create_user".to_string(),
|
|
38
|
+
request_validator: Some(validator),
|
|
39
|
+
response_validator: None,
|
|
40
|
+
parameter_validator: None,
|
|
41
|
+
file_params: None,
|
|
42
|
+
is_async: true,
|
|
43
|
+
cors: None,
|
|
44
|
+
expects_json_body: true,
|
|
45
|
+
#[cfg(feature = "di")]
|
|
46
|
+
handler_dependencies: vec![],
|
|
47
|
+
jsonrpc_method: None,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
let inner_handler = HandlerBuilder::new()
|
|
51
|
+
.status(201)
|
|
52
|
+
.json_body(json!({"id": 1, "created": true}))
|
|
53
|
+
.build();
|
|
54
|
+
|
|
55
|
+
let validator_handler = ValidatingHandler::new(inner_handler, &route);
|
|
56
|
+
|
|
57
|
+
let (request, request_data) = RequestBuilder::new()
|
|
58
|
+
.method(axum::http::Method::POST)
|
|
59
|
+
.path("/users")
|
|
60
|
+
.json_body(json!({"name": "Alice", "email": "alice@example.com"}))
|
|
61
|
+
.build();
|
|
62
|
+
|
|
63
|
+
let response = validator_handler.call(request, request_data).await.unwrap();
|
|
64
|
+
|
|
65
|
+
assert_status(&response, StatusCode::CREATED);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// Test 2: Request with invalid JSON body returns 422
|
|
69
|
+
#[tokio::test]
|
|
70
|
+
async fn test_validating_handler_rejects_invalid_json_body() {
|
|
71
|
+
let schema = json!({
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {
|
|
74
|
+
"email": {"type": "string"}
|
|
75
|
+
},
|
|
76
|
+
"required": ["email"]
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
let validator = Arc::new(spikard_core::SchemaValidator::new(schema).unwrap());
|
|
80
|
+
|
|
81
|
+
let route = Route {
|
|
82
|
+
method: spikard_core::http::Method::Post,
|
|
83
|
+
path: "/users".to_string(),
|
|
84
|
+
handler_name: "create_user".to_string(),
|
|
85
|
+
request_validator: Some(validator),
|
|
86
|
+
response_validator: None,
|
|
87
|
+
parameter_validator: None,
|
|
88
|
+
file_params: None,
|
|
89
|
+
is_async: true,
|
|
90
|
+
cors: None,
|
|
91
|
+
expects_json_body: true,
|
|
92
|
+
#[cfg(feature = "di")]
|
|
93
|
+
handler_dependencies: vec![],
|
|
94
|
+
jsonrpc_method: None,
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
let inner_handler = HandlerBuilder::new().status(200).build();
|
|
98
|
+
let validator_handler = ValidatingHandler::new(inner_handler, &route);
|
|
99
|
+
|
|
100
|
+
let (request, request_data) = RequestBuilder::new()
|
|
101
|
+
.method(axum::http::Method::POST)
|
|
102
|
+
.path("/users")
|
|
103
|
+
.json_body(json!({"name": "Alice"}))
|
|
104
|
+
.build();
|
|
105
|
+
|
|
106
|
+
let result = validator_handler.call(request, request_data).await;
|
|
107
|
+
|
|
108
|
+
assert!(result.is_err());
|
|
109
|
+
let (status, body) = result.unwrap_err();
|
|
110
|
+
assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
|
|
111
|
+
|
|
112
|
+
let error: serde_json::Value = serde_json::from_str(&body).unwrap();
|
|
113
|
+
assert!(error["errors"].is_array());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Test 3: Missing required field returns 422 with field-specific error
|
|
117
|
+
#[tokio::test]
|
|
118
|
+
async fn test_validating_handler_rejects_missing_required_field() {
|
|
119
|
+
let schema = json!({
|
|
120
|
+
"type": "object",
|
|
121
|
+
"properties": {
|
|
122
|
+
"name": {"type": "string"},
|
|
123
|
+
"age": {"type": "integer"}
|
|
124
|
+
},
|
|
125
|
+
"required": ["name"]
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
let validator = Arc::new(spikard_core::SchemaValidator::new(schema).unwrap());
|
|
129
|
+
|
|
130
|
+
let route = Route {
|
|
131
|
+
method: spikard_core::http::Method::Post,
|
|
132
|
+
path: "/api/test".to_string(),
|
|
133
|
+
handler_name: "test_handler".to_string(),
|
|
134
|
+
request_validator: Some(validator),
|
|
135
|
+
response_validator: None,
|
|
136
|
+
parameter_validator: None,
|
|
137
|
+
file_params: None,
|
|
138
|
+
is_async: true,
|
|
139
|
+
cors: None,
|
|
140
|
+
expects_json_body: true,
|
|
141
|
+
#[cfg(feature = "di")]
|
|
142
|
+
handler_dependencies: vec![],
|
|
143
|
+
jsonrpc_method: None,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
let inner_handler = HandlerBuilder::new().build();
|
|
147
|
+
let validator_handler = ValidatingHandler::new(inner_handler, &route);
|
|
148
|
+
|
|
149
|
+
let (request, request_data) = RequestBuilder::new()
|
|
150
|
+
.method(axum::http::Method::POST)
|
|
151
|
+
.json_body(json!({"age": 25}))
|
|
152
|
+
.build();
|
|
153
|
+
|
|
154
|
+
let result = validator_handler.call(request, request_data).await;
|
|
155
|
+
|
|
156
|
+
assert!(result.is_err());
|
|
157
|
+
let (status, body) = result.unwrap_err();
|
|
158
|
+
assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
|
|
159
|
+
|
|
160
|
+
let error: serde_json::Value = serde_json::from_str(&body).unwrap();
|
|
161
|
+
assert!(error["errors"][0]["loc"][1].as_str().map_or(false, |s| s == "name"));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Test 4: Wrong type in field returns 422 with type error
|
|
165
|
+
#[tokio::test]
|
|
166
|
+
async fn test_validating_handler_rejects_wrong_type() {
|
|
167
|
+
let schema = json!({
|
|
168
|
+
"type": "object",
|
|
169
|
+
"properties": {
|
|
170
|
+
"count": {"type": "integer"}
|
|
171
|
+
},
|
|
172
|
+
"required": ["count"]
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
let validator = Arc::new(spikard_core::SchemaValidator::new(schema).unwrap());
|
|
176
|
+
|
|
177
|
+
let route = Route {
|
|
178
|
+
method: spikard_core::http::Method::Post,
|
|
179
|
+
path: "/api/test".to_string(),
|
|
180
|
+
handler_name: "test_handler".to_string(),
|
|
181
|
+
request_validator: Some(validator),
|
|
182
|
+
response_validator: None,
|
|
183
|
+
parameter_validator: None,
|
|
184
|
+
file_params: None,
|
|
185
|
+
is_async: true,
|
|
186
|
+
cors: None,
|
|
187
|
+
expects_json_body: true,
|
|
188
|
+
#[cfg(feature = "di")]
|
|
189
|
+
handler_dependencies: vec![],
|
|
190
|
+
jsonrpc_method: None,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
let inner_handler = HandlerBuilder::new().build();
|
|
194
|
+
let validator_handler = ValidatingHandler::new(inner_handler, &route);
|
|
195
|
+
|
|
196
|
+
let (request, request_data) = RequestBuilder::new()
|
|
197
|
+
.method(axum::http::Method::POST)
|
|
198
|
+
.json_body(json!({"count": "not_a_number"}))
|
|
199
|
+
.build();
|
|
200
|
+
|
|
201
|
+
let result = validator_handler.call(request, request_data).await;
|
|
202
|
+
|
|
203
|
+
assert!(result.is_err());
|
|
204
|
+
let (status, body) = result.unwrap_err();
|
|
205
|
+
assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
|
|
206
|
+
|
|
207
|
+
let error: serde_json::Value = serde_json::from_str(&body).unwrap();
|
|
208
|
+
assert!(error["errors"].is_array());
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// Test 5: Optional fields missing still allows handler execution
|
|
212
|
+
#[tokio::test]
|
|
213
|
+
async fn test_validating_handler_allows_optional_fields() {
|
|
214
|
+
let schema = json!({
|
|
215
|
+
"type": "object",
|
|
216
|
+
"properties": {
|
|
217
|
+
"name": {"type": "string"},
|
|
218
|
+
"description": {"type": "string"}
|
|
219
|
+
},
|
|
220
|
+
"required": ["name"]
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
let validator = Arc::new(spikard_core::SchemaValidator::new(schema).unwrap());
|
|
224
|
+
|
|
225
|
+
let route = Route {
|
|
226
|
+
method: spikard_core::http::Method::Post,
|
|
227
|
+
path: "/api/test".to_string(),
|
|
228
|
+
handler_name: "test_handler".to_string(),
|
|
229
|
+
request_validator: Some(validator),
|
|
230
|
+
response_validator: None,
|
|
231
|
+
parameter_validator: None,
|
|
232
|
+
file_params: None,
|
|
233
|
+
is_async: true,
|
|
234
|
+
cors: None,
|
|
235
|
+
expects_json_body: true,
|
|
236
|
+
#[cfg(feature = "di")]
|
|
237
|
+
handler_dependencies: vec![],
|
|
238
|
+
jsonrpc_method: None,
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
let inner_handler = HandlerBuilder::new().status(200).build();
|
|
242
|
+
let validator_handler = ValidatingHandler::new(inner_handler, &route);
|
|
243
|
+
|
|
244
|
+
let (request, request_data) = RequestBuilder::new()
|
|
245
|
+
.method(axum::http::Method::POST)
|
|
246
|
+
.json_body(json!({"name": "Test"}))
|
|
247
|
+
.build();
|
|
248
|
+
|
|
249
|
+
let result = validator_handler.call(request, request_data).await;
|
|
250
|
+
|
|
251
|
+
assert!(result.is_ok());
|
|
252
|
+
let response = result.unwrap();
|
|
253
|
+
assert_status(&response, StatusCode::OK);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/// Test 6: Nested object validation returns 422 with error path
|
|
257
|
+
#[tokio::test]
|
|
258
|
+
async fn test_validating_handler_validates_nested_objects() {
|
|
259
|
+
let schema = json!({
|
|
260
|
+
"type": "object",
|
|
261
|
+
"properties": {
|
|
262
|
+
"user": {
|
|
263
|
+
"type": "object",
|
|
264
|
+
"properties": {
|
|
265
|
+
"name": {"type": "string"}
|
|
266
|
+
},
|
|
267
|
+
"required": ["name"]
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
"required": ["user"]
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
let validator = Arc::new(spikard_core::SchemaValidator::new(schema).unwrap());
|
|
274
|
+
|
|
275
|
+
let route = Route {
|
|
276
|
+
method: spikard_core::http::Method::Post,
|
|
277
|
+
path: "/api/test".to_string(),
|
|
278
|
+
handler_name: "test_handler".to_string(),
|
|
279
|
+
request_validator: Some(validator),
|
|
280
|
+
response_validator: None,
|
|
281
|
+
parameter_validator: None,
|
|
282
|
+
file_params: None,
|
|
283
|
+
is_async: true,
|
|
284
|
+
cors: None,
|
|
285
|
+
expects_json_body: true,
|
|
286
|
+
#[cfg(feature = "di")]
|
|
287
|
+
handler_dependencies: vec![],
|
|
288
|
+
jsonrpc_method: None,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
let inner_handler = HandlerBuilder::new().build();
|
|
292
|
+
let validator_handler = ValidatingHandler::new(inner_handler, &route);
|
|
293
|
+
|
|
294
|
+
let (request, request_data) = RequestBuilder::new()
|
|
295
|
+
.method(axum::http::Method::POST)
|
|
296
|
+
.json_body(json!({"user": {}}))
|
|
297
|
+
.build();
|
|
298
|
+
|
|
299
|
+
let result = validator_handler.call(request, request_data).await;
|
|
300
|
+
|
|
301
|
+
assert!(result.is_err());
|
|
302
|
+
let (status, body) = result.unwrap_err();
|
|
303
|
+
assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
|
|
304
|
+
|
|
305
|
+
let error: serde_json::Value = serde_json::from_str(&body).unwrap();
|
|
306
|
+
assert!(error["errors"].is_array());
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/// Test 7: Panicking handler returns 500 with structured panic error
|
|
310
|
+
#[tokio::test]
|
|
311
|
+
async fn test_validating_handler_catches_handler_panic() {
|
|
312
|
+
let route = Route {
|
|
313
|
+
method: spikard_core::http::Method::Post,
|
|
314
|
+
path: "/api/test".to_string(),
|
|
315
|
+
handler_name: "test_handler".to_string(),
|
|
316
|
+
request_validator: None,
|
|
317
|
+
response_validator: None,
|
|
318
|
+
parameter_validator: None,
|
|
319
|
+
file_params: None,
|
|
320
|
+
is_async: true,
|
|
321
|
+
cors: None,
|
|
322
|
+
expects_json_body: false,
|
|
323
|
+
#[cfg(feature = "di")]
|
|
324
|
+
handler_dependencies: vec![],
|
|
325
|
+
jsonrpc_method: None,
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
let inner_handler = HandlerBuilder::new().panics().build();
|
|
329
|
+
let validator_handler = ValidatingHandler::new(inner_handler, &route);
|
|
330
|
+
|
|
331
|
+
let (request, request_data) = RequestBuilder::new().method(axum::http::Method::POST).build();
|
|
332
|
+
|
|
333
|
+
let result = validator_handler.call(request, request_data).await;
|
|
334
|
+
|
|
335
|
+
assert!(result.is_err());
|
|
336
|
+
let (status, body) = result.unwrap_err();
|
|
337
|
+
assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
|
|
338
|
+
|
|
339
|
+
let error: serde_json::Value = serde_json::from_str(&body).unwrap();
|
|
340
|
+
assert_eq!(error["code"], "panic");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
#[cfg(feature = "di")]
|
|
345
|
+
mod dependency_injecting_handler {
|
|
346
|
+
use super::*;
|
|
347
|
+
use spikard_core::di::{DependencyContainer, ValueDependency};
|
|
348
|
+
use spikard_http::DependencyInjectingHandler;
|
|
349
|
+
|
|
350
|
+
/// Test 8: Dependencies are resolved and injected into handler
|
|
351
|
+
#[tokio::test]
|
|
352
|
+
async fn test_di_handler_injects_dependencies() {
|
|
353
|
+
let mut container = DependencyContainer::new();
|
|
354
|
+
container
|
|
355
|
+
.register(
|
|
356
|
+
"config".to_string(),
|
|
357
|
+
Arc::new(ValueDependency::new("config", "test_config_value")),
|
|
358
|
+
)
|
|
359
|
+
.unwrap();
|
|
360
|
+
|
|
361
|
+
let inner_handler = HandlerBuilder::new()
|
|
362
|
+
.status(200)
|
|
363
|
+
.json_body(json!({"status": "ok"}))
|
|
364
|
+
.build();
|
|
365
|
+
|
|
366
|
+
let di_handler =
|
|
367
|
+
DependencyInjectingHandler::new(inner_handler, Arc::new(container), vec!["config".to_string()]);
|
|
368
|
+
|
|
369
|
+
let (request, request_data) = RequestBuilder::new().method(axum::http::Method::GET).build();
|
|
370
|
+
|
|
371
|
+
let result = di_handler.call(request, request_data).await;
|
|
372
|
+
|
|
373
|
+
assert!(result.is_ok());
|
|
374
|
+
let response = result.unwrap();
|
|
375
|
+
assert_status(&response, StatusCode::OK);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/// Test 9: Missing dependency returns 500 with resolution error
|
|
379
|
+
#[tokio::test]
|
|
380
|
+
async fn test_di_handler_resolution_failure_returns_500() {
|
|
381
|
+
let container = DependencyContainer::new();
|
|
382
|
+
|
|
383
|
+
let inner_handler = HandlerBuilder::new().build();
|
|
384
|
+
let di_handler =
|
|
385
|
+
DependencyInjectingHandler::new(inner_handler, Arc::new(container), vec!["missing_db".to_string()]);
|
|
386
|
+
|
|
387
|
+
let (request, request_data) = RequestBuilder::new().method(axum::http::Method::GET).build();
|
|
388
|
+
|
|
389
|
+
let result = di_handler.call(request, request_data).await;
|
|
390
|
+
|
|
391
|
+
assert!(result.is_ok());
|
|
392
|
+
let response = result.unwrap();
|
|
393
|
+
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/// Test 10: Multiple dependencies are resolved correctly
|
|
397
|
+
#[tokio::test]
|
|
398
|
+
async fn test_di_handler_multiple_dependencies() {
|
|
399
|
+
let mut container = DependencyContainer::new();
|
|
400
|
+
container
|
|
401
|
+
.register("db".to_string(), Arc::new(ValueDependency::new("db", "postgres")))
|
|
402
|
+
.unwrap();
|
|
403
|
+
container
|
|
404
|
+
.register("cache".to_string(), Arc::new(ValueDependency::new("cache", "redis")))
|
|
405
|
+
.unwrap();
|
|
406
|
+
container
|
|
407
|
+
.register("logger".to_string(), Arc::new(ValueDependency::new("logger", "slog")))
|
|
408
|
+
.unwrap();
|
|
409
|
+
|
|
410
|
+
let inner_handler = HandlerBuilder::new()
|
|
411
|
+
.status(200)
|
|
412
|
+
.json_body(json!({"services": ["db", "cache", "logger"]}))
|
|
413
|
+
.build();
|
|
414
|
+
|
|
415
|
+
let di_handler = DependencyInjectingHandler::new(
|
|
416
|
+
inner_handler,
|
|
417
|
+
Arc::new(container),
|
|
418
|
+
vec!["db".to_string(), "cache".to_string(), "logger".to_string()],
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
let (request, request_data) = RequestBuilder::new().method(axum::http::Method::GET).build();
|
|
422
|
+
|
|
423
|
+
let result = di_handler.call(request, request_data).await;
|
|
424
|
+
|
|
425
|
+
assert!(result.is_ok());
|
|
426
|
+
let response = result.unwrap();
|
|
427
|
+
assert_status(&response, StatusCode::OK);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/// Test 11: Request-scoped dependencies are unique per request
|
|
431
|
+
#[tokio::test]
|
|
432
|
+
async fn test_di_handler_scoped_dependencies() {
|
|
433
|
+
let mut container = DependencyContainer::new();
|
|
434
|
+
container
|
|
435
|
+
.register(
|
|
436
|
+
"request_id".to_string(),
|
|
437
|
+
Arc::new(ValueDependency::new("request_id", "uuid-123")),
|
|
438
|
+
)
|
|
439
|
+
.unwrap();
|
|
440
|
+
|
|
441
|
+
let inner_handler = HandlerBuilder::new().status(200).build();
|
|
442
|
+
let di_handler =
|
|
443
|
+
DependencyInjectingHandler::new(inner_handler, Arc::new(container), vec!["request_id".to_string()]);
|
|
444
|
+
|
|
445
|
+
let (request1, request_data1) = RequestBuilder::new()
|
|
446
|
+
.method(axum::http::Method::GET)
|
|
447
|
+
.path("/request/1")
|
|
448
|
+
.build();
|
|
449
|
+
|
|
450
|
+
let result1 = di_handler.call(request1, request_data1).await;
|
|
451
|
+
assert!(result1.is_ok());
|
|
452
|
+
|
|
453
|
+
let (request2, request_data2) = RequestBuilder::new()
|
|
454
|
+
.method(axum::http::Method::GET)
|
|
455
|
+
.path("/request/2")
|
|
456
|
+
.build();
|
|
457
|
+
|
|
458
|
+
let result2 = di_handler.call(request2, request_data2).await;
|
|
459
|
+
assert!(result2.is_ok());
|
|
460
|
+
|
|
461
|
+
assert_eq!(result1.unwrap().status(), StatusCode::OK);
|
|
462
|
+
assert_eq!(result2.unwrap().status(), StatusCode::OK);
|
|
463
|
+
}
|
|
464
|
+
}
|