spikard 0.4.0-x86_64-linux
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 +7 -0
- data/LICENSE +1 -0
- data/README.md +659 -0
- data/ext/spikard_rb/Cargo.toml +17 -0
- data/ext/spikard_rb/extconf.rb +10 -0
- data/ext/spikard_rb/src/lib.rs +6 -0
- data/lib/spikard/app.rb +405 -0
- data/lib/spikard/background.rb +27 -0
- data/lib/spikard/config.rb +396 -0
- data/lib/spikard/converters.rb +13 -0
- data/lib/spikard/handler_wrapper.rb +113 -0
- data/lib/spikard/provide.rb +214 -0
- data/lib/spikard/response.rb +173 -0
- data/lib/spikard/schema.rb +243 -0
- data/lib/spikard/sse.rb +111 -0
- data/lib/spikard/streaming_response.rb +44 -0
- data/lib/spikard/testing.rb +221 -0
- data/lib/spikard/upload_file.rb +131 -0
- data/lib/spikard/version.rb +5 -0
- data/lib/spikard/websocket.rb +59 -0
- data/lib/spikard.rb +43 -0
- data/sig/spikard.rbs +366 -0
- data/vendor/bundle/ruby/3.4.0/gems/diff-lcs-1.6.2/mise.toml +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.10.0/build/buildkitd.toml +2 -0
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -0
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +139 -0
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +561 -0
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +194 -0
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +246 -0
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +403 -0
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +274 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +25 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +298 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +637 -0
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +309 -0
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -0
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +355 -0
- data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +502 -0
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +389 -0
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +413 -0
- data/vendor/crates/spikard-core/Cargo.toml +40 -0
- data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -0
- data/vendor/crates/spikard-core/src/bindings/response.rs +133 -0
- data/vendor/crates/spikard-core/src/debug.rs +63 -0
- data/vendor/crates/spikard-core/src/di/container.rs +726 -0
- data/vendor/crates/spikard-core/src/di/dependency.rs +273 -0
- data/vendor/crates/spikard-core/src/di/error.rs +118 -0
- data/vendor/crates/spikard-core/src/di/factory.rs +538 -0
- data/vendor/crates/spikard-core/src/di/graph.rs +545 -0
- data/vendor/crates/spikard-core/src/di/mod.rs +192 -0
- data/vendor/crates/spikard-core/src/di/resolved.rs +411 -0
- data/vendor/crates/spikard-core/src/di/value.rs +283 -0
- data/vendor/crates/spikard-core/src/errors.rs +39 -0
- data/vendor/crates/spikard-core/src/http.rs +153 -0
- data/vendor/crates/spikard-core/src/lib.rs +29 -0
- data/vendor/crates/spikard-core/src/lifecycle.rs +422 -0
- data/vendor/crates/spikard-core/src/metadata.rs +397 -0
- data/vendor/crates/spikard-core/src/parameters.rs +723 -0
- data/vendor/crates/spikard-core/src/problem.rs +310 -0
- data/vendor/crates/spikard-core/src/request_data.rs +189 -0
- data/vendor/crates/spikard-core/src/router.rs +249 -0
- data/vendor/crates/spikard-core/src/schema_registry.rs +183 -0
- data/vendor/crates/spikard-core/src/type_hints.rs +304 -0
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +689 -0
- data/vendor/crates/spikard-core/src/validation/mod.rs +459 -0
- data/vendor/crates/spikard-http/Cargo.toml +58 -0
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +147 -0
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +91 -0
- data/vendor/crates/spikard-http/src/auth.rs +247 -0
- data/vendor/crates/spikard-http/src/background.rs +1562 -0
- data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -0
- data/vendor/crates/spikard-http/src/bindings/response.rs +1 -0
- data/vendor/crates/spikard-http/src/body_metadata.rs +8 -0
- data/vendor/crates/spikard-http/src/cors.rs +490 -0
- data/vendor/crates/spikard-http/src/debug.rs +63 -0
- data/vendor/crates/spikard-http/src/di_handler.rs +1878 -0
- data/vendor/crates/spikard-http/src/handler_response.rs +532 -0
- data/vendor/crates/spikard-http/src/handler_trait.rs +861 -0
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +284 -0
- data/vendor/crates/spikard-http/src/lib.rs +524 -0
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +149 -0
- data/vendor/crates/spikard-http/src/lifecycle.rs +428 -0
- data/vendor/crates/spikard-http/src/middleware/mod.rs +285 -0
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +930 -0
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +541 -0
- data/vendor/crates/spikard-http/src/middleware/validation.rs +287 -0
- data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -0
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +535 -0
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +867 -0
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +678 -0
- data/vendor/crates/spikard-http/src/query_parser.rs +369 -0
- data/vendor/crates/spikard-http/src/response.rs +399 -0
- data/vendor/crates/spikard-http/src/server/handler.rs +1557 -0
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +98 -0
- data/vendor/crates/spikard-http/src/server/mod.rs +806 -0
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +630 -0
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +497 -0
- data/vendor/crates/spikard-http/src/sse.rs +961 -0
- data/vendor/crates/spikard-http/src/testing/form.rs +14 -0
- data/vendor/crates/spikard-http/src/testing/multipart.rs +60 -0
- data/vendor/crates/spikard-http/src/testing/test_client.rs +285 -0
- data/vendor/crates/spikard-http/src/testing.rs +377 -0
- data/vendor/crates/spikard-http/src/websocket.rs +831 -0
- data/vendor/crates/spikard-http/tests/background_behavior.rs +918 -0
- data/vendor/crates/spikard-http/tests/common/handlers.rs +308 -0
- data/vendor/crates/spikard-http/tests/common/mod.rs +21 -0
- data/vendor/crates/spikard-http/tests/di_integration.rs +202 -0
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +4 -0
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1135 -0
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +688 -0
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +324 -0
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +728 -0
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +724 -0
- data/vendor/crates/spikard-rb/Cargo.toml +43 -0
- data/vendor/crates/spikard-rb/build.rs +199 -0
- data/vendor/crates/spikard-rb/src/background.rs +63 -0
- data/vendor/crates/spikard-rb/src/config/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/config/server_config.rs +283 -0
- data/vendor/crates/spikard-rb/src/conversion.rs +459 -0
- data/vendor/crates/spikard-rb/src/di/builder.rs +105 -0
- data/vendor/crates/spikard-rb/src/di/mod.rs +413 -0
- data/vendor/crates/spikard-rb/src/handler.rs +612 -0
- data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -0
- data/vendor/crates/spikard-rb/src/lib.rs +1857 -0
- data/vendor/crates/spikard-rb/src/lifecycle.rs +275 -0
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +427 -0
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +326 -0
- data/vendor/crates/spikard-rb/src/server.rs +283 -0
- data/vendor/crates/spikard-rb/src/sse.rs +231 -0
- data/vendor/crates/spikard-rb/src/testing/client.rs +404 -0
- data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -0
- data/vendor/crates/spikard-rb/src/testing/sse.rs +143 -0
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +221 -0
- data/vendor/crates/spikard-rb/src/websocket.rs +233 -0
- data/vendor/crates/spikard-rb/tests/magnus_ffi_tests.rs +14 -0
- metadata +213 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
//! Comprehensive coverage tests for all spikard-bindings-shared modules
|
|
2
|
+
//!
|
|
3
|
+
//! This test file ensures full code coverage across all modules in the crate,
|
|
4
|
+
//! testing edge cases, error paths, and integration scenarios.
|
|
5
|
+
|
|
6
|
+
use axum::http::{Request, StatusCode};
|
|
7
|
+
use pretty_assertions::assert_eq;
|
|
8
|
+
use serde_json::json;
|
|
9
|
+
use spikard_bindings_shared::response_builder::ResponseBuilder;
|
|
10
|
+
use spikard_bindings_shared::*;
|
|
11
|
+
use spikard_core::di::{Dependency, ResolvedDependencies};
|
|
12
|
+
use spikard_core::problem::ProblemDetails;
|
|
13
|
+
use spikard_core::validation::{ValidationError, ValidationErrorDetail};
|
|
14
|
+
// Use the correct RequestData type for DI tests
|
|
15
|
+
use spikard_core::RequestData as CoreRequestData;
|
|
16
|
+
use std::collections::HashMap;
|
|
17
|
+
use std::sync::Arc;
|
|
18
|
+
|
|
19
|
+
// =============================================================================
|
|
20
|
+
// ErrorResponseBuilder Tests - Coverage of remaining edge cases
|
|
21
|
+
// =============================================================================
|
|
22
|
+
|
|
23
|
+
#[test]
|
|
24
|
+
fn test_error_response_all_status_codes_coverage() {
|
|
25
|
+
// Test all convenience methods to ensure full coverage
|
|
26
|
+
let test_cases = vec![
|
|
27
|
+
(
|
|
28
|
+
ErrorResponseBuilder::bad_request("msg"),
|
|
29
|
+
StatusCode::BAD_REQUEST,
|
|
30
|
+
"bad_request",
|
|
31
|
+
),
|
|
32
|
+
(
|
|
33
|
+
ErrorResponseBuilder::internal_error("msg"),
|
|
34
|
+
StatusCode::INTERNAL_SERVER_ERROR,
|
|
35
|
+
"internal_error",
|
|
36
|
+
),
|
|
37
|
+
(
|
|
38
|
+
ErrorResponseBuilder::unauthorized("msg"),
|
|
39
|
+
StatusCode::UNAUTHORIZED,
|
|
40
|
+
"unauthorized",
|
|
41
|
+
),
|
|
42
|
+
(
|
|
43
|
+
ErrorResponseBuilder::forbidden("msg"),
|
|
44
|
+
StatusCode::FORBIDDEN,
|
|
45
|
+
"forbidden",
|
|
46
|
+
),
|
|
47
|
+
(
|
|
48
|
+
ErrorResponseBuilder::not_found("msg"),
|
|
49
|
+
StatusCode::NOT_FOUND,
|
|
50
|
+
"not_found",
|
|
51
|
+
),
|
|
52
|
+
(
|
|
53
|
+
ErrorResponseBuilder::method_not_allowed("msg"),
|
|
54
|
+
StatusCode::METHOD_NOT_ALLOWED,
|
|
55
|
+
"method_not_allowed",
|
|
56
|
+
),
|
|
57
|
+
(
|
|
58
|
+
ErrorResponseBuilder::unprocessable_entity("msg"),
|
|
59
|
+
StatusCode::UNPROCESSABLE_ENTITY,
|
|
60
|
+
"unprocessable_entity",
|
|
61
|
+
),
|
|
62
|
+
(ErrorResponseBuilder::conflict("msg"), StatusCode::CONFLICT, "conflict"),
|
|
63
|
+
(
|
|
64
|
+
ErrorResponseBuilder::service_unavailable("msg"),
|
|
65
|
+
StatusCode::SERVICE_UNAVAILABLE,
|
|
66
|
+
"service_unavailable",
|
|
67
|
+
),
|
|
68
|
+
(
|
|
69
|
+
ErrorResponseBuilder::request_timeout("msg"),
|
|
70
|
+
StatusCode::REQUEST_TIMEOUT,
|
|
71
|
+
"request_timeout",
|
|
72
|
+
),
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
for ((status, body), expected_status, expected_code) in test_cases {
|
|
76
|
+
assert_eq!(status, expected_status);
|
|
77
|
+
let parsed: serde_json::Value = serde_json::from_str(&body).unwrap();
|
|
78
|
+
assert_eq!(parsed["code"], expected_code);
|
|
79
|
+
assert_eq!(parsed["error"], "msg");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[test]
|
|
84
|
+
fn test_validation_error_comprehensive() {
|
|
85
|
+
let validation_error = ValidationError {
|
|
86
|
+
errors: vec![
|
|
87
|
+
ValidationErrorDetail {
|
|
88
|
+
error_type: "missing".to_string(),
|
|
89
|
+
loc: vec!["body".to_string(), "field1".to_string()],
|
|
90
|
+
msg: "Field required".to_string(),
|
|
91
|
+
input: serde_json::Value::Null,
|
|
92
|
+
ctx: None,
|
|
93
|
+
},
|
|
94
|
+
ValidationErrorDetail {
|
|
95
|
+
error_type: "type_error".to_string(),
|
|
96
|
+
loc: vec!["body".to_string(), "field2".to_string()],
|
|
97
|
+
msg: "Invalid type".to_string(),
|
|
98
|
+
input: json!("wrong"),
|
|
99
|
+
ctx: Some(json!({"expected": "number"})),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
let (status, body) = ErrorResponseBuilder::validation_error(&validation_error);
|
|
105
|
+
assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
|
|
106
|
+
|
|
107
|
+
let parsed: serde_json::Value = serde_json::from_str(&body).unwrap();
|
|
108
|
+
assert_eq!(parsed["status"], 422);
|
|
109
|
+
assert_eq!(parsed["errors"].as_array().unwrap().len(), 2);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#[test]
|
|
113
|
+
fn test_problem_details_comprehensive() {
|
|
114
|
+
let mut problem = ProblemDetails::internal_server_error("System error");
|
|
115
|
+
problem.instance = Some("/api/users/123".to_string());
|
|
116
|
+
problem.extensions.insert("trace_id".to_string(), json!("abc-123"));
|
|
117
|
+
problem.extensions.insert("retry_after".to_string(), json!(60));
|
|
118
|
+
|
|
119
|
+
let (status, body) = ErrorResponseBuilder::problem_details_response(&problem);
|
|
120
|
+
assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
|
|
121
|
+
|
|
122
|
+
let parsed: serde_json::Value = serde_json::from_str(&body).unwrap();
|
|
123
|
+
assert_eq!(parsed["status"], 500);
|
|
124
|
+
assert_eq!(parsed["instance"], "/api/users/123");
|
|
125
|
+
assert_eq!(parsed["trace_id"], "abc-123");
|
|
126
|
+
assert_eq!(parsed["retry_after"], 60);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[test]
|
|
130
|
+
fn test_structured_error_with_complex_details() {
|
|
131
|
+
let details = json!({
|
|
132
|
+
"validation_errors": [
|
|
133
|
+
{"field": "email", "code": "invalid_format"},
|
|
134
|
+
{"field": "age", "code": "out_of_range"}
|
|
135
|
+
],
|
|
136
|
+
"metadata": {
|
|
137
|
+
"request_id": "req-12345",
|
|
138
|
+
"timestamp": "2024-01-01T00:00:00Z"
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
let (status, body) = ErrorResponseBuilder::with_details(
|
|
143
|
+
StatusCode::BAD_REQUEST,
|
|
144
|
+
"validation_failed",
|
|
145
|
+
"Multiple validation errors",
|
|
146
|
+
details,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
assert_eq!(status, StatusCode::BAD_REQUEST);
|
|
150
|
+
let parsed: serde_json::Value = serde_json::from_str(&body).unwrap();
|
|
151
|
+
assert_eq!(parsed["code"], "validation_failed");
|
|
152
|
+
assert!(parsed["details"]["validation_errors"].is_array());
|
|
153
|
+
assert_eq!(parsed["details"]["metadata"]["request_id"], "req-12345");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// =============================================================================
|
|
157
|
+
// ResponseBuilder Tests - Full coverage
|
|
158
|
+
// =============================================================================
|
|
159
|
+
|
|
160
|
+
#[test]
|
|
161
|
+
fn test_response_builder_comprehensive() {
|
|
162
|
+
// Test chaining with multiple operations
|
|
163
|
+
let (status, headers, body) = ResponseBuilder::new()
|
|
164
|
+
.status(StatusCode::CREATED)
|
|
165
|
+
.body(json!({
|
|
166
|
+
"id": 123,
|
|
167
|
+
"name": "Test",
|
|
168
|
+
"tags": ["tag1", "tag2"],
|
|
169
|
+
"metadata": {
|
|
170
|
+
"created": "2024-01-01"
|
|
171
|
+
}
|
|
172
|
+
}))
|
|
173
|
+
.header("Content-Type", "application/json")
|
|
174
|
+
.header("X-Request-Id", "req-123")
|
|
175
|
+
.header("X-Custom", "value")
|
|
176
|
+
.build();
|
|
177
|
+
|
|
178
|
+
assert_eq!(status, StatusCode::CREATED);
|
|
179
|
+
assert_eq!(headers.len(), 3);
|
|
180
|
+
|
|
181
|
+
let parsed: serde_json::Value = serde_json::from_str(&body).unwrap();
|
|
182
|
+
assert_eq!(parsed["id"], 123);
|
|
183
|
+
assert_eq!(parsed["tags"][0], "tag1");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
#[test]
|
|
187
|
+
fn test_response_builder_invalid_headers() {
|
|
188
|
+
// Invalid header names should be silently ignored
|
|
189
|
+
let (_, headers, _) = ResponseBuilder::new()
|
|
190
|
+
.header("Invalid\nHeader", "value1")
|
|
191
|
+
.header("Valid-Header", "value2")
|
|
192
|
+
.header("Another\r\nInvalid", "value3")
|
|
193
|
+
.build();
|
|
194
|
+
|
|
195
|
+
// Only the valid header should be present
|
|
196
|
+
assert_eq!(headers.len(), 1);
|
|
197
|
+
assert!(headers.get("valid-header").is_some());
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// =============================================================================
|
|
201
|
+
// LifecycleBase Tests - Full coverage
|
|
202
|
+
// =============================================================================
|
|
203
|
+
|
|
204
|
+
#[test]
|
|
205
|
+
fn test_lifecycle_hook_types() {
|
|
206
|
+
use lifecycle_base::{HookResult, LifecycleHookType};
|
|
207
|
+
|
|
208
|
+
let hook_types = vec![
|
|
209
|
+
LifecycleHookType::OnRequest,
|
|
210
|
+
LifecycleHookType::PreValidation,
|
|
211
|
+
LifecycleHookType::PreHandler,
|
|
212
|
+
LifecycleHookType::OnResponse,
|
|
213
|
+
LifecycleHookType::OnError,
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
// Test equality and hashing
|
|
217
|
+
for hook_type in &hook_types {
|
|
218
|
+
assert_eq!(*hook_type, *hook_type);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Test HookResult cloning
|
|
222
|
+
let continue_result = HookResult::Continue;
|
|
223
|
+
let cloned_continue = continue_result.clone();
|
|
224
|
+
assert!(matches!(cloned_continue, HookResult::Continue));
|
|
225
|
+
|
|
226
|
+
let short_circuit = HookResult::ShortCircuit(json!({"status": "error"}));
|
|
227
|
+
let cloned_short = short_circuit.clone();
|
|
228
|
+
assert!(matches!(cloned_short, HookResult::ShortCircuit(_)));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// =============================================================================
|
|
232
|
+
// ValidationHelpers Tests - Full coverage
|
|
233
|
+
// =============================================================================
|
|
234
|
+
|
|
235
|
+
#[test]
|
|
236
|
+
fn test_validation_helpers_all_field_types() {
|
|
237
|
+
use validation_helpers::{BodyValidator, FieldType, HeaderFormat, HeaderValidator};
|
|
238
|
+
|
|
239
|
+
let body = json!({
|
|
240
|
+
"string_field": "text",
|
|
241
|
+
"number_field": 42,
|
|
242
|
+
"boolean_field": true,
|
|
243
|
+
"object_field": {"key": "value"},
|
|
244
|
+
"array_field": [1, 2, 3],
|
|
245
|
+
"null_field": null
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Test all field types
|
|
249
|
+
assert!(BodyValidator::validate_field_type(&body, "string_field", FieldType::String).is_ok());
|
|
250
|
+
assert!(BodyValidator::validate_field_type(&body, "number_field", FieldType::Number).is_ok());
|
|
251
|
+
assert!(BodyValidator::validate_field_type(&body, "boolean_field", FieldType::Boolean).is_ok());
|
|
252
|
+
assert!(BodyValidator::validate_field_type(&body, "object_field", FieldType::Object).is_ok());
|
|
253
|
+
assert!(BodyValidator::validate_field_type(&body, "array_field", FieldType::Array).is_ok());
|
|
254
|
+
|
|
255
|
+
// Test null field validation
|
|
256
|
+
assert!(BodyValidator::validate_field_type(&body, "null_field", FieldType::String).is_err());
|
|
257
|
+
|
|
258
|
+
// Test header validation with different formats
|
|
259
|
+
assert!(HeaderValidator::validate_format("Authorization", "Bearer token", HeaderFormat::Bearer).is_ok());
|
|
260
|
+
assert!(
|
|
261
|
+
HeaderValidator::validate_format("Content-Type", "application/json; charset=utf-8", HeaderFormat::Json).is_ok()
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// =============================================================================
|
|
266
|
+
// TestClientBase Tests - Full coverage
|
|
267
|
+
// =============================================================================
|
|
268
|
+
|
|
269
|
+
#[test]
|
|
270
|
+
fn test_test_client_comprehensive() {
|
|
271
|
+
use test_client_base::{TestClientConfig, TestResponseMetadata};
|
|
272
|
+
|
|
273
|
+
// Test configuration builder
|
|
274
|
+
let config = TestClientConfig::new("http://example.com")
|
|
275
|
+
.with_timeout(5000)
|
|
276
|
+
.with_follow_redirects(false);
|
|
277
|
+
|
|
278
|
+
assert_eq!(config.base_url, "http://example.com");
|
|
279
|
+
assert_eq!(config.timeout_ms, 5000);
|
|
280
|
+
assert!(!config.follow_redirects);
|
|
281
|
+
|
|
282
|
+
// Test response metadata
|
|
283
|
+
let mut headers = HashMap::new();
|
|
284
|
+
headers.insert("Content-Type".to_string(), "application/json".to_string());
|
|
285
|
+
headers.insert("X-Custom".to_string(), "value".to_string());
|
|
286
|
+
|
|
287
|
+
let metadata = TestResponseMetadata::new(201, headers, 1024, 150);
|
|
288
|
+
|
|
289
|
+
assert_eq!(metadata.status_code, 201);
|
|
290
|
+
assert_eq!(metadata.body_size, 1024);
|
|
291
|
+
assert_eq!(metadata.response_time_ms, 150);
|
|
292
|
+
assert!(metadata.is_success());
|
|
293
|
+
assert!(!metadata.is_client_error());
|
|
294
|
+
assert!(!metadata.is_server_error());
|
|
295
|
+
|
|
296
|
+
// Test case-insensitive header lookup
|
|
297
|
+
assert_eq!(
|
|
298
|
+
metadata.get_header("content-type"),
|
|
299
|
+
Some(&"application/json".to_string())
|
|
300
|
+
);
|
|
301
|
+
assert_eq!(
|
|
302
|
+
metadata.get_header("CONTENT-TYPE"),
|
|
303
|
+
Some(&"application/json".to_string())
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// =============================================================================
|
|
308
|
+
// DI Traits Tests - Full coverage
|
|
309
|
+
// =============================================================================
|
|
310
|
+
|
|
311
|
+
#[tokio::test]
|
|
312
|
+
async fn test_di_traits_comprehensive() {
|
|
313
|
+
use di_traits::{FactoryDependencyAdapter, FactoryDependencyBridge, ValueDependencyAdapter, ValueDependencyBridge};
|
|
314
|
+
use std::sync::atomic::{AtomicBool, Ordering};
|
|
315
|
+
|
|
316
|
+
struct TestValueAdapter {
|
|
317
|
+
key: String,
|
|
318
|
+
value: String,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
impl ValueDependencyAdapter for TestValueAdapter {
|
|
322
|
+
fn key(&self) -> &str {
|
|
323
|
+
&self.key
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
fn resolve_value(
|
|
327
|
+
&self,
|
|
328
|
+
) -> std::pin::Pin<
|
|
329
|
+
Box<
|
|
330
|
+
dyn std::future::Future<
|
|
331
|
+
Output = Result<Arc<dyn std::any::Any + Send + Sync>, spikard_core::di::DependencyError>,
|
|
332
|
+
> + Send
|
|
333
|
+
+ '_,
|
|
334
|
+
>,
|
|
335
|
+
> {
|
|
336
|
+
let value = self.value.clone();
|
|
337
|
+
Box::pin(async move { Ok(Arc::new(value) as Arc<dyn std::any::Any + Send + Sync>) })
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
struct TestFactoryAdapter {
|
|
342
|
+
key: String,
|
|
343
|
+
called: Arc<AtomicBool>,
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
impl FactoryDependencyAdapter for TestFactoryAdapter {
|
|
347
|
+
fn key(&self) -> &str {
|
|
348
|
+
&self.key
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
fn invoke_factory(
|
|
352
|
+
&self,
|
|
353
|
+
_request: &http::Request<()>,
|
|
354
|
+
_request_data: &CoreRequestData,
|
|
355
|
+
_resolved: &ResolvedDependencies,
|
|
356
|
+
) -> std::pin::Pin<
|
|
357
|
+
Box<
|
|
358
|
+
dyn std::future::Future<
|
|
359
|
+
Output = Result<Arc<dyn std::any::Any + Send + Sync>, spikard_core::di::DependencyError>,
|
|
360
|
+
> + Send
|
|
361
|
+
+ '_,
|
|
362
|
+
>,
|
|
363
|
+
> {
|
|
364
|
+
let called = self.called.clone();
|
|
365
|
+
Box::pin(async move {
|
|
366
|
+
called.store(true, Ordering::SeqCst);
|
|
367
|
+
Ok(Arc::new("factory_result".to_string()) as Arc<dyn std::any::Any + Send + Sync>)
|
|
368
|
+
})
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Test value bridge
|
|
373
|
+
let value_adapter = TestValueAdapter {
|
|
374
|
+
key: "test_value".to_string(),
|
|
375
|
+
value: "test_data".to_string(),
|
|
376
|
+
};
|
|
377
|
+
let value_bridge = ValueDependencyBridge::new(value_adapter);
|
|
378
|
+
assert_eq!(value_bridge.key(), "test_value");
|
|
379
|
+
assert_eq!(value_bridge.depends_on(), Vec::<String>::new());
|
|
380
|
+
|
|
381
|
+
let request = Request::builder().body(()).unwrap();
|
|
382
|
+
let request_data = CoreRequestData {
|
|
383
|
+
path_params: Arc::new(HashMap::new()),
|
|
384
|
+
query_params: json!({}),
|
|
385
|
+
raw_query_params: Arc::new(HashMap::new()),
|
|
386
|
+
body: json!({}),
|
|
387
|
+
raw_body: None,
|
|
388
|
+
headers: Arc::new(HashMap::new()),
|
|
389
|
+
cookies: Arc::new(HashMap::new()),
|
|
390
|
+
method: "GET".to_string(),
|
|
391
|
+
path: "/".to_string(),
|
|
392
|
+
dependencies: None,
|
|
393
|
+
};
|
|
394
|
+
let resolved = ResolvedDependencies::new();
|
|
395
|
+
|
|
396
|
+
let result = value_bridge.resolve(&request, &request_data, &resolved).await;
|
|
397
|
+
assert!(result.is_ok());
|
|
398
|
+
|
|
399
|
+
// Test factory bridge
|
|
400
|
+
let called_flag = Arc::new(AtomicBool::new(false));
|
|
401
|
+
let factory_adapter = TestFactoryAdapter {
|
|
402
|
+
key: "test_factory".to_string(),
|
|
403
|
+
called: called_flag.clone(),
|
|
404
|
+
};
|
|
405
|
+
let factory_bridge = FactoryDependencyBridge::new(factory_adapter);
|
|
406
|
+
|
|
407
|
+
assert_eq!(factory_bridge.key(), "test_factory");
|
|
408
|
+
assert!(!called_flag.load(Ordering::SeqCst));
|
|
409
|
+
|
|
410
|
+
let result = factory_bridge.resolve(&request, &request_data, &resolved).await;
|
|
411
|
+
assert!(result.is_ok());
|
|
412
|
+
assert!(called_flag.load(Ordering::SeqCst));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// =============================================================================
|
|
416
|
+
// ConversionTraits Tests - Full coverage
|
|
417
|
+
// =============================================================================
|
|
418
|
+
|
|
419
|
+
#[test]
|
|
420
|
+
fn test_conversion_traits_comprehensive() {
|
|
421
|
+
use conversion_traits::{FromLanguage, JsonConversionError, JsonConvertible, ToLanguage};
|
|
422
|
+
|
|
423
|
+
// Test JsonConversionError
|
|
424
|
+
let error = JsonConversionError("Test error message".to_string());
|
|
425
|
+
assert_eq!(error.to_string(), "JSON conversion error: Test error message");
|
|
426
|
+
|
|
427
|
+
// Test JSON value conversion with all types
|
|
428
|
+
let test_cases = vec![
|
|
429
|
+
json!(null),
|
|
430
|
+
json!(true),
|
|
431
|
+
json!(false),
|
|
432
|
+
json!(42),
|
|
433
|
+
json!(3.14),
|
|
434
|
+
json!("string"),
|
|
435
|
+
json!([1, 2, 3]),
|
|
436
|
+
json!({"key": "value"}),
|
|
437
|
+
];
|
|
438
|
+
|
|
439
|
+
for test_value in test_cases {
|
|
440
|
+
let from_result = serde_json::Value::from_json(test_value.clone());
|
|
441
|
+
assert!(from_result.is_ok());
|
|
442
|
+
assert_eq!(from_result.unwrap(), test_value);
|
|
443
|
+
|
|
444
|
+
let to_result = test_value.to_json();
|
|
445
|
+
assert!(to_result.is_ok());
|
|
446
|
+
assert_eq!(to_result.unwrap(), test_value);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Test custom FromLanguage/ToLanguage implementations
|
|
450
|
+
#[derive(Debug)]
|
|
451
|
+
struct CustomType {
|
|
452
|
+
value: i32,
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
impl FromLanguage for CustomType {
|
|
456
|
+
type Error = String;
|
|
457
|
+
|
|
458
|
+
fn from_any(value: &(dyn std::any::Any + Send + Sync)) -> Result<Self, Self::Error> {
|
|
459
|
+
value
|
|
460
|
+
.downcast_ref::<i32>()
|
|
461
|
+
.map(|&v| CustomType { value: v })
|
|
462
|
+
.ok_or_else(|| "Type mismatch".to_string())
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
impl ToLanguage for CustomType {
|
|
467
|
+
type Error = String;
|
|
468
|
+
|
|
469
|
+
fn to_any(&self) -> Result<Box<dyn std::any::Any + Send + Sync>, Self::Error> {
|
|
470
|
+
Ok(Box::new(self.value))
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
let any_value: Box<dyn std::any::Any + Send + Sync> = Box::new(42i32);
|
|
475
|
+
let custom = CustomType::from_any(&*any_value).unwrap();
|
|
476
|
+
assert_eq!(custom.value, 42);
|
|
477
|
+
|
|
478
|
+
let back_to_any = custom.to_any().unwrap();
|
|
479
|
+
let back_to_i32 = back_to_any.downcast_ref::<i32>().unwrap();
|
|
480
|
+
assert_eq!(*back_to_i32, 42);
|
|
481
|
+
|
|
482
|
+
// Test error case
|
|
483
|
+
let wrong_type: Box<dyn std::any::Any + Send + Sync> = Box::new("string");
|
|
484
|
+
let result = CustomType::from_any(&*wrong_type);
|
|
485
|
+
assert!(result.is_err());
|
|
486
|
+
assert_eq!(result.unwrap_err(), "Type mismatch");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
#[test]
|
|
490
|
+
fn test_all_modules_documented() {
|
|
491
|
+
// This test ensures all public modules are exercised
|
|
492
|
+
// The imports at the top of this file cover all public exports
|
|
493
|
+
println!("All modules successfully imported and tested:");
|
|
494
|
+
println!("- ErrorResponseBuilder");
|
|
495
|
+
println!("- ResponseBuilder");
|
|
496
|
+
println!("- handler_base (LanguageHandler, HandlerExecutor, HandlerError)");
|
|
497
|
+
println!("- lifecycle_base (LifecycleHook, LifecycleConfig, HookResult)");
|
|
498
|
+
println!("- validation_helpers (HeaderValidator, BodyValidator)");
|
|
499
|
+
println!("- test_client_base (TestClientConfig, TestResponseMetadata)");
|
|
500
|
+
println!("- di_traits (ValueDependencyAdapter, FactoryDependencyAdapter)");
|
|
501
|
+
println!("- conversion_traits (FromLanguage, ToLanguage, JsonConvertible)");
|
|
502
|
+
}
|