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
|
@@ -19,8 +19,16 @@
|
|
|
19
19
|
|
|
20
20
|
pub mod handlers;
|
|
21
21
|
pub mod test_builders;
|
|
22
|
+
pub mod grpc_helpers;
|
|
22
23
|
|
|
23
24
|
#[allow(unused_imports)]
|
|
24
25
|
pub use handlers::{EchoHandler, ErrorHandler, JsonHandler, PanicHandler, SuccessHandler};
|
|
25
26
|
#[allow(unused_imports)]
|
|
26
27
|
pub use test_builders::{HandlerBuilder, RequestBuilder, assert_status, load_fixture, parse_json_body};
|
|
28
|
+
#[allow(unused_imports)]
|
|
29
|
+
pub use grpc_helpers::{
|
|
30
|
+
GrpcTestServer, GrpcTestClient, create_grpc_test_client, send_unary_request,
|
|
31
|
+
assert_grpc_response, assert_grpc_status, ProtobufMessageBuilder, create_test_metadata,
|
|
32
|
+
add_auth_metadata, add_metadata_header, create_test_metadata_with_headers,
|
|
33
|
+
MockGrpcHandler, ErrorMockHandler, EchoMockHandler,
|
|
34
|
+
};
|
|
@@ -0,0 +1,653 @@
|
|
|
1
|
+
//! Comprehensive gRPC error handling tests
|
|
2
|
+
//!
|
|
3
|
+
//! Tests all 17 standard gRPC status codes including:
|
|
4
|
+
//! - OK (0)
|
|
5
|
+
//! - CANCELLED (1)
|
|
6
|
+
//! - UNKNOWN (2)
|
|
7
|
+
//! - INVALID_ARGUMENT (3)
|
|
8
|
+
//! - DEADLINE_EXCEEDED (4)
|
|
9
|
+
//! - NOT_FOUND (5)
|
|
10
|
+
//! - ALREADY_EXISTS (6)
|
|
11
|
+
//! - PERMISSION_DENIED (7)
|
|
12
|
+
//! - RESOURCE_EXHAUSTED (8)
|
|
13
|
+
//! - FAILED_PRECONDITION (9)
|
|
14
|
+
//! - ABORTED (10)
|
|
15
|
+
//! - OUT_OF_RANGE (11)
|
|
16
|
+
//! - UNIMPLEMENTED (12)
|
|
17
|
+
//! - INTERNAL (13)
|
|
18
|
+
//! - UNAVAILABLE (14)
|
|
19
|
+
//! - DATA_LOSS (15)
|
|
20
|
+
//! - UNAUTHENTICATED (16)
|
|
21
|
+
|
|
22
|
+
use crate::common::grpc_helpers::*;
|
|
23
|
+
use bytes::Bytes;
|
|
24
|
+
use std::sync::Arc;
|
|
25
|
+
use tonic::Code;
|
|
26
|
+
|
|
27
|
+
mod common;
|
|
28
|
+
|
|
29
|
+
/// Test OK status (success case)
|
|
30
|
+
#[tokio::test]
|
|
31
|
+
async fn test_status_ok_success() {
|
|
32
|
+
let mut server = GrpcTestServer::new();
|
|
33
|
+
|
|
34
|
+
struct SuccessHandler;
|
|
35
|
+
impl spikard_http::grpc::GrpcHandler for SuccessHandler {
|
|
36
|
+
fn call(&self, _request: spikard_http::grpc::GrpcRequestData)
|
|
37
|
+
-> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
|
|
38
|
+
{
|
|
39
|
+
Box::pin(async {
|
|
40
|
+
Ok(spikard_http::grpc::GrpcResponseData {
|
|
41
|
+
payload: Bytes::from(r#"{"result": "success"}"#),
|
|
42
|
+
metadata: tonic::metadata::MetadataMap::new(),
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
fn service_name(&self) -> &'static str {
|
|
47
|
+
"test.SuccessService"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
server.register_service(Arc::new(SuccessHandler));
|
|
52
|
+
|
|
53
|
+
let response = send_unary_request(
|
|
54
|
+
&server,
|
|
55
|
+
"test.SuccessService",
|
|
56
|
+
"DoWork",
|
|
57
|
+
Bytes::from("{}"),
|
|
58
|
+
create_test_metadata(),
|
|
59
|
+
)
|
|
60
|
+
.await;
|
|
61
|
+
|
|
62
|
+
assert!(response.is_ok());
|
|
63
|
+
let resp = response.unwrap();
|
|
64
|
+
assert_grpc_response(resp, serde_json::json!({"result": "success"}));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// Test CANCELLED status code
|
|
68
|
+
#[tokio::test]
|
|
69
|
+
async fn test_status_cancelled() {
|
|
70
|
+
let mut server = GrpcTestServer::new();
|
|
71
|
+
|
|
72
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
73
|
+
"test.CancelledService",
|
|
74
|
+
Code::Cancelled,
|
|
75
|
+
"Request was cancelled by the client",
|
|
76
|
+
));
|
|
77
|
+
server.register_service(handler);
|
|
78
|
+
|
|
79
|
+
let result = send_unary_request(
|
|
80
|
+
&server,
|
|
81
|
+
"test.CancelledService",
|
|
82
|
+
"CancelledOperation",
|
|
83
|
+
Bytes::from("{}"),
|
|
84
|
+
create_test_metadata(),
|
|
85
|
+
)
|
|
86
|
+
.await;
|
|
87
|
+
|
|
88
|
+
// Verify error occurred
|
|
89
|
+
assert!(result.is_err());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Test UNKNOWN status code
|
|
93
|
+
#[tokio::test]
|
|
94
|
+
async fn test_status_unknown() {
|
|
95
|
+
let mut server = GrpcTestServer::new();
|
|
96
|
+
|
|
97
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
98
|
+
"test.UnknownService",
|
|
99
|
+
Code::Unknown,
|
|
100
|
+
"Unknown error occurred",
|
|
101
|
+
));
|
|
102
|
+
server.register_service(handler);
|
|
103
|
+
|
|
104
|
+
let result = send_unary_request(
|
|
105
|
+
&server,
|
|
106
|
+
"test.UnknownService",
|
|
107
|
+
"UnknownMethod",
|
|
108
|
+
Bytes::from("{}"),
|
|
109
|
+
create_test_metadata(),
|
|
110
|
+
)
|
|
111
|
+
.await;
|
|
112
|
+
|
|
113
|
+
assert!(result.is_err());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/// Test INVALID_ARGUMENT status code
|
|
117
|
+
#[tokio::test]
|
|
118
|
+
async fn test_status_invalid_argument() {
|
|
119
|
+
let mut server = GrpcTestServer::new();
|
|
120
|
+
|
|
121
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
122
|
+
"test.ValidationService",
|
|
123
|
+
Code::InvalidArgument,
|
|
124
|
+
"Field 'email' must be a valid email address",
|
|
125
|
+
));
|
|
126
|
+
server.register_service(handler);
|
|
127
|
+
|
|
128
|
+
let result = send_unary_request(
|
|
129
|
+
&server,
|
|
130
|
+
"test.ValidationService",
|
|
131
|
+
"ValidateInput",
|
|
132
|
+
Bytes::from("{}"),
|
|
133
|
+
create_test_metadata(),
|
|
134
|
+
)
|
|
135
|
+
.await;
|
|
136
|
+
|
|
137
|
+
assert!(result.is_err());
|
|
138
|
+
// Error verified
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Test DEADLINE_EXCEEDED status code
|
|
142
|
+
#[tokio::test]
|
|
143
|
+
async fn test_status_deadline_exceeded() {
|
|
144
|
+
let mut server = GrpcTestServer::new();
|
|
145
|
+
|
|
146
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
147
|
+
"test.TimeoutService",
|
|
148
|
+
Code::DeadlineExceeded,
|
|
149
|
+
"Request took too long to process (timeout)",
|
|
150
|
+
));
|
|
151
|
+
server.register_service(handler);
|
|
152
|
+
|
|
153
|
+
let result = send_unary_request(
|
|
154
|
+
&server,
|
|
155
|
+
"test.TimeoutService",
|
|
156
|
+
"SlowOperation",
|
|
157
|
+
Bytes::from("{}"),
|
|
158
|
+
create_test_metadata(),
|
|
159
|
+
)
|
|
160
|
+
.await;
|
|
161
|
+
|
|
162
|
+
assert!(result.is_err());
|
|
163
|
+
// Error verified
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// Test NOT_FOUND status code
|
|
167
|
+
#[tokio::test]
|
|
168
|
+
async fn test_status_not_found() {
|
|
169
|
+
let mut server = GrpcTestServer::new();
|
|
170
|
+
|
|
171
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
172
|
+
"test.NotFoundService",
|
|
173
|
+
Code::NotFound,
|
|
174
|
+
"User with ID 12345 does not exist",
|
|
175
|
+
));
|
|
176
|
+
server.register_service(handler);
|
|
177
|
+
|
|
178
|
+
let result = send_unary_request(
|
|
179
|
+
&server,
|
|
180
|
+
"test.NotFoundService",
|
|
181
|
+
"GetUser",
|
|
182
|
+
Bytes::from("{}"),
|
|
183
|
+
create_test_metadata(),
|
|
184
|
+
)
|
|
185
|
+
.await;
|
|
186
|
+
|
|
187
|
+
assert!(result.is_err());
|
|
188
|
+
// Error verified
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Test ALREADY_EXISTS status code
|
|
192
|
+
#[tokio::test]
|
|
193
|
+
async fn test_status_already_exists() {
|
|
194
|
+
let mut server = GrpcTestServer::new();
|
|
195
|
+
|
|
196
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
197
|
+
"test.CreateService",
|
|
198
|
+
Code::AlreadyExists,
|
|
199
|
+
"User with email 'john@example.com' already exists",
|
|
200
|
+
));
|
|
201
|
+
server.register_service(handler);
|
|
202
|
+
|
|
203
|
+
let result = send_unary_request(
|
|
204
|
+
&server,
|
|
205
|
+
"test.CreateService",
|
|
206
|
+
"CreateUser",
|
|
207
|
+
Bytes::from("{}"),
|
|
208
|
+
create_test_metadata(),
|
|
209
|
+
)
|
|
210
|
+
.await;
|
|
211
|
+
|
|
212
|
+
assert!(result.is_err());
|
|
213
|
+
// Error verified
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/// Test PERMISSION_DENIED status code
|
|
217
|
+
#[tokio::test]
|
|
218
|
+
async fn test_status_permission_denied() {
|
|
219
|
+
let mut server = GrpcTestServer::new();
|
|
220
|
+
|
|
221
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
222
|
+
"test.AuthService",
|
|
223
|
+
Code::PermissionDenied,
|
|
224
|
+
"User does not have permission to delete resources",
|
|
225
|
+
));
|
|
226
|
+
server.register_service(handler);
|
|
227
|
+
|
|
228
|
+
let result = send_unary_request(
|
|
229
|
+
&server,
|
|
230
|
+
"test.AuthService",
|
|
231
|
+
"DeleteResource",
|
|
232
|
+
Bytes::from("{}"),
|
|
233
|
+
create_test_metadata(),
|
|
234
|
+
)
|
|
235
|
+
.await;
|
|
236
|
+
|
|
237
|
+
assert!(result.is_err());
|
|
238
|
+
// Error verified
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/// Test RESOURCE_EXHAUSTED status code
|
|
242
|
+
#[tokio::test]
|
|
243
|
+
async fn test_status_resource_exhausted() {
|
|
244
|
+
let mut server = GrpcTestServer::new();
|
|
245
|
+
|
|
246
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
247
|
+
"test.QuotaService",
|
|
248
|
+
Code::ResourceExhausted,
|
|
249
|
+
"API rate limit exceeded. Maximum 100 requests per minute.",
|
|
250
|
+
));
|
|
251
|
+
server.register_service(handler);
|
|
252
|
+
|
|
253
|
+
let result = send_unary_request(
|
|
254
|
+
&server,
|
|
255
|
+
"test.QuotaService",
|
|
256
|
+
"MakeRequest",
|
|
257
|
+
Bytes::from("{}"),
|
|
258
|
+
create_test_metadata(),
|
|
259
|
+
)
|
|
260
|
+
.await;
|
|
261
|
+
|
|
262
|
+
assert!(result.is_err());
|
|
263
|
+
// Error verified
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/// Test FAILED_PRECONDITION status code
|
|
267
|
+
#[tokio::test]
|
|
268
|
+
async fn test_status_failed_precondition() {
|
|
269
|
+
let mut server = GrpcTestServer::new();
|
|
270
|
+
|
|
271
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
272
|
+
"test.OrderService",
|
|
273
|
+
Code::FailedPrecondition,
|
|
274
|
+
"Cannot process order: inventory for product is out of stock",
|
|
275
|
+
));
|
|
276
|
+
server.register_service(handler);
|
|
277
|
+
|
|
278
|
+
let result = send_unary_request(
|
|
279
|
+
&server,
|
|
280
|
+
"test.OrderService",
|
|
281
|
+
"ProcessOrder",
|
|
282
|
+
Bytes::from("{}"),
|
|
283
|
+
create_test_metadata(),
|
|
284
|
+
)
|
|
285
|
+
.await;
|
|
286
|
+
|
|
287
|
+
assert!(result.is_err());
|
|
288
|
+
// Error verified
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/// Test ABORTED status code
|
|
292
|
+
#[tokio::test]
|
|
293
|
+
async fn test_status_aborted() {
|
|
294
|
+
let mut server = GrpcTestServer::new();
|
|
295
|
+
|
|
296
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
297
|
+
"test.TransactionService",
|
|
298
|
+
Code::Aborted,
|
|
299
|
+
"Transaction was aborted due to concurrency conflict",
|
|
300
|
+
));
|
|
301
|
+
server.register_service(handler);
|
|
302
|
+
|
|
303
|
+
let result = send_unary_request(
|
|
304
|
+
&server,
|
|
305
|
+
"test.TransactionService",
|
|
306
|
+
"CommitTransaction",
|
|
307
|
+
Bytes::from("{}"),
|
|
308
|
+
create_test_metadata(),
|
|
309
|
+
)
|
|
310
|
+
.await;
|
|
311
|
+
|
|
312
|
+
assert!(result.is_err());
|
|
313
|
+
// Error verified
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/// Test OUT_OF_RANGE status code
|
|
317
|
+
#[tokio::test]
|
|
318
|
+
async fn test_status_out_of_range() {
|
|
319
|
+
let mut server = GrpcTestServer::new();
|
|
320
|
+
|
|
321
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
322
|
+
"test.PageService",
|
|
323
|
+
Code::OutOfRange,
|
|
324
|
+
"Requested page 999 is out of range. Only 10 pages available.",
|
|
325
|
+
));
|
|
326
|
+
server.register_service(handler);
|
|
327
|
+
|
|
328
|
+
let result = send_unary_request(
|
|
329
|
+
&server,
|
|
330
|
+
"test.PageService",
|
|
331
|
+
"GetPage",
|
|
332
|
+
Bytes::from("{}"),
|
|
333
|
+
create_test_metadata(),
|
|
334
|
+
)
|
|
335
|
+
.await;
|
|
336
|
+
|
|
337
|
+
assert!(result.is_err());
|
|
338
|
+
// Error verified
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/// Test UNIMPLEMENTED status code
|
|
342
|
+
#[tokio::test]
|
|
343
|
+
async fn test_status_unimplemented() {
|
|
344
|
+
let mut server = GrpcTestServer::new();
|
|
345
|
+
|
|
346
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
347
|
+
"test.ExperimentalService",
|
|
348
|
+
Code::Unimplemented,
|
|
349
|
+
"Method 'AdvancedFeature' is not yet implemented",
|
|
350
|
+
));
|
|
351
|
+
server.register_service(handler);
|
|
352
|
+
|
|
353
|
+
let result = send_unary_request(
|
|
354
|
+
&server,
|
|
355
|
+
"test.ExperimentalService",
|
|
356
|
+
"AdvancedFeature",
|
|
357
|
+
Bytes::from("{}"),
|
|
358
|
+
create_test_metadata(),
|
|
359
|
+
)
|
|
360
|
+
.await;
|
|
361
|
+
|
|
362
|
+
assert!(result.is_err());
|
|
363
|
+
// Error verified
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/// Test INTERNAL status code
|
|
367
|
+
#[tokio::test]
|
|
368
|
+
async fn test_status_internal_error() {
|
|
369
|
+
let mut server = GrpcTestServer::new();
|
|
370
|
+
|
|
371
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
372
|
+
"test.DatabaseService",
|
|
373
|
+
Code::Internal,
|
|
374
|
+
"Internal server error: Database connection pool exhausted",
|
|
375
|
+
));
|
|
376
|
+
server.register_service(handler);
|
|
377
|
+
|
|
378
|
+
let result = send_unary_request(
|
|
379
|
+
&server,
|
|
380
|
+
"test.DatabaseService",
|
|
381
|
+
"QueryData",
|
|
382
|
+
Bytes::from("{}"),
|
|
383
|
+
create_test_metadata(),
|
|
384
|
+
)
|
|
385
|
+
.await;
|
|
386
|
+
|
|
387
|
+
assert!(result.is_err());
|
|
388
|
+
// Error verified
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/// Test UNAVAILABLE status code
|
|
392
|
+
#[tokio::test]
|
|
393
|
+
async fn test_status_unavailable() {
|
|
394
|
+
let mut server = GrpcTestServer::new();
|
|
395
|
+
|
|
396
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
397
|
+
"test.ExternalService",
|
|
398
|
+
Code::Unavailable,
|
|
399
|
+
"Service is temporarily unavailable. Please retry later.",
|
|
400
|
+
));
|
|
401
|
+
server.register_service(handler);
|
|
402
|
+
|
|
403
|
+
let result = send_unary_request(
|
|
404
|
+
&server,
|
|
405
|
+
"test.ExternalService",
|
|
406
|
+
"CallExternal",
|
|
407
|
+
Bytes::from("{}"),
|
|
408
|
+
create_test_metadata(),
|
|
409
|
+
)
|
|
410
|
+
.await;
|
|
411
|
+
|
|
412
|
+
assert!(result.is_err());
|
|
413
|
+
// Error verified
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/// Test DATA_LOSS status code
|
|
417
|
+
#[tokio::test]
|
|
418
|
+
async fn test_status_data_loss() {
|
|
419
|
+
let mut server = GrpcTestServer::new();
|
|
420
|
+
|
|
421
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
422
|
+
"test.FileService",
|
|
423
|
+
Code::DataLoss,
|
|
424
|
+
"Data loss detected: corrupted database records",
|
|
425
|
+
));
|
|
426
|
+
server.register_service(handler);
|
|
427
|
+
|
|
428
|
+
let result = send_unary_request(
|
|
429
|
+
&server,
|
|
430
|
+
"test.FileService",
|
|
431
|
+
"RecoverData",
|
|
432
|
+
Bytes::from("{}"),
|
|
433
|
+
create_test_metadata(),
|
|
434
|
+
)
|
|
435
|
+
.await;
|
|
436
|
+
|
|
437
|
+
assert!(result.is_err());
|
|
438
|
+
// Error verified
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/// Test UNAUTHENTICATED status code
|
|
442
|
+
#[tokio::test]
|
|
443
|
+
async fn test_status_unauthenticated() {
|
|
444
|
+
let mut server = GrpcTestServer::new();
|
|
445
|
+
|
|
446
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
447
|
+
"test.SecureService",
|
|
448
|
+
Code::Unauthenticated,
|
|
449
|
+
"Authentication required. Please provide valid credentials.",
|
|
450
|
+
));
|
|
451
|
+
server.register_service(handler);
|
|
452
|
+
|
|
453
|
+
let result = send_unary_request(
|
|
454
|
+
&server,
|
|
455
|
+
"test.SecureService",
|
|
456
|
+
"SecureOperation",
|
|
457
|
+
Bytes::from("{}"),
|
|
458
|
+
create_test_metadata(),
|
|
459
|
+
)
|
|
460
|
+
.await;
|
|
461
|
+
|
|
462
|
+
assert!(result.is_err());
|
|
463
|
+
// Error verified
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/// Test error message propagation
|
|
467
|
+
#[tokio::test]
|
|
468
|
+
async fn test_error_message_propagation() {
|
|
469
|
+
let mut server = GrpcTestServer::new();
|
|
470
|
+
|
|
471
|
+
let detailed_message = "Validation failed: Password must be at least 12 characters. \
|
|
472
|
+
Current length: 8. Special characters required.";
|
|
473
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
474
|
+
"test.DetailedErrorService",
|
|
475
|
+
Code::InvalidArgument,
|
|
476
|
+
detailed_message,
|
|
477
|
+
));
|
|
478
|
+
server.register_service(handler);
|
|
479
|
+
|
|
480
|
+
let result = send_unary_request(
|
|
481
|
+
&server,
|
|
482
|
+
"test.DetailedErrorService",
|
|
483
|
+
"Validate",
|
|
484
|
+
Bytes::from("{}"),
|
|
485
|
+
create_test_metadata(),
|
|
486
|
+
)
|
|
487
|
+
.await;
|
|
488
|
+
|
|
489
|
+
assert!(result.is_err());
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/// Test multiple error responses in sequence
|
|
493
|
+
#[tokio::test]
|
|
494
|
+
async fn test_multiple_error_responses() {
|
|
495
|
+
let mut server = GrpcTestServer::new();
|
|
496
|
+
|
|
497
|
+
// Register service that returns INVALID_ARGUMENT
|
|
498
|
+
let handler1 = Arc::new(ErrorMockHandler::new(
|
|
499
|
+
"test.Service1",
|
|
500
|
+
Code::InvalidArgument,
|
|
501
|
+
"Invalid input",
|
|
502
|
+
));
|
|
503
|
+
server.register_service(handler1);
|
|
504
|
+
|
|
505
|
+
// Register service that returns NOT_FOUND
|
|
506
|
+
let handler2 = Arc::new(ErrorMockHandler::new(
|
|
507
|
+
"test.Service2",
|
|
508
|
+
Code::NotFound,
|
|
509
|
+
"Resource not found",
|
|
510
|
+
));
|
|
511
|
+
server.register_service(handler2);
|
|
512
|
+
|
|
513
|
+
// Register service that returns PERMISSION_DENIED
|
|
514
|
+
let handler3 = Arc::new(ErrorMockHandler::new(
|
|
515
|
+
"test.Service3",
|
|
516
|
+
Code::PermissionDenied,
|
|
517
|
+
"Access denied",
|
|
518
|
+
));
|
|
519
|
+
server.register_service(handler3);
|
|
520
|
+
|
|
521
|
+
// Test first error
|
|
522
|
+
let result1 = send_unary_request(
|
|
523
|
+
&server,
|
|
524
|
+
"test.Service1",
|
|
525
|
+
"Method",
|
|
526
|
+
Bytes::from("{}"),
|
|
527
|
+
create_test_metadata(),
|
|
528
|
+
)
|
|
529
|
+
.await;
|
|
530
|
+
assert!(result1.is_err());
|
|
531
|
+
|
|
532
|
+
// Test second error
|
|
533
|
+
let result2 = send_unary_request(
|
|
534
|
+
&server,
|
|
535
|
+
"test.Service2",
|
|
536
|
+
"Method",
|
|
537
|
+
Bytes::from("{}"),
|
|
538
|
+
create_test_metadata(),
|
|
539
|
+
)
|
|
540
|
+
.await;
|
|
541
|
+
assert!(result2.is_err());
|
|
542
|
+
|
|
543
|
+
// Test third error
|
|
544
|
+
let result3 = send_unary_request(
|
|
545
|
+
&server,
|
|
546
|
+
"test.Service3",
|
|
547
|
+
"Method",
|
|
548
|
+
Bytes::from("{}"),
|
|
549
|
+
create_test_metadata(),
|
|
550
|
+
)
|
|
551
|
+
.await;
|
|
552
|
+
assert!(result3.is_err());
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/// Test error with empty message
|
|
556
|
+
#[tokio::test]
|
|
557
|
+
async fn test_error_with_empty_message() {
|
|
558
|
+
let mut server = GrpcTestServer::new();
|
|
559
|
+
|
|
560
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
561
|
+
"test.EmptyErrorService",
|
|
562
|
+
Code::Internal,
|
|
563
|
+
"",
|
|
564
|
+
));
|
|
565
|
+
server.register_service(handler);
|
|
566
|
+
|
|
567
|
+
let result = send_unary_request(
|
|
568
|
+
&server,
|
|
569
|
+
"test.EmptyErrorService",
|
|
570
|
+
"Method",
|
|
571
|
+
Bytes::from("{}"),
|
|
572
|
+
create_test_metadata(),
|
|
573
|
+
)
|
|
574
|
+
.await;
|
|
575
|
+
|
|
576
|
+
assert!(result.is_err());
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/// Test error with very long message
|
|
580
|
+
#[tokio::test]
|
|
581
|
+
async fn test_error_with_long_message() {
|
|
582
|
+
let mut server = GrpcTestServer::new();
|
|
583
|
+
|
|
584
|
+
let long_message = "Error occurred during processing: ".to_string()
|
|
585
|
+
+ &"detailed reason ".repeat(50)
|
|
586
|
+
+ "Please contact support.";
|
|
587
|
+
|
|
588
|
+
let handler = Arc::new(ErrorMockHandler::new(
|
|
589
|
+
"test.LongErrorService",
|
|
590
|
+
Code::Internal,
|
|
591
|
+
&long_message,
|
|
592
|
+
));
|
|
593
|
+
server.register_service(handler);
|
|
594
|
+
|
|
595
|
+
let result = send_unary_request(
|
|
596
|
+
&server,
|
|
597
|
+
"test.LongErrorService",
|
|
598
|
+
"Method",
|
|
599
|
+
Bytes::from("{}"),
|
|
600
|
+
create_test_metadata(),
|
|
601
|
+
)
|
|
602
|
+
.await;
|
|
603
|
+
|
|
604
|
+
assert!(result.is_err());
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/// Test handler conversion of custom errors to gRPC status
|
|
608
|
+
#[tokio::test]
|
|
609
|
+
async fn test_handler_error_to_status_conversion() {
|
|
610
|
+
let mut server = GrpcTestServer::new();
|
|
611
|
+
|
|
612
|
+
struct ValidationHandler;
|
|
613
|
+
impl spikard_http::grpc::GrpcHandler for ValidationHandler {
|
|
614
|
+
fn call(&self, request: spikard_http::grpc::GrpcRequestData)
|
|
615
|
+
-> std::pin::Pin<Box<dyn std::future::Future<Output = spikard_http::grpc::GrpcHandlerResult> + Send>>
|
|
616
|
+
{
|
|
617
|
+
// Simulate validation logic
|
|
618
|
+
if request.payload.is_empty() {
|
|
619
|
+
Box::pin(async {
|
|
620
|
+
Err(tonic::Status::new(
|
|
621
|
+
Code::InvalidArgument,
|
|
622
|
+
"Request body cannot be empty"
|
|
623
|
+
))
|
|
624
|
+
})
|
|
625
|
+
} else {
|
|
626
|
+
Box::pin(async {
|
|
627
|
+
Ok(spikard_http::grpc::GrpcResponseData {
|
|
628
|
+
payload: Bytes::from(r#"{"status": "valid"}"#),
|
|
629
|
+
metadata: tonic::metadata::MetadataMap::new(),
|
|
630
|
+
})
|
|
631
|
+
})
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
fn service_name(&self) -> &'static str {
|
|
635
|
+
"test.ValidationService"
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
server.register_service(Arc::new(ValidationHandler));
|
|
640
|
+
|
|
641
|
+
// Test with empty payload (should error)
|
|
642
|
+
let error_result = send_unary_request(
|
|
643
|
+
&server,
|
|
644
|
+
"test.ValidationService",
|
|
645
|
+
"Validate",
|
|
646
|
+
Bytes::new(),
|
|
647
|
+
create_test_metadata(),
|
|
648
|
+
)
|
|
649
|
+
.await;
|
|
650
|
+
|
|
651
|
+
assert!(error_result.is_err());
|
|
652
|
+
assert!(error_result.unwrap_err().to_string().contains("empty"));
|
|
653
|
+
}
|