spikard 0.7.4 → 0.8.0

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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/ext/spikard_rb/Cargo.lock +583 -201
  3. data/ext/spikard_rb/Cargo.toml +1 -1
  4. data/lib/spikard/grpc.rb +182 -0
  5. data/lib/spikard/version.rb +1 -1
  6. data/lib/spikard.rb +1 -1
  7. data/vendor/crates/spikard-bindings-shared/Cargo.toml +2 -1
  8. data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +197 -0
  9. data/vendor/crates/spikard-bindings-shared/src/lib.rs +2 -0
  10. data/vendor/crates/spikard-core/Cargo.toml +1 -1
  11. data/vendor/crates/spikard-http/Cargo.toml +5 -1
  12. data/vendor/crates/spikard-http/src/grpc/handler.rs +260 -0
  13. data/vendor/crates/spikard-http/src/grpc/mod.rs +342 -0
  14. data/vendor/crates/spikard-http/src/grpc/service.rs +392 -0
  15. data/vendor/crates/spikard-http/src/grpc/streaming.rs +237 -0
  16. data/vendor/crates/spikard-http/src/lib.rs +14 -0
  17. data/vendor/crates/spikard-http/src/server/grpc_routing.rs +288 -0
  18. data/vendor/crates/spikard-http/src/server/mod.rs +1 -0
  19. data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +1023 -0
  20. data/vendor/crates/spikard-http/tests/common/mod.rs +8 -0
  21. data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +653 -0
  22. data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +332 -0
  23. data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +518 -0
  24. data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +476 -0
  25. data/vendor/crates/spikard-rb/Cargo.toml +2 -1
  26. data/vendor/crates/spikard-rb/src/config/server_config.rs +1 -0
  27. data/vendor/crates/spikard-rb/src/grpc/handler.rs +352 -0
  28. data/vendor/crates/spikard-rb/src/grpc/mod.rs +9 -0
  29. data/vendor/crates/spikard-rb/src/lib.rs +4 -0
  30. data/vendor/crates/spikard-rb-macros/Cargo.toml +1 -1
  31. metadata +15 -1
