swadhinbiswas 🚀 release: 2.0.8 — write-path fix (ADR-080), fork-safe GIL pool (ADR-081), HTTP/3 (ADR-082), Security DI fix, docs honesty pass · 8/6/2026
731fffe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
//! Integration tests for edge cases and security hardening.
use hyper::StatusCode;
use justapi_core::testing::TestClient;
use justapi_core::{json_response, middleware::HandlerFn, router::Router};
use std::sync::Arc;
fn test_handler() -> HandlerFn {
Arc::new(|_| Box::pin(async { Ok(json_response(StatusCode::OK, r#"{"ok":true}"#)) }))
}
#[tokio::test]
async fn test_oversized_path_rejected() {
let mut router = Router::new();
router.insert(hyper::Method::GET, "/", test_handler()).unwrap();
let client = TestClient::new(Arc::new(move |req| {
let chain = justapi_core::middleware::MiddlewareChain::new(test_handler());
Box::pin(async move {
let path = req.uri().path().to_string();
if path.len() > 8192 {
return Ok(justapi_core::error_response(
hyper::StatusCode::URI_TOO_LONG,
"request URI exceeds maximum length",
));
}
chain.run(req).await
})
}));
// Normal path should work
let resp = client.get("/").await.unwrap();
assert_eq!(resp.status, 200);
// Oversized path should be rejected
let oversized_path = format!("/{}", "a".repeat(9000));
let resp = client.get(&oversized_path).await.unwrap();
assert_eq!(resp.status, 414);
}
#[tokio::test]
async fn test_oversized_header_rejected() {
let client = TestClient::new(Arc::new(move |req| {
let chain = justapi_core::middleware::MiddlewareChain::new(test_handler());
Box::pin(async move {
// Check header count
if req.headers().len() > 100 {
return Ok(justapi_core::error_response(
hyper::StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
"too many request headers",
));
}
// Check header value length
for (name, value) in req.headers().iter() {
if value.len() > 8192 {
return Ok(justapi_core::error_response(
hyper::StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
&format!("header '{}' exceeds maximum value length", name.as_str()),
));
}
}
chain.run(req).await
})
}));
// Normal request should work
let resp = client.get("/").await.unwrap();
assert_eq!(resp.status, 200);
}
#[tokio::test]
#[cfg(feature = "db")]
async fn test_sql_identifier_validation() {
use justapi_core::server::{CrudOp, CrudSpec};
// Valid identifiers
let spec = CrudSpec {
op: CrudOp::Insert,
table: "users".to_string(),
columns: vec!["name".to_string(), "email".to_string()],
id_column: "id".to_string(),
};
assert!(spec.validate().is_ok());
// Invalid table name (SQL injection attempt)
let spec = CrudSpec {
op: CrudOp::Insert,
table: "users; DROP TABLE users--".to_string(),
columns: vec!["name".to_string()],
id_column: "id".to_string(),
};
assert!(spec.validate().is_err());
// Invalid column name
let spec = CrudSpec {
op: CrudOp::Insert,
table: "users".to_string(),
columns: vec!["name OR 1=1".to_string()],
id_column: "id".to_string(),
};
assert!(spec.validate().is_err());
// Empty table name
let spec = CrudSpec {
op: CrudOp::Insert,
table: "".to_string(),
columns: vec!["name".to_string()],
id_column: "id".to_string(),
};
assert!(spec.validate().is_err());
// Table starting with number
let spec = CrudSpec {
op: CrudOp::Insert,
table: "1users".to_string(),
columns: vec!["name".to_string()],
id_column: "id".to_string(),
};
assert!(spec.validate().is_err());
}
#[tokio::test]
async fn test_chaos_middleware_config_validation() {
use justapi_core::resilience::{ChaosConfig, ChaosMiddleware};
// Valid config
let config = ChaosConfig {
enabled: true,
latency_p: 0.5,
latency_min_ms: 100,
latency_max_ms: 500,
error_p: 0.1,
error_status: StatusCode::INTERNAL_SERVER_ERROR,
};
let _mw = ChaosMiddleware::new(config);
// Invalid config (min > max) - should not panic
let config = ChaosConfig {
enabled: true,
latency_p: 0.5,
latency_min_ms: 500,
latency_max_ms: 100,
error_p: 0.1,
error_status: StatusCode::INTERNAL_SERVER_ERROR,
};
let _mw = ChaosMiddleware::new(config);
}
#[tokio::test]
async fn test_health_check_timeout() {
use justapi_core::health::{HealthRegistry, HealthStatus};
let mut registry = HealthRegistry::new();
// Fast check
registry.register_fn("fast", || async { HealthStatus::Healthy });
// Slow check (simulated)
registry.register_fn("slow", || async {
tokio::time::sleep(std::time::Duration::from_millis(200)).await;
HealthStatus::Healthy
});
let report = registry.check_all_with_timeout(std::time::Duration::from_millis(50)).await;
// Fast check should pass
let fast = report.components.iter().find(|c| c.name == "fast");
assert!(fast.is_some());
assert_eq!(fast.unwrap().status, HealthStatus::Healthy);
// Slow check should timeout
let slow = report.components.iter().find(|c| c.name == "slow");
assert!(slow.is_some());
match &slow.unwrap().status {
HealthStatus::Unhealthy(msg) => {
assert!(msg.contains("timed out"));
}
_ => panic!("Expected Unhealthy status for slow check"),
}
}
#[tokio::test]
async fn test_router_cache_eviction() {
let mut router = Router::new();
// Insert many routes to trigger eviction
for i in 0..20000 {
router
.insert(hyper::Method::GET, &format!("/route/{}", i), format!("handler_{}", i))
.unwrap();
}
// Verify routes still work
let result = router.resolve(&hyper::Method::GET, "/route/0");
assert!(result.is_ok());
let result = router.resolve(&hyper::Method::GET, "/route/19999");
assert!(result.is_ok());
}