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.
- checksums.yaml +4 -4
- data/ext/spikard_rb/Cargo.lock +583 -201
- data/ext/spikard_rb/Cargo.toml +1 -1
- data/lib/spikard/grpc.rb +182 -0
- data/lib/spikard/version.rb +1 -1
- data/lib/spikard.rb +1 -1
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +2 -1
- data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +197 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +2 -0
- data/vendor/crates/spikard-core/Cargo.toml +1 -1
- data/vendor/crates/spikard-http/Cargo.toml +5 -1
- data/vendor/crates/spikard-http/src/grpc/handler.rs +260 -0
- data/vendor/crates/spikard-http/src/grpc/mod.rs +342 -0
- data/vendor/crates/spikard-http/src/grpc/service.rs +392 -0
- data/vendor/crates/spikard-http/src/grpc/streaming.rs +237 -0
- data/vendor/crates/spikard-http/src/lib.rs +14 -0
- data/vendor/crates/spikard-http/src/server/grpc_routing.rs +288 -0
- data/vendor/crates/spikard-http/src/server/mod.rs +1 -0
- data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +1023 -0
- data/vendor/crates/spikard-http/tests/common/mod.rs +8 -0
- data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +653 -0
- data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +332 -0
- data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +518 -0
- data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +476 -0
- data/vendor/crates/spikard-rb/Cargo.toml +2 -1
- data/vendor/crates/spikard-rb/src/config/server_config.rs +1 -0
- data/vendor/crates/spikard-rb/src/grpc/handler.rs +352 -0
- data/vendor/crates/spikard-rb/src/grpc/mod.rs +9 -0
- data/vendor/crates/spikard-rb/src/lib.rs +4 -0
- data/vendor/crates/spikard-rb-macros/Cargo.toml +1 -1
- metadata +15 -1
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
//! Comprehensive gRPC server integration tests
|
|
2
|
+
//!
|
|
3
|
+
//! Tests the gRPC runtime infrastructure including:
|
|
4
|
+
//! - Server routing with multiple services
|
|
5
|
+
//! - Unary RPC handling
|
|
6
|
+
//! - Request/response payload handling
|
|
7
|
+
//! - Error responses with different gRPC status codes
|
|
8
|
+
|
|
9
|
+
use crate::common::grpc_helpers::*;
|
|
10
|
+
use bytes::Bytes;
|
|
11
|
+
use serde_json::json;
|
|
12
|
+
use std::sync::Arc;
|
|
13
|
+
use tonic::Code;
|
|
14
|
+
|
|
15
|
+
mod common;
|
|
16
|
+
|
|
17
|
+
/// Test successful unary RPC with JSON payload
|
|
18
|
+
#[tokio::test]
|
|
19
|
+
async fn test_unary_rpc_success_with_json_payload() {
|
|
20
|
+
// Arrange: Create test server and handler
|
|
21
|
+
let mut server = GrpcTestServer::new();
|
|
22
|
+
|
|
23
|
+
// Create a custom handler with proper service name
|
|
24
|
+
struct UserServiceHandler;
|
|
25
|
+
impl spikard_http::grpc::GrpcHandler for UserServiceHandler {
|
|
26
|
+
fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
|
|
27
|
+
-> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
|
|
28
|
+
{
|
|
29
|
+
let payload = serde_json::to_vec(&json!({
|
|
30
|
+
"id": 123,
|
|
31
|
+
"name": "Alice Johnson",
|
|
32
|
+
"email": "alice@example.com"
|
|
33
|
+
})).unwrap();
|
|
34
|
+
Box::pin(async {
|
|
35
|
+
Ok(spikard_http::grpc::GrpcResponseData {
|
|
36
|
+
payload: Bytes::from(payload),
|
|
37
|
+
metadata: tonic::metadata::MetadataMap::new(),
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
fn service_name(&self) -> &'static str {
|
|
42
|
+
"com.example.api.v1.UserService"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
server.register_service(Arc::new(UserServiceHandler));
|
|
47
|
+
|
|
48
|
+
// Act: Create and send request
|
|
49
|
+
let mut request_builder = ProtobufMessageBuilder::new();
|
|
50
|
+
let request_payload = request_builder
|
|
51
|
+
.add_int_field("user_id", 123)
|
|
52
|
+
.build()
|
|
53
|
+
.unwrap();
|
|
54
|
+
|
|
55
|
+
let response = send_unary_request(
|
|
56
|
+
&server,
|
|
57
|
+
"com.example.api.v1.UserService",
|
|
58
|
+
"GetUser",
|
|
59
|
+
request_payload,
|
|
60
|
+
create_test_metadata(),
|
|
61
|
+
)
|
|
62
|
+
.await
|
|
63
|
+
.expect("Failed to send unary request");
|
|
64
|
+
|
|
65
|
+
// Assert
|
|
66
|
+
assert_grpc_response(response, json!({
|
|
67
|
+
"id": 123,
|
|
68
|
+
"name": "Alice Johnson",
|
|
69
|
+
"email": "alice@example.com"
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/// Test server routing with multiple services
|
|
74
|
+
#[tokio::test]
|
|
75
|
+
async fn test_server_routes_to_correct_service() {
|
|
76
|
+
// Arrange: Register multiple services
|
|
77
|
+
let mut server = GrpcTestServer::new();
|
|
78
|
+
|
|
79
|
+
struct UserServiceHandler;
|
|
80
|
+
impl spikard_http::grpc::GrpcHandler for UserServiceHandler {
|
|
81
|
+
fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
|
|
82
|
+
-> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
|
|
83
|
+
{
|
|
84
|
+
Box::pin(async {
|
|
85
|
+
Ok(spikard_http::grpc::GrpcResponseData {
|
|
86
|
+
payload: Bytes::from(r#"{"service": "UserService"}"#),
|
|
87
|
+
metadata: tonic::metadata::MetadataMap::new(),
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
fn service_name(&self) -> &'static str {
|
|
92
|
+
"api.UserService"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
struct OrderServiceHandler;
|
|
97
|
+
impl spikard_http::grpc::GrpcHandler for OrderServiceHandler {
|
|
98
|
+
fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
|
|
99
|
+
-> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
|
|
100
|
+
{
|
|
101
|
+
Box::pin(async {
|
|
102
|
+
Ok(spikard_http::grpc::GrpcResponseData {
|
|
103
|
+
payload: Bytes::from(r#"{"service": "OrderService"}"#),
|
|
104
|
+
metadata: tonic::metadata::MetadataMap::new(),
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
fn service_name(&self) -> &'static str {
|
|
109
|
+
"api.OrderService"
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
server.register_service(Arc::new(UserServiceHandler));
|
|
114
|
+
server.register_service(Arc::new(OrderServiceHandler));
|
|
115
|
+
|
|
116
|
+
// Act & Assert: Route to UserService
|
|
117
|
+
let user_response = send_unary_request(
|
|
118
|
+
&server,
|
|
119
|
+
"api.UserService",
|
|
120
|
+
"GetUser",
|
|
121
|
+
Bytes::from("{}"),
|
|
122
|
+
create_test_metadata(),
|
|
123
|
+
)
|
|
124
|
+
.await
|
|
125
|
+
.expect("UserService request failed");
|
|
126
|
+
|
|
127
|
+
assert_grpc_response(user_response, json!({"service": "UserService"}));
|
|
128
|
+
|
|
129
|
+
// Act & Assert: Route to OrderService
|
|
130
|
+
let order_response = send_unary_request(
|
|
131
|
+
&server,
|
|
132
|
+
"api.OrderService",
|
|
133
|
+
"GetOrder",
|
|
134
|
+
Bytes::from("{}"),
|
|
135
|
+
create_test_metadata(),
|
|
136
|
+
)
|
|
137
|
+
.await
|
|
138
|
+
.expect("OrderService request failed");
|
|
139
|
+
|
|
140
|
+
assert_grpc_response(order_response, json!({"service": "OrderService"}));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Test server correctly counts registered services
|
|
144
|
+
#[tokio::test]
|
|
145
|
+
async fn test_server_service_registration_count() {
|
|
146
|
+
let mut server = GrpcTestServer::new();
|
|
147
|
+
|
|
148
|
+
assert_eq!(server.service_count(), 0);
|
|
149
|
+
|
|
150
|
+
// Register first service
|
|
151
|
+
let handler1 = Arc::new(MockGrpcHandler::with_json("service.One", json!({"id": 1})));
|
|
152
|
+
server.register_service(handler1);
|
|
153
|
+
assert_eq!(server.service_count(), 1);
|
|
154
|
+
|
|
155
|
+
// Register second service
|
|
156
|
+
let handler2 = Arc::new(MockGrpcHandler::with_json("service.Two", json!({"id": 2})));
|
|
157
|
+
server.register_service(handler2);
|
|
158
|
+
assert_eq!(server.service_count(), 2);
|
|
159
|
+
|
|
160
|
+
// Register third service
|
|
161
|
+
let handler3 = Arc::new(MockGrpcHandler::with_json("service.Three", json!({"id": 3})));
|
|
162
|
+
server.register_service(handler3);
|
|
163
|
+
assert_eq!(server.service_count(), 3);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Test unary RPC with complex nested JSON payload
|
|
167
|
+
#[tokio::test]
|
|
168
|
+
async fn test_unary_rpc_with_nested_json_payload() {
|
|
169
|
+
let mut server = GrpcTestServer::new();
|
|
170
|
+
|
|
171
|
+
struct ProductServiceHandler;
|
|
172
|
+
impl spikard_http::grpc::GrpcHandler for ProductServiceHandler {
|
|
173
|
+
fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
|
|
174
|
+
-> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
|
|
175
|
+
{
|
|
176
|
+
Box::pin(async {
|
|
177
|
+
Ok(spikard_http::grpc::GrpcResponseData {
|
|
178
|
+
payload: Bytes::from(r#"{
|
|
179
|
+
"id": 42,
|
|
180
|
+
"name": "Laptop",
|
|
181
|
+
"price": 999.99,
|
|
182
|
+
"inventory": {
|
|
183
|
+
"quantity": 100,
|
|
184
|
+
"warehouse": "US-WEST"
|
|
185
|
+
},
|
|
186
|
+
"tags": ["electronics", "computers", "portable"]
|
|
187
|
+
}"#),
|
|
188
|
+
metadata: tonic::metadata::MetadataMap::new(),
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
fn service_name(&self) -> &'static str {
|
|
193
|
+
"shop.ProductService"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
server.register_service(Arc::new(ProductServiceHandler));
|
|
198
|
+
|
|
199
|
+
let response = send_unary_request(
|
|
200
|
+
&server,
|
|
201
|
+
"shop.ProductService",
|
|
202
|
+
"GetProduct",
|
|
203
|
+
Bytes::from("{}"),
|
|
204
|
+
create_test_metadata(),
|
|
205
|
+
)
|
|
206
|
+
.await
|
|
207
|
+
.expect("Failed to get product");
|
|
208
|
+
|
|
209
|
+
// Verify nested structure
|
|
210
|
+
let payload = serde_json::from_slice::<serde_json::Value>(&response.payload).unwrap();
|
|
211
|
+
assert_eq!(payload["id"], 42);
|
|
212
|
+
assert_eq!(payload["name"], "Laptop");
|
|
213
|
+
assert_eq!(payload["price"], 999.99);
|
|
214
|
+
assert_eq!(payload["inventory"]["quantity"], 100);
|
|
215
|
+
assert_eq!(payload["inventory"]["warehouse"], "US-WEST");
|
|
216
|
+
assert_eq!(payload["tags"].as_array().unwrap().len(), 3);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// Test unary RPC with binary payload (raw bytes)
|
|
220
|
+
#[tokio::test]
|
|
221
|
+
async fn test_unary_rpc_with_binary_payload() {
|
|
222
|
+
let mut server = GrpcTestServer::new();
|
|
223
|
+
|
|
224
|
+
struct EchoServiceHandler;
|
|
225
|
+
impl spikard_http::grpc::GrpcHandler for EchoServiceHandler {
|
|
226
|
+
fn call(&self, request: spikard_http::grpc::GrpcRequestData)
|
|
227
|
+
-> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
|
|
228
|
+
{
|
|
229
|
+
let payload = request.payload;
|
|
230
|
+
Box::pin(async {
|
|
231
|
+
Ok(spikard_http::grpc::GrpcResponseData {
|
|
232
|
+
payload,
|
|
233
|
+
metadata: tonic::metadata::MetadataMap::new(),
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
|
+
}
|
|
237
|
+
fn service_name(&self) -> &'static str {
|
|
238
|
+
"echo.EchoService"
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
server.register_service(Arc::new(EchoServiceHandler));
|
|
243
|
+
|
|
244
|
+
// Send binary payload
|
|
245
|
+
let binary_data = vec![0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD];
|
|
246
|
+
let response = send_unary_request(
|
|
247
|
+
&server,
|
|
248
|
+
"echo.EchoService",
|
|
249
|
+
"Echo",
|
|
250
|
+
Bytes::from(binary_data.clone()),
|
|
251
|
+
create_test_metadata(),
|
|
252
|
+
)
|
|
253
|
+
.await
|
|
254
|
+
.expect("Failed to echo");
|
|
255
|
+
|
|
256
|
+
// Verify binary data is unchanged
|
|
257
|
+
assert_eq!(response.payload.to_vec(), binary_data);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// Test request with custom metadata is preserved in response
|
|
261
|
+
#[tokio::test]
|
|
262
|
+
async fn test_request_metadata_handling() {
|
|
263
|
+
let mut server = GrpcTestServer::new();
|
|
264
|
+
|
|
265
|
+
struct MetadataAwareHandler;
|
|
266
|
+
impl spikard_http::grpc::GrpcHandler for MetadataAwareHandler {
|
|
267
|
+
fn call(&self, request: spikard_http::grpc::GrpcRequestData)
|
|
268
|
+
-> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
|
|
269
|
+
{
|
|
270
|
+
// Check that metadata was received
|
|
271
|
+
let has_custom_header = request.metadata.get("x-custom-header").is_some();
|
|
272
|
+
let response_payload = if has_custom_header {
|
|
273
|
+
Bytes::from(r#"{"status": "metadata_received"}"#)
|
|
274
|
+
} else {
|
|
275
|
+
Bytes::from(r#"{"status": "no_metadata"}"#)
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
Box::pin(async move {
|
|
279
|
+
Ok(spikard_http::grpc::GrpcResponseData {
|
|
280
|
+
payload: response_payload,
|
|
281
|
+
metadata: tonic::metadata::MetadataMap::new(),
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
fn service_name(&self) -> &'static str {
|
|
286
|
+
"test.MetadataService"
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
server.register_service(Arc::new(MetadataAwareHandler));
|
|
291
|
+
|
|
292
|
+
let mut metadata = create_test_metadata();
|
|
293
|
+
add_metadata_header(&mut metadata, "x-custom-header", "test-value").unwrap();
|
|
294
|
+
|
|
295
|
+
let response = send_unary_request(
|
|
296
|
+
&server,
|
|
297
|
+
"test.MetadataService",
|
|
298
|
+
"TestMetadata",
|
|
299
|
+
Bytes::from("{}"),
|
|
300
|
+
metadata,
|
|
301
|
+
)
|
|
302
|
+
.await
|
|
303
|
+
.expect("Failed to send request with metadata");
|
|
304
|
+
|
|
305
|
+
assert_grpc_response(response, json!({"status": "metadata_received"}));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/// Test error response with NOT_FOUND status
|
|
309
|
+
#[tokio::test]
|
|
310
|
+
async fn test_error_response_not_found() {
|
|
311
|
+
let mut server = GrpcTestServer::new();
|
|
312
|
+
|
|
313
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
314
|
+
"api.NotFoundService",
|
|
315
|
+
Code::NotFound,
|
|
316
|
+
"User with ID 999 not found",
|
|
317
|
+
));
|
|
318
|
+
server.register_service(handler);
|
|
319
|
+
|
|
320
|
+
let result = send_unary_request(
|
|
321
|
+
&server,
|
|
322
|
+
"api.NotFoundService",
|
|
323
|
+
"GetUser",
|
|
324
|
+
Bytes::from("{}"),
|
|
325
|
+
create_test_metadata(),
|
|
326
|
+
)
|
|
327
|
+
.await;
|
|
328
|
+
|
|
329
|
+
assert!(result.is_err());
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/// Test error response with INVALID_ARGUMENT status
|
|
333
|
+
#[tokio::test]
|
|
334
|
+
async fn test_error_response_invalid_argument() {
|
|
335
|
+
let mut server = GrpcTestServer::new();
|
|
336
|
+
|
|
337
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
338
|
+
"api.ValidationService",
|
|
339
|
+
Code::InvalidArgument,
|
|
340
|
+
"Email address is invalid",
|
|
341
|
+
));
|
|
342
|
+
server.register_service(handler);
|
|
343
|
+
|
|
344
|
+
let result = send_unary_request(
|
|
345
|
+
&server,
|
|
346
|
+
"api.ValidationService",
|
|
347
|
+
"ValidateEmail",
|
|
348
|
+
Bytes::from("{}"),
|
|
349
|
+
create_test_metadata(),
|
|
350
|
+
)
|
|
351
|
+
.await;
|
|
352
|
+
|
|
353
|
+
assert!(result.is_err());
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/// Test error response with INTERNAL status
|
|
357
|
+
#[tokio::test]
|
|
358
|
+
async fn test_error_response_internal_server_error() {
|
|
359
|
+
let mut server = GrpcTestServer::new();
|
|
360
|
+
|
|
361
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
362
|
+
"api.DatabaseService",
|
|
363
|
+
Code::Internal,
|
|
364
|
+
"Database connection failed",
|
|
365
|
+
));
|
|
366
|
+
server.register_service(handler);
|
|
367
|
+
|
|
368
|
+
let result = send_unary_request(
|
|
369
|
+
&server,
|
|
370
|
+
"api.DatabaseService",
|
|
371
|
+
"QueryData",
|
|
372
|
+
Bytes::from("{}"),
|
|
373
|
+
create_test_metadata(),
|
|
374
|
+
)
|
|
375
|
+
.await;
|
|
376
|
+
|
|
377
|
+
assert!(result.is_err());
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/// Test server with no services registered
|
|
381
|
+
#[tokio::test]
|
|
382
|
+
async fn test_server_no_services_registered() {
|
|
383
|
+
let server = GrpcTestServer::new();
|
|
384
|
+
|
|
385
|
+
assert_eq!(server.service_count(), 0);
|
|
386
|
+
assert!(server.handlers().is_empty());
|
|
387
|
+
|
|
388
|
+
// Attempting to call a non-existent service should fail
|
|
389
|
+
let result = send_unary_request(
|
|
390
|
+
&server,
|
|
391
|
+
"nonexistent.Service",
|
|
392
|
+
"Method",
|
|
393
|
+
Bytes::from("{}"),
|
|
394
|
+
create_test_metadata(),
|
|
395
|
+
)
|
|
396
|
+
.await;
|
|
397
|
+
|
|
398
|
+
assert!(result.is_err());
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/// Test unary RPC with empty request payload
|
|
402
|
+
#[tokio::test]
|
|
403
|
+
async fn test_unary_rpc_empty_request_payload() {
|
|
404
|
+
let mut server = GrpcTestServer::new();
|
|
405
|
+
|
|
406
|
+
struct EmptyRequestHandler;
|
|
407
|
+
impl spikard_http::grpc::GrpcHandler for EmptyRequestHandler {
|
|
408
|
+
fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
|
|
409
|
+
-> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
|
|
410
|
+
{
|
|
411
|
+
Box::pin(async {
|
|
412
|
+
Ok(spikard_http::grpc::GrpcResponseData {
|
|
413
|
+
payload: Bytes::from(r#"{"status": "ok"}"#),
|
|
414
|
+
metadata: tonic::metadata::MetadataMap::new(),
|
|
415
|
+
})
|
|
416
|
+
})
|
|
417
|
+
}
|
|
418
|
+
fn service_name(&self) -> &'static str {
|
|
419
|
+
"test.EmptyRequestService"
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
server.register_service(Arc::new(EmptyRequestHandler));
|
|
424
|
+
|
|
425
|
+
let response = send_unary_request(
|
|
426
|
+
&server,
|
|
427
|
+
"test.EmptyRequestService",
|
|
428
|
+
"HealthCheck",
|
|
429
|
+
Bytes::new(),
|
|
430
|
+
create_test_metadata(),
|
|
431
|
+
)
|
|
432
|
+
.await
|
|
433
|
+
.expect("Failed to send empty request");
|
|
434
|
+
|
|
435
|
+
assert_grpc_response(response, json!({"status": "ok"}));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/// Test echo handler preserves exact request payload
|
|
439
|
+
#[tokio::test]
|
|
440
|
+
async fn test_echo_handler_payload_preservation() {
|
|
441
|
+
let mut server = GrpcTestServer::new();
|
|
442
|
+
|
|
443
|
+
struct TestEchoHandler;
|
|
444
|
+
impl spikard_http::grpc::GrpcHandler for TestEchoHandler {
|
|
445
|
+
fn call(&self, request: spikard_http::grpc::GrpcRequestData)
|
|
446
|
+
-> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
|
|
447
|
+
{
|
|
448
|
+
let payload = request.payload;
|
|
449
|
+
Box::pin(async move {
|
|
450
|
+
Ok(spikard_http::grpc::GrpcResponseData {
|
|
451
|
+
payload,
|
|
452
|
+
metadata: tonic::metadata::MetadataMap::new(),
|
|
453
|
+
})
|
|
454
|
+
})
|
|
455
|
+
}
|
|
456
|
+
fn service_name(&self) -> &'static str {
|
|
457
|
+
"test.EchoService"
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
server.register_service(Arc::new(TestEchoHandler));
|
|
462
|
+
|
|
463
|
+
let request_payload = Bytes::from(r#"{"echo": "test message"}"#);
|
|
464
|
+
|
|
465
|
+
let response = send_unary_request(
|
|
466
|
+
&server,
|
|
467
|
+
"test.EchoService",
|
|
468
|
+
"Echo",
|
|
469
|
+
request_payload.clone(),
|
|
470
|
+
create_test_metadata(),
|
|
471
|
+
)
|
|
472
|
+
.await
|
|
473
|
+
.expect("Failed to echo");
|
|
474
|
+
|
|
475
|
+
assert_eq!(response.payload, request_payload);
|
|
476
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "spikard-rb"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
edition = "2024"
|
|
5
5
|
authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
|
|
6
6
|
license = "MIT"
|
|
@@ -29,6 +29,7 @@ axum-test = "18"
|
|
|
29
29
|
bytes = "1.11"
|
|
30
30
|
cookie = "0.18"
|
|
31
31
|
tokio = { version = "1", features = ["full"] }
|
|
32
|
+
tonic = { version = "0.14", features = ["transport", "codegen", "gzip"] }
|
|
32
33
|
tungstenite = "0.28"
|
|
33
34
|
tracing = "0.1"
|
|
34
35
|
serde_qs = "0.15"
|