@@ -0,0 +1,332 @@
1
+ //! Integration tests for gRPC runtime support
2
+ //!
3
+ //! These tests verify that the gRPC infrastructure works end-to-end,
4
+ //! including handler registration, request routing, and response handling.
5
+
6
+ use bytes::Bytes;
7
+ use spikard_http::grpc::{GrpcConfig, GrpcHandler, GrpcHandlerResult, GrpcRegistry, GrpcRequestData, GrpcResponseData};
8
+ use std::future::Future;
9
+ use std::pin::Pin;
10
+ use std::sync::Arc;
11
+
12
+ /// Test handler that echoes the request payload
13
+ struct EchoGrpcHandler;
14
+
15
+ impl GrpcHandler for EchoGrpcHandler {
16
+ fn call(&self, request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
17
+ Box::pin(async move {
18
+ Ok(GrpcResponseData {
19
+ payload: request.payload,
20
+ metadata: tonic::metadata::MetadataMap::new(),
21
+ })
22
+ })
23
+ }
24
+
25
+ fn service_name(&self) -> &'static str {
26
+ "test.EchoService"
27
+ }
28
+ }
29
+
30
+ /// Test handler that returns a fixed response
31
+ struct FixedResponseHandler {
32
+ response: Bytes,
33
+ }
34
+
35
+ impl GrpcHandler for FixedResponseHandler {
36
+ fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
37
+ let response = self.response.clone();
38
+ Box::pin(async move {
39
+ Ok(GrpcResponseData {
40
+ payload: response,
41
+ metadata: tonic::metadata::MetadataMap::new(),
42
+ })
43
+ })
44
+ }
45
+
46
+ fn service_name(&self) -> &'static str {
47
+ "test.FixedService"
48
+ }
49
+ }
50
+
51
+ /// Test handler that returns an error
52
+ struct ErrorGrpcHandler;
53
+
54
+ impl GrpcHandler for ErrorGrpcHandler {
55
+ fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
56
+ Box::pin(async { Err(tonic::Status::not_found("Resource not found")) })
57
+ }
58
+
59
+ fn service_name(&self) -> &'static str {
60
+ "test.ErrorService"
61
+ }
62
+ }
63
+
64
+ #[test]
65
+ fn test_grpc_config_creation() {
66
+ let config = GrpcConfig::default();
67
+ assert!(config.enabled);
68
+ assert_eq!(config.max_message_size, 4 * 1024 * 1024);
69
+ assert!(config.enable_compression);
70
+ assert!(config.request_timeout.is_none());
71
+ }
72
+
73
+ #[test]
74
+ fn test_grpc_config_customization() {
75
+ let config = GrpcConfig {
76
+ enabled: true,
77
+ max_message_size: 1024,
78
+ enable_compression: false,
79
+ request_timeout: Some(30),
80
+ max_concurrent_streams: 50,
81
+ enable_keepalive: false,
82
+ keepalive_interval: 60,
83
+ keepalive_timeout: 10,
84
+ };
85
+
86
+ assert_eq!(config.max_message_size, 1024);
87
+ assert!(!config.enable_compression);
88
+ assert_eq!(config.request_timeout, Some(30));
89
+ assert_eq!(config.max_concurrent_streams, 50);
90
+ assert!(!config.enable_keepalive);
91
+ }
92
+
93
+ #[test]
94
+ fn test_grpc_registry_creation() {
95
+ let registry = GrpcRegistry::new();
96
+ assert!(registry.is_empty());
97
+ assert_eq!(registry.len(), 0);
98
+ }
99
+
100
+ #[test]
101
+ fn test_grpc_registry_register_handler() {
102
+ let mut registry = GrpcRegistry::new();
103
+ let handler = Arc::new(EchoGrpcHandler);
104
+
105
+ registry.register("test.EchoService", handler);
106
+
107
+ assert!(!registry.is_empty());
108
+ assert_eq!(registry.len(), 1);
109
+ assert!(registry.contains("test.EchoService"));
110
+ }
111
+
112
+ #[test]
113
+ fn test_grpc_registry_get_handler() {
114
+ let mut registry = GrpcRegistry::new();
115
+ let handler = Arc::new(EchoGrpcHandler);
116
+
117
+ registry.register("test.EchoService", handler);
118
+
119
+ let retrieved = registry.get("test.EchoService");
120
+ assert!(retrieved.is_some());
121
+ assert_eq!(retrieved.unwrap().service_name(), "test.EchoService");
122
+ }
123
+
124
+ #[test]
125
+ fn test_grpc_registry_multiple_handlers() {
126
+ let mut registry = GrpcRegistry::new();
127
+
128
+ registry.register("test.EchoService", Arc::new(EchoGrpcHandler));
129
+ registry.register(
130
+ "test.FixedService",
131
+ Arc::new(FixedResponseHandler {
132
+ response: Bytes::from("fixed"),
133
+ }),
134
+ );
135
+ registry.register("test.ErrorService", Arc::new(ErrorGrpcHandler));
136
+
137
+ assert_eq!(registry.len(), 3);
138
+ assert!(registry.contains("test.EchoService"));
139
+ assert!(registry.contains("test.FixedService"));
140
+ assert!(registry.contains("test.ErrorService"));
141
+
142
+ let names = registry.service_names();
143
+ assert_eq!(names.len(), 3);
144
+ }
145
+
146
+ #[tokio::test]
147
+ async fn test_echo_handler_basic() {
148
+ let handler = EchoGrpcHandler;
149
+ let request = GrpcRequestData {
150
+ service_name: "test.EchoService".to_string(),
151
+ method_name: "Echo".to_string(),
152
+ payload: Bytes::from("test payload"),
153
+ metadata: tonic::metadata::MetadataMap::new(),
154
+ };
155
+
156
+ let result = handler.call(request).await;
157
+ assert!(result.is_ok());
158
+
159
+ let response = result.unwrap();
160
+ assert_eq!(response.payload, Bytes::from("test payload"));
161
+ }
162
+
163
+ #[tokio::test]
164
+ async fn test_echo_handler_empty_payload() {
165
+ let handler = EchoGrpcHandler;
166
+ let request = GrpcRequestData {
167
+ service_name: "test.EchoService".to_string(),
168
+ method_name: "Echo".to_string(),
169
+ payload: Bytes::new(),
170
+ metadata: tonic::metadata::MetadataMap::new(),
171
+ };
172
+
173
+ let result = handler.call(request).await;
174
+ assert!(result.is_ok());
175
+
176
+ let response = result.unwrap();
177
+ assert_eq!(response.payload, Bytes::new());
178
+ }
179
+
180
+ #[tokio::test]
181
+ async fn test_echo_handler_large_payload() {
182
+ let handler = EchoGrpcHandler;
183
+ let large_data = vec![0u8; 10_000];
184
+ let request = GrpcRequestData {
185
+ service_name: "test.EchoService".to_string(),
186
+ method_name: "Echo".to_string(),
187
+ payload: Bytes::from(large_data.clone()),
188
+ metadata: tonic::metadata::MetadataMap::new(),
189
+ };
190
+
191
+ let result = handler.call(request).await;
192
+ assert!(result.is_ok());
193
+
194
+ let response = result.unwrap();
195
+ assert_eq!(response.payload.len(), 10_000);
196
+ assert_eq!(response.payload, Bytes::from(large_data));
197
+ }
198
+
199
+ #[tokio::test]
200
+ async fn test_fixed_response_handler() {
201
+ let handler = FixedResponseHandler {
202
+ response: Bytes::from("fixed response"),
203
+ };
204
+
205
+ let request = GrpcRequestData {
206
+ service_name: "test.FixedService".to_string(),
207
+ method_name: "GetFixed".to_string(),
208
+ payload: Bytes::from("any input"),
209
+ metadata: tonic::metadata::MetadataMap::new(),
210
+ };
211
+
212
+ let result = handler.call(request).await;
213
+ assert!(result.is_ok());
214
+
215
+ let response = result.unwrap();
216
+ assert_eq!(response.payload, Bytes::from("fixed response"));
217
+ }
218
+
219
+ #[tokio::test]
220
+ async fn test_error_handler() {
221
+ let handler = ErrorGrpcHandler;
222
+ let request = GrpcRequestData {
223
+ service_name: "test.ErrorService".to_string(),
224
+ method_name: "Error".to_string(),
225
+ payload: Bytes::new(),
226
+ metadata: tonic::metadata::MetadataMap::new(),
227
+ };
228
+
229
+ let result = handler.call(request).await;
230
+ assert!(result.is_err());
231
+
232
+ let error = result.unwrap_err();
233
+ assert_eq!(error.code(), tonic::Code::NotFound);
234
+ assert_eq!(error.message(), "Resource not found");
235
+ }
236
+
237
+ #[tokio::test]
238
+ async fn test_handler_with_metadata() {
239
+ let handler = EchoGrpcHandler;
240
+ let mut metadata = tonic::metadata::MetadataMap::new();
241
+ metadata.insert("custom-header", "custom-value".parse().unwrap());
242
+
243
+ let request = GrpcRequestData {
244
+ service_name: "test.EchoService".to_string(),
245
+ method_name: "Echo".to_string(),
246
+ payload: Bytes::from("test"),
247
+ metadata,
248
+ };
249
+
250
+ let result = handler.call(request).await;
251
+ assert!(result.is_ok());
252
+ }
253
+
254
+ #[test]
255
+ fn test_grpc_request_data_creation() {
256
+ let request = GrpcRequestData {
257
+ service_name: "mypackage.MyService".to_string(),
258
+ method_name: "GetUser".to_string(),
259
+ payload: Bytes::from("payload"),
260
+ metadata: tonic::metadata::MetadataMap::new(),
261
+ };
262
+
263
+ assert_eq!(request.service_name, "mypackage.MyService");
264
+ assert_eq!(request.method_name, "GetUser");
265
+ assert_eq!(request.payload, Bytes::from("payload"));
266
+ }
267
+
268
+ #[test]
269
+ fn test_grpc_response_data_creation() {
270
+ let response = GrpcResponseData {
271
+ payload: Bytes::from("response"),
272
+ metadata: tonic::metadata::MetadataMap::new(),
273
+ };
274
+
275
+ assert_eq!(response.payload, Bytes::from("response"));
276
+ }
277
+
278
+ #[test]
279
+ fn test_handler_service_name() {
280
+ let echo_handler = EchoGrpcHandler;
281
+ assert_eq!(echo_handler.service_name(), "test.EchoService");
282
+
283
+ let fixed_handler = FixedResponseHandler {
284
+ response: Bytes::new(),
285
+ };
286
+ assert_eq!(fixed_handler.service_name(), "test.FixedService");
287
+
288
+ let error_handler = ErrorGrpcHandler;
289
+ assert_eq!(error_handler.service_name(), "test.ErrorService");
290
+ }
291
+
292
+ #[test]
293
+ fn test_handler_default_streaming_support() {
294
+ let handler = EchoGrpcHandler;
295
+ assert!(!handler.supports_streaming_requests());
296
+ assert!(!handler.supports_streaming_responses());
297
+ }
298
+
299
+ #[test]
300
+ fn test_grpc_config_serialization() {
301
+ let config = GrpcConfig::default();
302
+ let json = serde_json::to_string(&config).unwrap();
303
+ let deserialized: GrpcConfig = serde_json::from_str(&json).unwrap();
304
+
305
+ assert_eq!(config.enabled, deserialized.enabled);
306
+ assert_eq!(config.max_message_size, deserialized.max_message_size);
307
+ }
308
+
309
+ #[tokio::test]
310
+ async fn test_concurrent_handler_calls() {
311
+ let handler = Arc::new(EchoGrpcHandler);
312
+
313
+ let mut tasks = vec![];
314
+ for i in 0..10 {
315
+ let handler_clone = Arc::clone(&handler);
316
+ let handle = tokio::spawn(async move {
317
+ let request = GrpcRequestData {
318
+ service_name: "test.EchoService".to_string(),
319
+ method_name: "Echo".to_string(),
320
+ payload: Bytes::from(format!("message {i}")),
321
+ metadata: tonic::metadata::MetadataMap::new(),
322
+ };
323
+ handler_clone.call(request).await
324
+ });
325
+ tasks.push(handle);
326
+ }
327
+
328
+ for handle in tasks {
329
+ let result = handle.await.unwrap();
330
+ assert!(result.is_ok());
331
+ }
332
+ }