spikard 0.7.5 → 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 -0
- 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,1023 @@
|
|
|
1
|
+
//! gRPC test utilities and helpers for comprehensive integration tests
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides reusable utilities for writing gRPC integration tests without
|
|
4
|
+
//! requiring language bindings. It includes helpers for creating test servers, clients,
|
|
5
|
+
//! building protobuf messages, and asserting on responses.
|
|
6
|
+
//!
|
|
7
|
+
//! # Examples
|
|
8
|
+
//!
|
|
9
|
+
//! ```ignore
|
|
10
|
+
//! use common::grpc_helpers::{
|
|
11
|
+
//! GrpcTestServer, create_grpc_test_client, send_unary_request,
|
|
12
|
+
//! assert_grpc_response, create_test_metadata, ProtobufMessageBuilder,
|
|
13
|
+
//! };
|
|
14
|
+
//! use std::sync::Arc;
|
|
15
|
+
//! use bytes::Bytes;
|
|
16
|
+
//!
|
|
17
|
+
//! #[tokio::test]
|
|
18
|
+
//! async fn test_grpc_service() {
|
|
19
|
+
//! let mut server = GrpcTestServer::new();
|
|
20
|
+
//! let metadata = create_test_metadata();
|
|
21
|
+
//!
|
|
22
|
+
//! let message = ProtobufMessageBuilder::new()
|
|
23
|
+
//! .add_field("name", "Alice")
|
|
24
|
+
//! .add_field("age", 30)
|
|
25
|
+
//! .build()
|
|
26
|
+
//! .unwrap();
|
|
27
|
+
//!
|
|
28
|
+
//! let response = send_unary_request(
|
|
29
|
+
//! &server,
|
|
30
|
+
//! "mypackage.UserService",
|
|
31
|
+
//! "GetUser",
|
|
32
|
+
//! message,
|
|
33
|
+
//! metadata,
|
|
34
|
+
//! )
|
|
35
|
+
//! .await
|
|
36
|
+
//! .unwrap();
|
|
37
|
+
//!
|
|
38
|
+
//! assert_grpc_response(response, serde_json::json!({"id": 1}));
|
|
39
|
+
//! }
|
|
40
|
+
//! ```
|
|
41
|
+
|
|
42
|
+
use bytes::Bytes;
|
|
43
|
+
use serde_json::{json, Value};
|
|
44
|
+
use spikard_http::grpc::{GrpcHandler, GrpcHandlerResult, GrpcRequestData, GrpcResponseData};
|
|
45
|
+
use std::future::Future;
|
|
46
|
+
use std::pin::Pin;
|
|
47
|
+
use std::sync::Arc;
|
|
48
|
+
use tonic::metadata::MetadataMap;
|
|
49
|
+
use tonic::Code;
|
|
50
|
+
|
|
51
|
+
/// Test server for gRPC integration testing
|
|
52
|
+
///
|
|
53
|
+
/// Provides a simple in-memory server for registering test handlers and managing
|
|
54
|
+
/// their lifecycle. This is useful for testing gRPC services without spinning up
|
|
55
|
+
/// a real network server.
|
|
56
|
+
///
|
|
57
|
+
/// # Example
|
|
58
|
+
///
|
|
59
|
+
/// ```ignore
|
|
60
|
+
/// let mut server = GrpcTestServer::new();
|
|
61
|
+
/// server.register_service(Arc::new(my_handler));
|
|
62
|
+
/// assert!(!server.handlers().is_empty());
|
|
63
|
+
/// ```
|
|
64
|
+
#[derive(Clone)]
|
|
65
|
+
pub struct GrpcTestServer {
|
|
66
|
+
handlers: Arc<std::sync::Mutex<Vec<Arc<dyn GrpcHandler>>>>,
|
|
67
|
+
base_url: String,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
impl GrpcTestServer {
|
|
71
|
+
/// Create a new gRPC test server
|
|
72
|
+
///
|
|
73
|
+
/// Initializes an empty test server with a default localhost base URL.
|
|
74
|
+
pub fn new() -> Self {
|
|
75
|
+
Self {
|
|
76
|
+
handlers: Arc::new(std::sync::Mutex::new(Vec::new())),
|
|
77
|
+
base_url: "http://localhost:50051".to_string(),
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/// Create a new gRPC test server with a custom base URL
|
|
82
|
+
///
|
|
83
|
+
/// # Arguments
|
|
84
|
+
///
|
|
85
|
+
/// * `url` - The base URL for the server (e.g., "http://localhost:8080")
|
|
86
|
+
pub fn with_url(url: impl Into<String>) -> Self {
|
|
87
|
+
Self {
|
|
88
|
+
handlers: Arc::new(std::sync::Mutex::new(Vec::new())),
|
|
89
|
+
base_url: url.into(),
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Register a gRPC service handler with the test server
|
|
94
|
+
///
|
|
95
|
+
/// # Arguments
|
|
96
|
+
///
|
|
97
|
+
/// * `handler` - Arc-wrapped handler implementation to register
|
|
98
|
+
///
|
|
99
|
+
/// # Example
|
|
100
|
+
///
|
|
101
|
+
/// ```ignore
|
|
102
|
+
/// let mut server = GrpcTestServer::new();
|
|
103
|
+
/// server.register_service(Arc::new(MyHandler));
|
|
104
|
+
/// ```
|
|
105
|
+
pub fn register_service(&mut self, handler: Arc<dyn GrpcHandler>) {
|
|
106
|
+
let mut handlers = self.handlers.lock().unwrap();
|
|
107
|
+
handlers.push(handler);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Get the base URL of the test server
|
|
111
|
+
pub fn url(&self) -> &str {
|
|
112
|
+
&self.base_url
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Get all registered handlers
|
|
116
|
+
///
|
|
117
|
+
/// Useful for inspecting which handlers have been registered.
|
|
118
|
+
pub fn handlers(&self) -> Vec<Arc<dyn GrpcHandler>> {
|
|
119
|
+
let handlers = self.handlers.lock().unwrap();
|
|
120
|
+
handlers.clone()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Find a handler by service name
|
|
124
|
+
///
|
|
125
|
+
/// # Arguments
|
|
126
|
+
///
|
|
127
|
+
/// * `service_name` - The fully qualified service name to look up
|
|
128
|
+
pub fn get_handler(&self, service_name: &str) -> Option<Arc<dyn GrpcHandler>> {
|
|
129
|
+
let handlers = self.handlers.lock().unwrap();
|
|
130
|
+
handlers
|
|
131
|
+
.iter()
|
|
132
|
+
.find(|h| h.service_name() == service_name)
|
|
133
|
+
.cloned()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/// Check if a service is registered
|
|
137
|
+
///
|
|
138
|
+
/// # Arguments
|
|
139
|
+
///
|
|
140
|
+
/// * `service_name` - The service name to check
|
|
141
|
+
pub fn has_service(&self, service_name: &str) -> bool {
|
|
142
|
+
self.get_handler(service_name).is_some()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// Get the count of registered services
|
|
146
|
+
pub fn service_count(&self) -> usize {
|
|
147
|
+
self.handlers.lock().unwrap().len()
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
impl Default for GrpcTestServer {
|
|
152
|
+
fn default() -> Self {
|
|
153
|
+
Self::new()
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Create a simple gRPC test client for testing
|
|
158
|
+
///
|
|
159
|
+
/// In real-world scenarios, you would use a proper gRPC client library like
|
|
160
|
+
/// `tonic::Client`. This helper creates a lightweight wrapper for testing
|
|
161
|
+
/// without requiring actual network connections.
|
|
162
|
+
///
|
|
163
|
+
/// # Example
|
|
164
|
+
///
|
|
165
|
+
/// ```ignore
|
|
166
|
+
/// let _client = create_grpc_test_client();
|
|
167
|
+
/// // Use with send_unary_request
|
|
168
|
+
/// ```
|
|
169
|
+
pub struct GrpcTestClient;
|
|
170
|
+
|
|
171
|
+
impl GrpcTestClient {
|
|
172
|
+
/// Create a new test client
|
|
173
|
+
pub fn new() -> Self {
|
|
174
|
+
Self
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
impl Default for GrpcTestClient {
|
|
179
|
+
fn default() -> Self {
|
|
180
|
+
Self::new()
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/// Factory function to create a gRPC test client
|
|
185
|
+
pub fn create_grpc_test_client() -> GrpcTestClient {
|
|
186
|
+
GrpcTestClient::new()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/// Send a unary gRPC request to a test server
|
|
190
|
+
///
|
|
191
|
+
/// This is a helper function to simulate sending a unary RPC request to a gRPC service.
|
|
192
|
+
/// In a real test, you would use a proper gRPC client library, but this helper
|
|
193
|
+
/// allows testing the handler logic directly.
|
|
194
|
+
///
|
|
195
|
+
/// # Arguments
|
|
196
|
+
///
|
|
197
|
+
/// * `server` - The test server instance
|
|
198
|
+
/// * `service` - Fully qualified service name (e.g., "mypackage.UserService")
|
|
199
|
+
/// * `method` - Method name (e.g., "GetUser")
|
|
200
|
+
/// * `payload` - Serialized protobuf message bytes
|
|
201
|
+
/// * `metadata` - gRPC metadata (headers) to include in the request
|
|
202
|
+
///
|
|
203
|
+
/// # Errors
|
|
204
|
+
///
|
|
205
|
+
/// Returns an error if:
|
|
206
|
+
/// - The service is not registered on the server
|
|
207
|
+
/// - The handler returns an error response
|
|
208
|
+
///
|
|
209
|
+
/// # Example
|
|
210
|
+
///
|
|
211
|
+
/// ```ignore
|
|
212
|
+
/// let mut server = GrpcTestServer::new();
|
|
213
|
+
/// server.register_service(Arc::new(my_handler));
|
|
214
|
+
///
|
|
215
|
+
/// let response = send_unary_request(
|
|
216
|
+
/// &server,
|
|
217
|
+
/// "mypackage.UserService",
|
|
218
|
+
/// "GetUser",
|
|
219
|
+
/// Bytes::from("request"),
|
|
220
|
+
/// create_test_metadata(),
|
|
221
|
+
/// ).await?;
|
|
222
|
+
/// ```
|
|
223
|
+
pub async fn send_unary_request(
|
|
224
|
+
server: &GrpcTestServer,
|
|
225
|
+
service: &str,
|
|
226
|
+
method: &str,
|
|
227
|
+
payload: Bytes,
|
|
228
|
+
metadata: MetadataMap,
|
|
229
|
+
) -> Result<GrpcResponseData, Box<dyn std::error::Error>> {
|
|
230
|
+
let handler = server
|
|
231
|
+
.get_handler(service)
|
|
232
|
+
.ok_or_else(|| Box::new(std::io::Error::new(
|
|
233
|
+
std::io::ErrorKind::NotFound,
|
|
234
|
+
format!("Service not found: {}", service),
|
|
235
|
+
)) as Box<dyn std::error::Error>)?;
|
|
236
|
+
|
|
237
|
+
let request = GrpcRequestData {
|
|
238
|
+
service_name: service.to_string(),
|
|
239
|
+
method_name: method.to_string(),
|
|
240
|
+
payload,
|
|
241
|
+
metadata,
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
handler.call(request).await.map_err(|e| e.message().into())
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// Assert that a gRPC response payload matches the expected JSON value
|
|
248
|
+
///
|
|
249
|
+
/// Parses the response payload as JSON and compares it with the expected value.
|
|
250
|
+
/// This is useful for testing JSON-based gRPC messages.
|
|
251
|
+
///
|
|
252
|
+
/// # Panics
|
|
253
|
+
///
|
|
254
|
+
/// Panics if the payload cannot be parsed as JSON or doesn't match the expected value.
|
|
255
|
+
///
|
|
256
|
+
/// # Arguments
|
|
257
|
+
///
|
|
258
|
+
/// * `response` - The gRPC response data
|
|
259
|
+
/// * `expected` - The expected JSON value
|
|
260
|
+
///
|
|
261
|
+
/// # Example
|
|
262
|
+
///
|
|
263
|
+
/// ```ignore
|
|
264
|
+
/// let response = send_unary_request(...).await?;
|
|
265
|
+
/// assert_grpc_response(response, json!({"id": 1, "name": "Alice"}));
|
|
266
|
+
/// ```
|
|
267
|
+
pub fn assert_grpc_response(response: GrpcResponseData, expected: Value) {
|
|
268
|
+
let actual = serde_json::from_slice::<Value>(&response.payload)
|
|
269
|
+
.expect("Failed to parse response payload as JSON");
|
|
270
|
+
|
|
271
|
+
assert_eq!(
|
|
272
|
+
actual, expected,
|
|
273
|
+
"Response payload mismatch.\nExpected: {}\nActual: {}",
|
|
274
|
+
expected, actual
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/// Assert that a gRPC handler result has a specific status code
|
|
279
|
+
///
|
|
280
|
+
/// Useful for testing error handling and status code responses.
|
|
281
|
+
///
|
|
282
|
+
/// # Panics
|
|
283
|
+
///
|
|
284
|
+
/// Panics if the status code doesn't match.
|
|
285
|
+
///
|
|
286
|
+
/// # Arguments
|
|
287
|
+
///
|
|
288
|
+
/// * `result` - The gRPC handler result
|
|
289
|
+
/// * `expected_status` - The expected tonic::Code
|
|
290
|
+
///
|
|
291
|
+
/// # Example
|
|
292
|
+
///
|
|
293
|
+
/// ```ignore
|
|
294
|
+
/// let result = handler.call(request).await;
|
|
295
|
+
/// assert_grpc_status(&result, tonic::Code::NotFound);
|
|
296
|
+
/// ```
|
|
297
|
+
pub fn assert_grpc_status(result: &GrpcHandlerResult, expected_status: Code) {
|
|
298
|
+
match result {
|
|
299
|
+
Err(status) => {
|
|
300
|
+
assert_eq!(
|
|
301
|
+
status.code(),
|
|
302
|
+
expected_status,
|
|
303
|
+
"Expected status {:?} but got {:?}: {}",
|
|
304
|
+
expected_status,
|
|
305
|
+
status.code(),
|
|
306
|
+
status.message()
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
Ok(_) => {
|
|
310
|
+
panic!(
|
|
311
|
+
"Expected error status {:?} but got success response",
|
|
312
|
+
expected_status
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/// Fluent builder for creating test protobuf messages
|
|
319
|
+
///
|
|
320
|
+
/// Provides a simple way to construct JSON-serializable test messages
|
|
321
|
+
/// that can be used as gRPC payloads. In practice, you would use actual
|
|
322
|
+
/// protobuf code generation, but this is useful for simple tests.
|
|
323
|
+
///
|
|
324
|
+
/// # Example
|
|
325
|
+
///
|
|
326
|
+
/// ```ignore
|
|
327
|
+
/// let message = ProtobufMessageBuilder::new()
|
|
328
|
+
/// .add_field("id", 42)
|
|
329
|
+
/// .add_field("name", "Alice")
|
|
330
|
+
/// .add_field("email", "alice@example.com")
|
|
331
|
+
/// .build()?;
|
|
332
|
+
/// ```
|
|
333
|
+
pub struct ProtobufMessageBuilder {
|
|
334
|
+
fields: serde_json::Map<String, Value>,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
impl ProtobufMessageBuilder {
|
|
338
|
+
/// Create a new message builder with no fields
|
|
339
|
+
pub fn new() -> Self {
|
|
340
|
+
Self {
|
|
341
|
+
fields: serde_json::Map::new(),
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/// Add a field to the message
|
|
346
|
+
///
|
|
347
|
+
/// # Arguments
|
|
348
|
+
///
|
|
349
|
+
/// * `name` - The field name
|
|
350
|
+
/// * `value` - The field value (automatically converted to JSON)
|
|
351
|
+
///
|
|
352
|
+
/// # Example
|
|
353
|
+
///
|
|
354
|
+
/// ```ignore
|
|
355
|
+
/// builder.add_field("name", "Alice")
|
|
356
|
+
/// .add_field("age", 30)
|
|
357
|
+
/// .add_field("active", true);
|
|
358
|
+
/// ```
|
|
359
|
+
pub fn add_field(&mut self, name: &str, value: impl Into<Value>) -> &mut Self {
|
|
360
|
+
self.fields.insert(name.to_string(), value.into());
|
|
361
|
+
self
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/// Add a string field to the message
|
|
365
|
+
pub fn add_string_field(&mut self, name: &str, value: &str) -> &mut Self {
|
|
366
|
+
self.fields
|
|
367
|
+
.insert(name.to_string(), Value::String(value.to_string()));
|
|
368
|
+
self
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/// Add an integer field to the message
|
|
372
|
+
pub fn add_int_field(&mut self, name: &str, value: i64) -> &mut Self {
|
|
373
|
+
self.fields
|
|
374
|
+
.insert(name.to_string(), json!(value));
|
|
375
|
+
self
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/// Add a boolean field to the message
|
|
379
|
+
pub fn add_bool_field(&mut self, name: &str, value: bool) -> &mut Self {
|
|
380
|
+
self.fields.insert(name.to_string(), json!(value));
|
|
381
|
+
self
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/// Add a nested object field to the message
|
|
385
|
+
pub fn add_object_field(&mut self, name: &str, value: Value) -> &mut Self {
|
|
386
|
+
self.fields.insert(name.to_string(), value);
|
|
387
|
+
self
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/// Add an array field to the message
|
|
391
|
+
pub fn add_array_field(&mut self, name: &str, values: Vec<Value>) -> &mut Self {
|
|
392
|
+
self.fields.insert(name.to_string(), Value::Array(values));
|
|
393
|
+
self
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/// Build the message into serialized bytes
|
|
397
|
+
///
|
|
398
|
+
/// # Errors
|
|
399
|
+
///
|
|
400
|
+
/// Returns an error if the message cannot be serialized to JSON.
|
|
401
|
+
pub fn build(&self) -> Result<Bytes, Box<dyn std::error::Error>> {
|
|
402
|
+
let json_value = Value::Object(self.fields.clone());
|
|
403
|
+
let serialized = serde_json::to_vec(&json_value)?;
|
|
404
|
+
Ok(Bytes::from(serialized))
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/// Clear all fields from the message
|
|
408
|
+
pub fn clear(&mut self) -> &mut Self {
|
|
409
|
+
self.fields.clear();
|
|
410
|
+
self
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/// Get the current fields as a JSON object
|
|
414
|
+
pub fn as_json(&self) -> Value {
|
|
415
|
+
Value::Object(self.fields.clone())
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/// Get the number of fields in the message
|
|
419
|
+
pub fn field_count(&self) -> usize {
|
|
420
|
+
self.fields.len()
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
impl Default for ProtobufMessageBuilder {
|
|
425
|
+
fn default() -> Self {
|
|
426
|
+
Self::new()
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/// Create test metadata with common default headers
|
|
431
|
+
///
|
|
432
|
+
/// Useful for constructing standard gRPC request metadata for testing.
|
|
433
|
+
///
|
|
434
|
+
/// # Example
|
|
435
|
+
///
|
|
436
|
+
/// ```ignore
|
|
437
|
+
/// let metadata = create_test_metadata();
|
|
438
|
+
/// // metadata now contains typical gRPC headers
|
|
439
|
+
/// ```
|
|
440
|
+
pub fn create_test_metadata() -> MetadataMap {
|
|
441
|
+
let mut metadata = MetadataMap::new();
|
|
442
|
+
metadata.insert("user-agent", "spikard-test/1.0".parse().unwrap());
|
|
443
|
+
metadata.insert("content-type", "application/grpc".parse().unwrap());
|
|
444
|
+
metadata
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/// Create test metadata with custom headers
|
|
448
|
+
///
|
|
449
|
+
/// Allows building metadata from a HashMap of key-value pairs.
|
|
450
|
+
///
|
|
451
|
+
/// # Arguments
|
|
452
|
+
///
|
|
453
|
+
/// * `headers` - HashMap of header names to values (String-based)
|
|
454
|
+
///
|
|
455
|
+
/// # Example
|
|
456
|
+
///
|
|
457
|
+
/// ```ignore
|
|
458
|
+
/// use std::collections::HashMap;
|
|
459
|
+
///
|
|
460
|
+
/// let mut headers = HashMap::new();
|
|
461
|
+
/// headers.insert("authorization".to_string(), "Bearer token123".to_string());
|
|
462
|
+
/// headers.insert("x-custom".to_string(), "value".to_string());
|
|
463
|
+
///
|
|
464
|
+
/// let metadata = create_test_metadata_with_headers(&headers).unwrap();
|
|
465
|
+
/// ```
|
|
466
|
+
pub fn create_test_metadata_with_headers(
|
|
467
|
+
headers: &std::collections::HashMap<String, String>,
|
|
468
|
+
) -> Result<MetadataMap, Box<dyn std::error::Error>> {
|
|
469
|
+
use std::str::FromStr;
|
|
470
|
+
let mut metadata = MetadataMap::new();
|
|
471
|
+
for (key, value) in headers {
|
|
472
|
+
let meta_key = tonic::metadata::MetadataKey::from_str(key)?;
|
|
473
|
+
metadata.insert(meta_key, value.parse()?);
|
|
474
|
+
}
|
|
475
|
+
Ok(metadata)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/// Add authentication metadata to an existing MetadataMap
|
|
479
|
+
///
|
|
480
|
+
/// Adds a standard Bearer token authorization header.
|
|
481
|
+
///
|
|
482
|
+
/// # Arguments
|
|
483
|
+
///
|
|
484
|
+
/// * `metadata` - The metadata map to modify
|
|
485
|
+
/// * `token` - The authentication token
|
|
486
|
+
///
|
|
487
|
+
/// # Example
|
|
488
|
+
///
|
|
489
|
+
/// ```ignore
|
|
490
|
+
/// let mut metadata = create_test_metadata();
|
|
491
|
+
/// add_auth_metadata(&mut metadata, "secret_token_123");
|
|
492
|
+
/// ```
|
|
493
|
+
pub fn add_auth_metadata(metadata: &mut MetadataMap, token: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
494
|
+
let auth_value = format!("Bearer {}", token);
|
|
495
|
+
metadata.insert("authorization", auth_value.parse()?);
|
|
496
|
+
Ok(())
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/// Add custom metadata header to an existing MetadataMap
|
|
500
|
+
///
|
|
501
|
+
/// # Arguments
|
|
502
|
+
///
|
|
503
|
+
/// * `metadata` - The metadata map to modify
|
|
504
|
+
/// * `key` - The header name
|
|
505
|
+
/// * `value` - The header value
|
|
506
|
+
///
|
|
507
|
+
/// # Example
|
|
508
|
+
///
|
|
509
|
+
/// ```ignore
|
|
510
|
+
/// let mut metadata = create_test_metadata();
|
|
511
|
+
/// add_metadata_header(&mut metadata, "x-request-id", "req-123")?;
|
|
512
|
+
/// ```
|
|
513
|
+
pub fn add_metadata_header(
|
|
514
|
+
metadata: &mut MetadataMap,
|
|
515
|
+
key: &str,
|
|
516
|
+
value: &str,
|
|
517
|
+
) -> Result<(), Box<dyn std::error::Error>> {
|
|
518
|
+
use std::str::FromStr;
|
|
519
|
+
let meta_key = tonic::metadata::MetadataKey::from_str(key)?;
|
|
520
|
+
metadata.insert(meta_key, value.parse()?);
|
|
521
|
+
Ok(())
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/// Mock gRPC handler for testing
|
|
525
|
+
///
|
|
526
|
+
/// A simple mock handler that always returns a fixed response. Useful for
|
|
527
|
+
/// basic integration tests that just need a handler to exist.
|
|
528
|
+
pub struct MockGrpcHandler {
|
|
529
|
+
service_name: &'static str,
|
|
530
|
+
response_payload: Bytes,
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
impl MockGrpcHandler {
|
|
534
|
+
/// Create a new mock handler
|
|
535
|
+
///
|
|
536
|
+
/// # Arguments
|
|
537
|
+
///
|
|
538
|
+
/// * `service_name` - The service name this handler serves (must be a static string)
|
|
539
|
+
/// * `response_payload` - The payload to return in responses
|
|
540
|
+
pub fn new(service_name: &'static str, response_payload: impl Into<Bytes>) -> Self {
|
|
541
|
+
Self {
|
|
542
|
+
service_name,
|
|
543
|
+
response_payload: response_payload.into(),
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/// Create a mock handler that returns JSON
|
|
548
|
+
pub fn with_json(service_name: &'static str, json: Value) -> Self {
|
|
549
|
+
let bytes = serde_json::to_vec(&json).unwrap_or_default();
|
|
550
|
+
Self::new(service_name, Bytes::from(bytes))
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
impl GrpcHandler for MockGrpcHandler {
|
|
555
|
+
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
556
|
+
let response = GrpcResponseData {
|
|
557
|
+
payload: self.response_payload.clone(),
|
|
558
|
+
metadata: MetadataMap::new(),
|
|
559
|
+
};
|
|
560
|
+
Box::pin(async move { Ok(response) })
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
fn service_name(&self) -> &'static str {
|
|
564
|
+
self.service_name
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/// Error mock handler for testing error responses
|
|
569
|
+
///
|
|
570
|
+
/// A mock handler that always returns an error. Useful for testing error handling.
|
|
571
|
+
pub struct ErrorMockHandler {
|
|
572
|
+
service_name: String,
|
|
573
|
+
error_code: Code,
|
|
574
|
+
error_message: String,
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
impl ErrorMockHandler {
|
|
578
|
+
/// Create a new error mock handler
|
|
579
|
+
///
|
|
580
|
+
/// # Arguments
|
|
581
|
+
///
|
|
582
|
+
/// * `service_name` - The service name this handler serves
|
|
583
|
+
/// * `error_code` - The gRPC error code to return
|
|
584
|
+
/// * `error_message` - The error message
|
|
585
|
+
pub fn new(service_name: impl Into<String>, error_code: Code, error_message: impl Into<String>) -> Self {
|
|
586
|
+
Self {
|
|
587
|
+
service_name: service_name.into(),
|
|
588
|
+
error_code,
|
|
589
|
+
error_message: error_message.into(),
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
impl GrpcHandler for ErrorMockHandler {
|
|
595
|
+
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
596
|
+
let message = self.error_message.clone();
|
|
597
|
+
let code = self.error_code;
|
|
598
|
+
Box::pin(async move { Err(tonic::Status::new(code, message)) })
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
fn service_name(&self) -> &'static str {
|
|
602
|
+
"mock.ErrorMockService"
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/// Echo mock handler for testing request/response flow
|
|
607
|
+
///
|
|
608
|
+
/// A mock handler that echoes the request payload back as the response.
|
|
609
|
+
pub struct EchoMockHandler {
|
|
610
|
+
service_name: String,
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
impl EchoMockHandler {
|
|
614
|
+
/// Create a new echo mock handler
|
|
615
|
+
///
|
|
616
|
+
/// # Arguments
|
|
617
|
+
///
|
|
618
|
+
/// * `service_name` - The service name this handler serves
|
|
619
|
+
pub fn new(service_name: impl Into<String>) -> Self {
|
|
620
|
+
Self {
|
|
621
|
+
service_name: service_name.into(),
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
impl GrpcHandler for EchoMockHandler {
|
|
627
|
+
fn call(&self, request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
628
|
+
let payload = request.payload;
|
|
629
|
+
Box::pin(async move {
|
|
630
|
+
Ok(GrpcResponseData {
|
|
631
|
+
payload,
|
|
632
|
+
metadata: MetadataMap::new(),
|
|
633
|
+
})
|
|
634
|
+
})
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
fn service_name(&self) -> &'static str {
|
|
638
|
+
"mock.EchoMockService"
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
#[cfg(test)]
|
|
643
|
+
mod tests {
|
|
644
|
+
use super::*;
|
|
645
|
+
|
|
646
|
+
#[test]
|
|
647
|
+
fn test_grpc_test_server_new() {
|
|
648
|
+
let server = GrpcTestServer::new();
|
|
649
|
+
assert_eq!(server.url(), "http://localhost:50051");
|
|
650
|
+
assert_eq!(server.service_count(), 0);
|
|
651
|
+
assert!(server.handlers().is_empty());
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
#[test]
|
|
655
|
+
fn test_grpc_test_server_with_url() {
|
|
656
|
+
let server = GrpcTestServer::with_url("http://localhost:8080");
|
|
657
|
+
assert_eq!(server.url(), "http://localhost:8080");
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
#[test]
|
|
661
|
+
fn test_grpc_test_server_register_service() {
|
|
662
|
+
let mut server = GrpcTestServer::new();
|
|
663
|
+
let handler = Arc::new(MockGrpcHandler::new("test.Service", "response"));
|
|
664
|
+
|
|
665
|
+
server.register_service(handler);
|
|
666
|
+
|
|
667
|
+
assert_eq!(server.service_count(), 1);
|
|
668
|
+
assert!(!server.handlers().is_empty());
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
#[test]
|
|
672
|
+
fn test_grpc_test_server_register_multiple_services() {
|
|
673
|
+
let mut server = GrpcTestServer::new();
|
|
674
|
+
let handler1 = Arc::new(MockGrpcHandler::new("service1", "response1"));
|
|
675
|
+
let handler2 = Arc::new(MockGrpcHandler::new("service2", "response2"));
|
|
676
|
+
|
|
677
|
+
server.register_service(handler1);
|
|
678
|
+
server.register_service(handler2);
|
|
679
|
+
|
|
680
|
+
assert_eq!(server.service_count(), 2);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
#[test]
|
|
684
|
+
fn test_grpc_test_server_default() {
|
|
685
|
+
let server = GrpcTestServer::default();
|
|
686
|
+
assert_eq!(server.service_count(), 0);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
#[tokio::test]
|
|
690
|
+
async fn test_mock_grpc_handler_basic() {
|
|
691
|
+
let handler = MockGrpcHandler::new("test.Service", Bytes::from("response"));
|
|
692
|
+
let request = GrpcRequestData {
|
|
693
|
+
service_name: "test.Service".to_string(),
|
|
694
|
+
method_name: "TestMethod".to_string(),
|
|
695
|
+
payload: Bytes::from("request"),
|
|
696
|
+
metadata: MetadataMap::new(),
|
|
697
|
+
};
|
|
698
|
+
|
|
699
|
+
let result = handler.call(request).await;
|
|
700
|
+
assert!(result.is_ok());
|
|
701
|
+
|
|
702
|
+
let response = result.unwrap();
|
|
703
|
+
assert_eq!(response.payload, Bytes::from("response"));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
#[tokio::test]
|
|
707
|
+
async fn test_mock_grpc_handler_with_json() {
|
|
708
|
+
let json_response = json!({"id": 1, "name": "Alice"});
|
|
709
|
+
let handler = MockGrpcHandler::with_json("test.UserService", json_response.clone());
|
|
710
|
+
|
|
711
|
+
let request = GrpcRequestData {
|
|
712
|
+
service_name: "test.UserService".to_string(),
|
|
713
|
+
method_name: "GetUser".to_string(),
|
|
714
|
+
payload: Bytes::from("{}"),
|
|
715
|
+
metadata: MetadataMap::new(),
|
|
716
|
+
};
|
|
717
|
+
|
|
718
|
+
let result = handler.call(request).await;
|
|
719
|
+
assert!(result.is_ok());
|
|
720
|
+
|
|
721
|
+
let response = result.unwrap();
|
|
722
|
+
let deserialized = serde_json::from_slice::<Value>(&response.payload).unwrap();
|
|
723
|
+
assert_eq!(deserialized, json_response);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
#[tokio::test]
|
|
727
|
+
async fn test_error_mock_handler() {
|
|
728
|
+
let handler = ErrorMockHandler::new("test.Service", Code::NotFound, "Resource not found");
|
|
729
|
+
|
|
730
|
+
let request = GrpcRequestData {
|
|
731
|
+
service_name: "test.Service".to_string(),
|
|
732
|
+
method_name: "GetResource".to_string(),
|
|
733
|
+
payload: Bytes::new(),
|
|
734
|
+
metadata: MetadataMap::new(),
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
let result = handler.call(request).await;
|
|
738
|
+
assert!(result.is_err());
|
|
739
|
+
|
|
740
|
+
let status = result.unwrap_err();
|
|
741
|
+
assert_eq!(status.code(), Code::NotFound);
|
|
742
|
+
assert_eq!(status.message(), "Resource not found");
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
#[tokio::test]
|
|
746
|
+
async fn test_echo_mock_handler() {
|
|
747
|
+
let handler = EchoMockHandler::new("test.Service");
|
|
748
|
+
let payload = Bytes::from("echo this");
|
|
749
|
+
|
|
750
|
+
let request = GrpcRequestData {
|
|
751
|
+
service_name: "test.Service".to_string(),
|
|
752
|
+
method_name: "Echo".to_string(),
|
|
753
|
+
payload: payload.clone(),
|
|
754
|
+
metadata: MetadataMap::new(),
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
let result = handler.call(request).await;
|
|
758
|
+
assert!(result.is_ok());
|
|
759
|
+
|
|
760
|
+
let response = result.unwrap();
|
|
761
|
+
assert_eq!(response.payload, payload);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
#[test]
|
|
765
|
+
fn test_protobuf_message_builder_new() {
|
|
766
|
+
let builder = ProtobufMessageBuilder::new();
|
|
767
|
+
assert_eq!(builder.field_count(), 0);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
#[test]
|
|
771
|
+
fn test_protobuf_message_builder_add_field() {
|
|
772
|
+
let mut builder = ProtobufMessageBuilder::new();
|
|
773
|
+
builder.add_field("name", "Alice").add_field("age", 30);
|
|
774
|
+
|
|
775
|
+
assert_eq!(builder.field_count(), 2);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
#[test]
|
|
779
|
+
fn test_protobuf_message_builder_add_string_field() {
|
|
780
|
+
let mut builder = ProtobufMessageBuilder::new();
|
|
781
|
+
builder.add_string_field("name", "Bob");
|
|
782
|
+
|
|
783
|
+
let json = builder.as_json();
|
|
784
|
+
assert_eq!(json["name"], "Bob");
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
#[test]
|
|
788
|
+
fn test_protobuf_message_builder_add_int_field() {
|
|
789
|
+
let mut builder = ProtobufMessageBuilder::new();
|
|
790
|
+
builder.add_int_field("age", 42);
|
|
791
|
+
|
|
792
|
+
let json = builder.as_json();
|
|
793
|
+
assert_eq!(json["age"], 42);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
#[test]
|
|
797
|
+
fn test_protobuf_message_builder_add_bool_field() {
|
|
798
|
+
let mut builder = ProtobufMessageBuilder::new();
|
|
799
|
+
builder.add_bool_field("active", true);
|
|
800
|
+
|
|
801
|
+
let json = builder.as_json();
|
|
802
|
+
assert_eq!(json["active"], true);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
#[test]
|
|
806
|
+
fn test_protobuf_message_builder_add_object_field() {
|
|
807
|
+
let mut builder = ProtobufMessageBuilder::new();
|
|
808
|
+
builder.add_object_field("user", json!({"name": "Alice", "id": 1}));
|
|
809
|
+
|
|
810
|
+
let json = builder.as_json();
|
|
811
|
+
assert_eq!(json["user"]["name"], "Alice");
|
|
812
|
+
assert_eq!(json["user"]["id"], 1);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
#[test]
|
|
816
|
+
fn test_protobuf_message_builder_add_array_field() {
|
|
817
|
+
let mut builder = ProtobufMessageBuilder::new();
|
|
818
|
+
builder.add_array_field("tags", vec![json!("rust"), json!("testing"), json!("grpc")]);
|
|
819
|
+
|
|
820
|
+
let json = builder.as_json();
|
|
821
|
+
assert_eq!(json["tags"].as_array().unwrap().len(), 3);
|
|
822
|
+
assert_eq!(json["tags"][0], "rust");
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
#[test]
|
|
826
|
+
fn test_protobuf_message_builder_build() {
|
|
827
|
+
let mut builder = ProtobufMessageBuilder::new();
|
|
828
|
+
builder.add_field("id", 1).add_field("name", "Alice");
|
|
829
|
+
|
|
830
|
+
let bytes = builder.build().unwrap();
|
|
831
|
+
let deserialized = serde_json::from_slice::<Value>(&bytes).unwrap();
|
|
832
|
+
|
|
833
|
+
assert_eq!(deserialized["id"], 1);
|
|
834
|
+
assert_eq!(deserialized["name"], "Alice");
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
#[test]
|
|
838
|
+
fn test_protobuf_message_builder_clear() {
|
|
839
|
+
let mut builder = ProtobufMessageBuilder::new();
|
|
840
|
+
builder.add_field("id", 1).add_field("name", "Alice");
|
|
841
|
+
assert_eq!(builder.field_count(), 2);
|
|
842
|
+
|
|
843
|
+
builder.clear();
|
|
844
|
+
assert_eq!(builder.field_count(), 0);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
#[test]
|
|
848
|
+
fn test_protobuf_message_builder_fluent_api() {
|
|
849
|
+
let mut builder = ProtobufMessageBuilder::new();
|
|
850
|
+
builder
|
|
851
|
+
.add_string_field("email", "alice@example.com")
|
|
852
|
+
.add_int_field("age", 30)
|
|
853
|
+
.add_bool_field("verified", true);
|
|
854
|
+
|
|
855
|
+
assert_eq!(builder.field_count(), 3);
|
|
856
|
+
|
|
857
|
+
let json = builder.as_json();
|
|
858
|
+
assert_eq!(json["email"], "alice@example.com");
|
|
859
|
+
assert_eq!(json["age"], 30);
|
|
860
|
+
assert_eq!(json["verified"], true);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
#[test]
|
|
864
|
+
fn test_create_test_metadata() {
|
|
865
|
+
let metadata = create_test_metadata();
|
|
866
|
+
|
|
867
|
+
assert!(metadata.get("user-agent").is_some());
|
|
868
|
+
assert!(metadata.get("content-type").is_some());
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
#[test]
|
|
872
|
+
fn test_create_test_metadata_with_headers() {
|
|
873
|
+
use std::collections::HashMap;
|
|
874
|
+
|
|
875
|
+
let mut headers = HashMap::new();
|
|
876
|
+
headers.insert("authorization".to_string(), "Bearer token".to_string());
|
|
877
|
+
headers.insert("x-custom".to_string(), "value".to_string());
|
|
878
|
+
|
|
879
|
+
let metadata = create_test_metadata_with_headers(&headers).unwrap();
|
|
880
|
+
|
|
881
|
+
assert!(metadata.get("authorization").is_some());
|
|
882
|
+
assert!(metadata.get("x-custom").is_some());
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
#[test]
|
|
886
|
+
fn test_add_auth_metadata() {
|
|
887
|
+
let mut metadata = create_test_metadata();
|
|
888
|
+
add_auth_metadata(&mut metadata, "secret_token_123").unwrap();
|
|
889
|
+
|
|
890
|
+
let auth_header = metadata.get("authorization").unwrap();
|
|
891
|
+
assert_eq!(auth_header.to_str().unwrap(), "Bearer secret_token_123");
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
#[test]
|
|
895
|
+
fn test_add_metadata_header() {
|
|
896
|
+
let mut metadata = create_test_metadata();
|
|
897
|
+
add_metadata_header(&mut metadata, "x-request-id", "req-123").unwrap();
|
|
898
|
+
|
|
899
|
+
let header = metadata.get("x-request-id").unwrap();
|
|
900
|
+
assert_eq!(header.to_str().unwrap(), "req-123");
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
#[test]
|
|
904
|
+
fn test_assert_grpc_response_matching() {
|
|
905
|
+
let response = GrpcResponseData {
|
|
906
|
+
payload: Bytes::from(r#"{"id": 1, "name": "Alice"}"#),
|
|
907
|
+
metadata: MetadataMap::new(),
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
let expected = json!({"id": 1, "name": "Alice"});
|
|
911
|
+
assert_grpc_response(response, expected);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
#[test]
|
|
915
|
+
fn test_assert_grpc_status_error() {
|
|
916
|
+
let result: GrpcHandlerResult = Err(tonic::Status::not_found("Resource not found"));
|
|
917
|
+
|
|
918
|
+
assert_grpc_status(&result, Code::NotFound);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
#[test]
|
|
922
|
+
#[should_panic(expected = "Expected status")]
|
|
923
|
+
fn test_assert_grpc_status_mismatch() {
|
|
924
|
+
let result: GrpcHandlerResult = Err(tonic::Status::not_found("Resource not found"));
|
|
925
|
+
|
|
926
|
+
assert_grpc_status(&result, Code::InvalidArgument);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
#[test]
|
|
930
|
+
#[should_panic(expected = "Expected error status")]
|
|
931
|
+
fn test_assert_grpc_status_on_success() {
|
|
932
|
+
let result: GrpcHandlerResult = Ok(GrpcResponseData {
|
|
933
|
+
payload: Bytes::new(),
|
|
934
|
+
metadata: MetadataMap::new(),
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
assert_grpc_status(&result, Code::NotFound);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
#[test]
|
|
941
|
+
fn test_protobuf_message_builder_default() {
|
|
942
|
+
let builder = ProtobufMessageBuilder::default();
|
|
943
|
+
assert_eq!(builder.field_count(), 0);
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
#[tokio::test]
|
|
947
|
+
async fn test_send_unary_request_with_mock_handler() {
|
|
948
|
+
let mut server = GrpcTestServer::new();
|
|
949
|
+
let _response_payload = json!({"result": "success"});
|
|
950
|
+
|
|
951
|
+
// Create a custom handler that reports the correct service name
|
|
952
|
+
struct TestHandler;
|
|
953
|
+
impl GrpcHandler for TestHandler {
|
|
954
|
+
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
955
|
+
Box::pin(async {
|
|
956
|
+
Ok(GrpcResponseData {
|
|
957
|
+
payload: serde_json::to_vec(&json!({"result": "success"})).unwrap().into(),
|
|
958
|
+
metadata: MetadataMap::new(),
|
|
959
|
+
})
|
|
960
|
+
})
|
|
961
|
+
}
|
|
962
|
+
fn service_name(&self) -> &'static str {
|
|
963
|
+
"test.TestService"
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
server.register_service(Arc::new(TestHandler));
|
|
968
|
+
|
|
969
|
+
let message = ProtobufMessageBuilder::new()
|
|
970
|
+
.add_field("input", "test")
|
|
971
|
+
.build()
|
|
972
|
+
.unwrap();
|
|
973
|
+
|
|
974
|
+
let result = send_unary_request(
|
|
975
|
+
&server,
|
|
976
|
+
"test.TestService",
|
|
977
|
+
"TestMethod",
|
|
978
|
+
message,
|
|
979
|
+
create_test_metadata(),
|
|
980
|
+
)
|
|
981
|
+
.await;
|
|
982
|
+
|
|
983
|
+
assert!(result.is_ok());
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
#[tokio::test]
|
|
987
|
+
async fn test_send_unary_request_service_not_found() {
|
|
988
|
+
let server = GrpcTestServer::new();
|
|
989
|
+
let message = Bytes::from("test");
|
|
990
|
+
|
|
991
|
+
let result = send_unary_request(
|
|
992
|
+
&server,
|
|
993
|
+
"nonexistent.Service",
|
|
994
|
+
"Method",
|
|
995
|
+
message,
|
|
996
|
+
create_test_metadata(),
|
|
997
|
+
)
|
|
998
|
+
.await;
|
|
999
|
+
|
|
1000
|
+
assert!(result.is_err());
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
#[test]
|
|
1004
|
+
fn test_error_mock_handler_invalid_argument() {
|
|
1005
|
+
let handler = ErrorMockHandler::new("test.Service", Code::InvalidArgument, "Bad input");
|
|
1006
|
+
assert_eq!(handler.error_code, Code::InvalidArgument);
|
|
1007
|
+
assert_eq!(handler.error_message, "Bad input");
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
#[test]
|
|
1011
|
+
fn test_echo_mock_handler_creates_with_service_name() {
|
|
1012
|
+
let handler = EchoMockHandler::new("mypackage.EchoService");
|
|
1013
|
+
assert_eq!(handler.service_name, "mypackage.EchoService");
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
#[test]
|
|
1017
|
+
fn test_grpc_test_client_creation() {
|
|
1018
|
+
let _client = create_grpc_test_client();
|
|
1019
|
+
let _default_client = GrpcTestClient::default();
|
|
1020
|
+
let _new_client = GrpcTestClient::new();
|
|
1021
|
+
// Just ensure it can be created without panicking
|
|
1022
|
+
}
|
|
1023
|
+
}
|