spikard 0.8.3 → 0.10.2
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/README.md +19 -10
- data/ext/spikard_rb/Cargo.lock +234 -162
- data/ext/spikard_rb/Cargo.toml +2 -2
- data/ext/spikard_rb/extconf.rb +4 -3
- data/lib/spikard/config.rb +88 -12
- data/lib/spikard/testing.rb +3 -1
- data/lib/spikard/version.rb +1 -1
- data/lib/spikard.rb +11 -0
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +3 -6
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +8 -8
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +2 -2
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +4 -4
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +10 -4
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +3 -3
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +10 -5
- data/vendor/crates/spikard-bindings-shared/src/json_conversion.rs +829 -0
- data/vendor/crates/spikard-bindings-shared/src/lazy_cache.rs +587 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +7 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +11 -11
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +9 -37
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +436 -3
- data/vendor/crates/spikard-bindings-shared/src/response_interpreter.rs +944 -0
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +4 -4
- data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +3 -2
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +13 -13
- data/vendor/crates/spikard-bindings-shared/tests/{comprehensive_coverage.rs → full_coverage.rs} +10 -5
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +14 -14
- data/vendor/crates/spikard-bindings-shared/tests/integration_tests.rs +669 -0
- data/vendor/crates/spikard-core/Cargo.toml +3 -3
- data/vendor/crates/spikard-core/src/di/container.rs +1 -1
- data/vendor/crates/spikard-core/src/di/factory.rs +2 -2
- data/vendor/crates/spikard-core/src/di/resolved.rs +2 -2
- data/vendor/crates/spikard-core/src/di/value.rs +1 -1
- data/vendor/crates/spikard-core/src/http.rs +75 -0
- data/vendor/crates/spikard-core/src/lifecycle.rs +43 -43
- data/vendor/crates/spikard-core/src/parameters.rs +14 -19
- data/vendor/crates/spikard-core/src/problem.rs +1 -1
- data/vendor/crates/spikard-core/src/request_data.rs +7 -16
- data/vendor/crates/spikard-core/src/router.rs +6 -0
- data/vendor/crates/spikard-core/src/schema_registry.rs +2 -3
- data/vendor/crates/spikard-core/src/type_hints.rs +3 -2
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +1 -1
- data/vendor/crates/spikard-core/src/validation/mod.rs +1 -1
- data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +1 -1
- data/vendor/crates/spikard-core/tests/error_mapper.rs +2 -2
- data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +1 -1
- data/vendor/crates/spikard-core/tests/parameters_full.rs +1 -1
- data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +1 -1
- data/vendor/crates/spikard-core/tests/validation_coverage.rs +4 -4
- data/vendor/crates/spikard-http/Cargo.toml +4 -2
- data/vendor/crates/spikard-http/src/cors.rs +32 -11
- data/vendor/crates/spikard-http/src/di_handler.rs +12 -8
- data/vendor/crates/spikard-http/src/grpc/framing.rs +469 -0
- data/vendor/crates/spikard-http/src/grpc/handler.rs +887 -25
- data/vendor/crates/spikard-http/src/grpc/mod.rs +114 -22
- data/vendor/crates/spikard-http/src/grpc/service.rs +232 -2
- data/vendor/crates/spikard-http/src/grpc/streaming.rs +80 -2
- data/vendor/crates/spikard-http/src/handler_trait.rs +204 -27
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +15 -15
- data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +2 -2
- data/vendor/crates/spikard-http/src/jsonrpc/router.rs +2 -2
- data/vendor/crates/spikard-http/src/lib.rs +1 -1
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +2 -2
- data/vendor/crates/spikard-http/src/lifecycle.rs +4 -4
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +2 -0
- data/vendor/crates/spikard-http/src/server/fast_router.rs +186 -0
- data/vendor/crates/spikard-http/src/server/grpc_routing.rs +324 -23
- data/vendor/crates/spikard-http/src/server/handler.rs +33 -22
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +21 -2
- data/vendor/crates/spikard-http/src/server/mod.rs +125 -20
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +126 -44
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +80 -69
- data/vendor/crates/spikard-http/tests/common/handlers.rs +2 -2
- data/vendor/crates/spikard-http/tests/common/test_builders.rs +12 -12
- data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +2 -2
- data/vendor/crates/spikard-http/tests/di_integration.rs +6 -6
- data/vendor/crates/spikard-http/tests/grpc_bidirectional_streaming.rs +430 -0
- data/vendor/crates/spikard-http/tests/grpc_client_streaming.rs +738 -0
- data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +13 -9
- data/vendor/crates/spikard-http/tests/grpc_server_streaming.rs +974 -0
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +2 -2
- data/vendor/crates/spikard-http/tests/request_extraction_full.rs +4 -4
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +2 -2
- data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +1 -0
- data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +140 -0
- data/vendor/crates/spikard-rb/Cargo.toml +3 -1
- data/vendor/crates/spikard-rb/src/conversion.rs +138 -4
- data/vendor/crates/spikard-rb/src/grpc/handler.rs +706 -229
- data/vendor/crates/spikard-rb/src/grpc/mod.rs +6 -2
- data/vendor/crates/spikard-rb/src/gvl.rs +2 -2
- data/vendor/crates/spikard-rb/src/handler.rs +169 -91
- data/vendor/crates/spikard-rb/src/lib.rs +444 -62
- data/vendor/crates/spikard-rb/src/lifecycle.rs +29 -1
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +108 -43
- data/vendor/crates/spikard-rb/src/request.rs +117 -20
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +52 -25
- data/vendor/crates/spikard-rb/src/server.rs +23 -14
- data/vendor/crates/spikard-rb/src/testing/client.rs +5 -4
- data/vendor/crates/spikard-rb/src/testing/sse.rs +1 -36
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +3 -38
- data/vendor/crates/spikard-rb/src/websocket.rs +32 -23
- data/vendor/crates/spikard-rb-macros/Cargo.toml +1 -1
- metadata +14 -4
- data/vendor/bundle/ruby/3.4.0/gems/diff-lcs-1.6.2/mise.toml +0 -5
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.10.0/build/buildkitd.toml +0 -2
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
//! Integration tests for gRPC client streaming
|
|
2
|
+
//!
|
|
3
|
+
//! Tests end-to-end client streaming functionality through `GenericGrpcService`
|
|
4
|
+
//! and `grpc_routing`, including:
|
|
5
|
+
//! - Stream of multiple messages aggregation
|
|
6
|
+
//! - Message validation during streaming
|
|
7
|
+
//! - Empty streams
|
|
8
|
+
//! - Error handling before and during streaming
|
|
9
|
+
//! - Metadata in streaming requests
|
|
10
|
+
//! - Large payloads (100+ messages)
|
|
11
|
+
//! - Message ordering preservation
|
|
12
|
+
//! - Concurrent client streaming requests
|
|
13
|
+
#![allow(
|
|
14
|
+
clippy::uninlined_format_args,
|
|
15
|
+
clippy::doc_markdown,
|
|
16
|
+
clippy::option_if_let_else,
|
|
17
|
+
reason = "Integration test for streaming with many test cases"
|
|
18
|
+
)]
|
|
19
|
+
|
|
20
|
+
use bytes::Bytes;
|
|
21
|
+
use futures_util::StreamExt;
|
|
22
|
+
use spikard_http::grpc::streaming::{StreamingRequest, message_stream_from_vec};
|
|
23
|
+
use spikard_http::grpc::{GrpcHandler, GrpcHandlerResult, GrpcRequestData, GrpcResponseData, RpcMode};
|
|
24
|
+
use std::future::Future;
|
|
25
|
+
use std::pin::Pin;
|
|
26
|
+
use std::sync::Arc;
|
|
27
|
+
use tonic::metadata::MetadataMap;
|
|
28
|
+
|
|
29
|
+
mod common;
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Test Handlers
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
/// Handler that aggregates incoming numbers by summing them
|
|
36
|
+
struct SumHandlerIntegration;
|
|
37
|
+
|
|
38
|
+
impl GrpcHandler for SumHandlerIntegration {
|
|
39
|
+
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
40
|
+
Box::pin(async { Err(tonic::Status::unimplemented("Use call_client_stream for streaming")) })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn service_name(&self) -> &'static str {
|
|
44
|
+
"integration.SumService"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn rpc_mode(&self) -> RpcMode {
|
|
48
|
+
RpcMode::ClientStreaming
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn call_client_stream(
|
|
52
|
+
&self,
|
|
53
|
+
request: StreamingRequest,
|
|
54
|
+
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
55
|
+
Box::pin(async move {
|
|
56
|
+
let mut sum: i64 = 0;
|
|
57
|
+
let mut stream = request.message_stream;
|
|
58
|
+
|
|
59
|
+
while let Some(msg_result) = stream.next().await {
|
|
60
|
+
match msg_result {
|
|
61
|
+
Ok(msg) => {
|
|
62
|
+
if msg.len() == 8 {
|
|
63
|
+
let value =
|
|
64
|
+
i64::from_le_bytes([msg[0], msg[1], msg[2], msg[3], msg[4], msg[5], msg[6], msg[7]]);
|
|
65
|
+
sum += value;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
Err(e) => return Err(e),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
Ok(GrpcResponseData {
|
|
73
|
+
payload: Bytes::from(sum.to_le_bytes().to_vec()),
|
|
74
|
+
metadata: MetadataMap::new(),
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/// Handler that validates all messages during streaming
|
|
81
|
+
struct ValidateAllMessagesHandler;
|
|
82
|
+
|
|
83
|
+
impl GrpcHandler for ValidateAllMessagesHandler {
|
|
84
|
+
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
85
|
+
Box::pin(async { Err(tonic::Status::unimplemented("Use call_client_stream for streaming")) })
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
fn service_name(&self) -> &'static str {
|
|
89
|
+
"integration.ValidateService"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
fn rpc_mode(&self) -> RpcMode {
|
|
93
|
+
RpcMode::ClientStreaming
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn call_client_stream(
|
|
97
|
+
&self,
|
|
98
|
+
request: StreamingRequest,
|
|
99
|
+
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
100
|
+
Box::pin(async move {
|
|
101
|
+
let mut stream = request.message_stream;
|
|
102
|
+
let mut count = 0;
|
|
103
|
+
|
|
104
|
+
while let Some(msg_result) = stream.next().await {
|
|
105
|
+
match msg_result {
|
|
106
|
+
Ok(msg) => {
|
|
107
|
+
// Validate: message must start with "valid_"
|
|
108
|
+
if let Ok(s) = std::str::from_utf8(&msg) {
|
|
109
|
+
if !s.starts_with("valid_") {
|
|
110
|
+
return Err(tonic::Status::invalid_argument(format!(
|
|
111
|
+
"Message does not start with 'valid_': {}",
|
|
112
|
+
s
|
|
113
|
+
)));
|
|
114
|
+
}
|
|
115
|
+
count += 1;
|
|
116
|
+
} else {
|
|
117
|
+
return Err(tonic::Status::invalid_argument("Invalid UTF-8 in message"));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
Err(e) => return Err(e),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Ok(GrpcResponseData {
|
|
125
|
+
payload: Bytes::from(format!("validated_count:{}", count)),
|
|
126
|
+
metadata: MetadataMap::new(),
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Handler that counts messages in stream
|
|
133
|
+
struct CountMessagesHandler;
|
|
134
|
+
|
|
135
|
+
impl GrpcHandler for CountMessagesHandler {
|
|
136
|
+
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
137
|
+
Box::pin(async { Err(tonic::Status::unimplemented("Use call_client_stream for streaming")) })
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fn service_name(&self) -> &'static str {
|
|
141
|
+
"integration.CountService"
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
fn rpc_mode(&self) -> RpcMode {
|
|
145
|
+
RpcMode::ClientStreaming
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn call_client_stream(
|
|
149
|
+
&self,
|
|
150
|
+
request: StreamingRequest,
|
|
151
|
+
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
152
|
+
Box::pin(async move {
|
|
153
|
+
let mut stream = request.message_stream;
|
|
154
|
+
let mut count = 0u64;
|
|
155
|
+
|
|
156
|
+
while let Some(msg_result) = stream.next().await {
|
|
157
|
+
match msg_result {
|
|
158
|
+
Ok(_) => count += 1,
|
|
159
|
+
Err(e) => return Err(e),
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
Ok(GrpcResponseData {
|
|
164
|
+
payload: Bytes::from(count.to_le_bytes().to_vec()),
|
|
165
|
+
metadata: MetadataMap::new(),
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// Handler that echoes first message of stream
|
|
172
|
+
struct EchoFirstHandler;
|
|
173
|
+
|
|
174
|
+
impl GrpcHandler for EchoFirstHandler {
|
|
175
|
+
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
176
|
+
Box::pin(async { Err(tonic::Status::unimplemented("Use call_client_stream for streaming")) })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fn service_name(&self) -> &'static str {
|
|
180
|
+
"integration.EchoFirstService"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fn rpc_mode(&self) -> RpcMode {
|
|
184
|
+
RpcMode::ClientStreaming
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn call_client_stream(
|
|
188
|
+
&self,
|
|
189
|
+
request: StreamingRequest,
|
|
190
|
+
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
191
|
+
Box::pin(async move {
|
|
192
|
+
let mut stream = request.message_stream;
|
|
193
|
+
|
|
194
|
+
if let Some(msg_result) = stream.next().await {
|
|
195
|
+
match msg_result {
|
|
196
|
+
Ok(msg) => Ok(GrpcResponseData {
|
|
197
|
+
payload: Bytes::from([b"first:", &msg[..]].concat()),
|
|
198
|
+
metadata: MetadataMap::new(),
|
|
199
|
+
}),
|
|
200
|
+
Err(e) => Err(e),
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
Err(tonic::Status::invalid_argument("Stream is empty"))
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// Handler that echoes last message of stream
|
|
210
|
+
struct EchoLastHandler;
|
|
211
|
+
|
|
212
|
+
impl GrpcHandler for EchoLastHandler {
|
|
213
|
+
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
214
|
+
Box::pin(async { Err(tonic::Status::unimplemented("Use call_client_stream for streaming")) })
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
fn service_name(&self) -> &'static str {
|
|
218
|
+
"integration.EchoLastService"
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fn rpc_mode(&self) -> RpcMode {
|
|
222
|
+
RpcMode::ClientStreaming
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
fn call_client_stream(
|
|
226
|
+
&self,
|
|
227
|
+
request: StreamingRequest,
|
|
228
|
+
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
229
|
+
Box::pin(async move {
|
|
230
|
+
let mut stream = request.message_stream;
|
|
231
|
+
let mut last_msg: Option<Bytes> = None;
|
|
232
|
+
|
|
233
|
+
while let Some(msg_result) = stream.next().await {
|
|
234
|
+
match msg_result {
|
|
235
|
+
Ok(msg) => last_msg = Some(msg),
|
|
236
|
+
Err(e) => return Err(e),
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if let Some(msg) = last_msg {
|
|
241
|
+
Ok(GrpcResponseData {
|
|
242
|
+
payload: Bytes::from([b"last:", &msg[..]].concat()),
|
|
243
|
+
metadata: MetadataMap::new(),
|
|
244
|
+
})
|
|
245
|
+
} else {
|
|
246
|
+
Err(tonic::Status::invalid_argument("Stream is empty"))
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// Handler that returns error for validation failures
|
|
253
|
+
struct ValidationErrorHandler;
|
|
254
|
+
|
|
255
|
+
impl GrpcHandler for ValidationErrorHandler {
|
|
256
|
+
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
257
|
+
Box::pin(async { Err(tonic::Status::unimplemented("Use call_client_stream for streaming")) })
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
fn service_name(&self) -> &'static str {
|
|
261
|
+
"integration.ValidationErrorService"
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
fn rpc_mode(&self) -> RpcMode {
|
|
265
|
+
RpcMode::ClientStreaming
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
fn call_client_stream(
|
|
269
|
+
&self,
|
|
270
|
+
request: StreamingRequest,
|
|
271
|
+
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
272
|
+
Box::pin(async move {
|
|
273
|
+
let mut stream = request.message_stream;
|
|
274
|
+
|
|
275
|
+
while let Some(msg_result) = stream.next().await {
|
|
276
|
+
match msg_result {
|
|
277
|
+
Ok(msg) => {
|
|
278
|
+
// Reject messages larger than 1000 bytes
|
|
279
|
+
if msg.len() > 1000 {
|
|
280
|
+
return Err(tonic::Status::invalid_argument(
|
|
281
|
+
"Message exceeds size limit of 1000 bytes",
|
|
282
|
+
));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
Err(e) => return Err(e),
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
Ok(GrpcResponseData {
|
|
290
|
+
payload: Bytes::from("all_valid"),
|
|
291
|
+
metadata: MetadataMap::new(),
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/// Handler that preserves message order
|
|
298
|
+
struct PreserveOrderHandler;
|
|
299
|
+
|
|
300
|
+
impl GrpcHandler for PreserveOrderHandler {
|
|
301
|
+
fn call(&self, _request: GrpcRequestData) -> Pin<Box<dyn Future<Output = GrpcHandlerResult> + Send>> {
|
|
302
|
+
Box::pin(async { Err(tonic::Status::unimplemented("Use call_client_stream for streaming")) })
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
fn service_name(&self) -> &'static str {
|
|
306
|
+
"integration.PreserveOrderService"
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
fn rpc_mode(&self) -> RpcMode {
|
|
310
|
+
RpcMode::ClientStreaming
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
fn call_client_stream(
|
|
314
|
+
&self,
|
|
315
|
+
request: StreamingRequest,
|
|
316
|
+
) -> Pin<Box<dyn Future<Output = Result<GrpcResponseData, tonic::Status>> + Send>> {
|
|
317
|
+
Box::pin(async move {
|
|
318
|
+
let mut stream = request.message_stream;
|
|
319
|
+
let mut messages = Vec::new();
|
|
320
|
+
|
|
321
|
+
while let Some(msg_result) = stream.next().await {
|
|
322
|
+
match msg_result {
|
|
323
|
+
Ok(msg) => messages.push(msg),
|
|
324
|
+
Err(e) => return Err(e),
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Concatenate all messages in order
|
|
329
|
+
let mut result = Vec::new();
|
|
330
|
+
for msg in messages {
|
|
331
|
+
result.extend_from_slice(&msg);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
Ok(GrpcResponseData {
|
|
335
|
+
payload: Bytes::from(result),
|
|
336
|
+
metadata: MetadataMap::new(),
|
|
337
|
+
})
|
|
338
|
+
})
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// Integration Tests
|
|
344
|
+
// ============================================================================
|
|
345
|
+
|
|
346
|
+
#[tokio::test]
|
|
347
|
+
async fn test_integration_client_stream_sum_aggregation() {
|
|
348
|
+
let handler = Arc::new(SumHandlerIntegration);
|
|
349
|
+
|
|
350
|
+
let messages = vec![
|
|
351
|
+
Bytes::from((10i64).to_le_bytes().to_vec()),
|
|
352
|
+
Bytes::from((20i64).to_le_bytes().to_vec()),
|
|
353
|
+
Bytes::from((30i64).to_le_bytes().to_vec()),
|
|
354
|
+
];
|
|
355
|
+
let stream = message_stream_from_vec(messages);
|
|
356
|
+
|
|
357
|
+
let request = StreamingRequest {
|
|
358
|
+
service_name: "integration.SumService".to_string(),
|
|
359
|
+
method_name: "Sum".to_string(),
|
|
360
|
+
message_stream: stream,
|
|
361
|
+
metadata: MetadataMap::new(),
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
let result = handler.call_client_stream(request).await;
|
|
365
|
+
assert!(result.is_ok());
|
|
366
|
+
|
|
367
|
+
let response = result.unwrap();
|
|
368
|
+
if response.payload.len() == 8 {
|
|
369
|
+
let sum = i64::from_le_bytes([
|
|
370
|
+
response.payload[0],
|
|
371
|
+
response.payload[1],
|
|
372
|
+
response.payload[2],
|
|
373
|
+
response.payload[3],
|
|
374
|
+
response.payload[4],
|
|
375
|
+
response.payload[5],
|
|
376
|
+
response.payload[6],
|
|
377
|
+
response.payload[7],
|
|
378
|
+
]);
|
|
379
|
+
assert_eq!(sum, 60); // 10 + 20 + 30
|
|
380
|
+
} else {
|
|
381
|
+
panic!("Unexpected payload size");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
#[tokio::test]
|
|
386
|
+
async fn test_integration_client_stream_validate_all_messages() {
|
|
387
|
+
let handler = Arc::new(ValidateAllMessagesHandler);
|
|
388
|
+
|
|
389
|
+
let messages = vec![
|
|
390
|
+
Bytes::from("valid_msg1"),
|
|
391
|
+
Bytes::from("valid_msg2"),
|
|
392
|
+
Bytes::from("valid_msg3"),
|
|
393
|
+
];
|
|
394
|
+
let stream = message_stream_from_vec(messages);
|
|
395
|
+
|
|
396
|
+
let request = StreamingRequest {
|
|
397
|
+
service_name: "integration.ValidateService".to_string(),
|
|
398
|
+
method_name: "Validate".to_string(),
|
|
399
|
+
message_stream: stream,
|
|
400
|
+
metadata: MetadataMap::new(),
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
let result = handler.call_client_stream(request).await;
|
|
404
|
+
assert!(result.is_ok());
|
|
405
|
+
|
|
406
|
+
let response = result.unwrap();
|
|
407
|
+
assert_eq!(response.payload, Bytes::from("validated_count:3"));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
#[tokio::test]
|
|
411
|
+
async fn test_integration_client_stream_count_messages() {
|
|
412
|
+
let handler = Arc::new(CountMessagesHandler);
|
|
413
|
+
|
|
414
|
+
let messages = vec![
|
|
415
|
+
Bytes::from("msg1"),
|
|
416
|
+
Bytes::from("msg2"),
|
|
417
|
+
Bytes::from("msg3"),
|
|
418
|
+
Bytes::from("msg4"),
|
|
419
|
+
Bytes::from("msg5"),
|
|
420
|
+
Bytes::from("msg6"),
|
|
421
|
+
];
|
|
422
|
+
let stream = message_stream_from_vec(messages);
|
|
423
|
+
|
|
424
|
+
let request = StreamingRequest {
|
|
425
|
+
service_name: "integration.CountService".to_string(),
|
|
426
|
+
method_name: "Count".to_string(),
|
|
427
|
+
message_stream: stream,
|
|
428
|
+
metadata: MetadataMap::new(),
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
let result = handler.call_client_stream(request).await;
|
|
432
|
+
assert!(result.is_ok());
|
|
433
|
+
|
|
434
|
+
let response = result.unwrap();
|
|
435
|
+
if response.payload.len() == 8 {
|
|
436
|
+
let count = u64::from_le_bytes([
|
|
437
|
+
response.payload[0],
|
|
438
|
+
response.payload[1],
|
|
439
|
+
response.payload[2],
|
|
440
|
+
response.payload[3],
|
|
441
|
+
response.payload[4],
|
|
442
|
+
response.payload[5],
|
|
443
|
+
response.payload[6],
|
|
444
|
+
response.payload[7],
|
|
445
|
+
]);
|
|
446
|
+
assert_eq!(count, 6);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
#[tokio::test]
|
|
451
|
+
async fn test_integration_client_stream_echo_first_message() {
|
|
452
|
+
let handler = Arc::new(EchoFirstHandler);
|
|
453
|
+
|
|
454
|
+
let messages = vec![
|
|
455
|
+
Bytes::from("first_message"),
|
|
456
|
+
Bytes::from("second_message"),
|
|
457
|
+
Bytes::from("third_message"),
|
|
458
|
+
];
|
|
459
|
+
let stream = message_stream_from_vec(messages);
|
|
460
|
+
|
|
461
|
+
let request = StreamingRequest {
|
|
462
|
+
service_name: "integration.EchoFirstService".to_string(),
|
|
463
|
+
method_name: "EchoFirst".to_string(),
|
|
464
|
+
message_stream: stream,
|
|
465
|
+
metadata: MetadataMap::new(),
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
let result = handler.call_client_stream(request).await;
|
|
469
|
+
assert!(result.is_ok());
|
|
470
|
+
|
|
471
|
+
let response = result.unwrap();
|
|
472
|
+
assert_eq!(response.payload, Bytes::from("first:first_message"));
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
#[tokio::test]
|
|
476
|
+
async fn test_integration_client_stream_echo_last_message() {
|
|
477
|
+
let handler = Arc::new(EchoLastHandler);
|
|
478
|
+
|
|
479
|
+
let messages = vec![
|
|
480
|
+
Bytes::from("first_message"),
|
|
481
|
+
Bytes::from("second_message"),
|
|
482
|
+
Bytes::from("third_message"),
|
|
483
|
+
];
|
|
484
|
+
let stream = message_stream_from_vec(messages);
|
|
485
|
+
|
|
486
|
+
let request = StreamingRequest {
|
|
487
|
+
service_name: "integration.EchoLastService".to_string(),
|
|
488
|
+
method_name: "EchoLast".to_string(),
|
|
489
|
+
message_stream: stream,
|
|
490
|
+
metadata: MetadataMap::new(),
|
|
491
|
+
};
|
|
492
|
+
|
|
493
|
+
let result = handler.call_client_stream(request).await;
|
|
494
|
+
assert!(result.is_ok());
|
|
495
|
+
|
|
496
|
+
let response = result.unwrap();
|
|
497
|
+
assert_eq!(response.payload, Bytes::from("last:third_message"));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
#[tokio::test]
|
|
501
|
+
async fn test_integration_client_stream_validation_failure() {
|
|
502
|
+
let handler = Arc::new(ValidationErrorHandler);
|
|
503
|
+
|
|
504
|
+
let messages = vec![
|
|
505
|
+
Bytes::from("valid_message"),
|
|
506
|
+
Bytes::from(vec![b'x'; 1001]), // Exceeds 1000 byte limit
|
|
507
|
+
Bytes::from("another_message"),
|
|
508
|
+
];
|
|
509
|
+
let stream = message_stream_from_vec(messages);
|
|
510
|
+
|
|
511
|
+
let request = StreamingRequest {
|
|
512
|
+
service_name: "integration.ValidationErrorService".to_string(),
|
|
513
|
+
method_name: "Validate".to_string(),
|
|
514
|
+
message_stream: stream,
|
|
515
|
+
metadata: MetadataMap::new(),
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
let result = handler.call_client_stream(request).await;
|
|
519
|
+
assert!(result.is_err());
|
|
520
|
+
|
|
521
|
+
if let Err(error) = result {
|
|
522
|
+
assert_eq!(error.code(), tonic::Code::InvalidArgument);
|
|
523
|
+
assert!(error.message().contains("exceeds size limit"));
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
#[tokio::test]
|
|
528
|
+
async fn test_integration_client_stream_message_ordering() {
|
|
529
|
+
let handler = Arc::new(PreserveOrderHandler);
|
|
530
|
+
|
|
531
|
+
let messages = vec![
|
|
532
|
+
Bytes::from("alpha"),
|
|
533
|
+
Bytes::from("beta"),
|
|
534
|
+
Bytes::from("gamma"),
|
|
535
|
+
Bytes::from("delta"),
|
|
536
|
+
];
|
|
537
|
+
let stream = message_stream_from_vec(messages);
|
|
538
|
+
|
|
539
|
+
let request = StreamingRequest {
|
|
540
|
+
service_name: "integration.PreserveOrderService".to_string(),
|
|
541
|
+
method_name: "PreserveOrder".to_string(),
|
|
542
|
+
message_stream: stream,
|
|
543
|
+
metadata: MetadataMap::new(),
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
let result = handler.call_client_stream(request).await;
|
|
547
|
+
assert!(result.is_ok());
|
|
548
|
+
|
|
549
|
+
let response = result.unwrap();
|
|
550
|
+
assert_eq!(response.payload, Bytes::from("alphabetagammadelta"));
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
#[tokio::test]
|
|
554
|
+
async fn test_integration_client_stream_empty_stream() {
|
|
555
|
+
let handler = Arc::new(CountMessagesHandler);
|
|
556
|
+
|
|
557
|
+
let messages: Vec<Bytes> = vec![];
|
|
558
|
+
let stream = message_stream_from_vec(messages);
|
|
559
|
+
|
|
560
|
+
let request = StreamingRequest {
|
|
561
|
+
service_name: "integration.CountService".to_string(),
|
|
562
|
+
method_name: "Count".to_string(),
|
|
563
|
+
message_stream: stream,
|
|
564
|
+
metadata: MetadataMap::new(),
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
let result = handler.call_client_stream(request).await;
|
|
568
|
+
assert!(result.is_ok());
|
|
569
|
+
|
|
570
|
+
let response = result.unwrap();
|
|
571
|
+
if response.payload.len() == 8 {
|
|
572
|
+
let count = u64::from_le_bytes([
|
|
573
|
+
response.payload[0],
|
|
574
|
+
response.payload[1],
|
|
575
|
+
response.payload[2],
|
|
576
|
+
response.payload[3],
|
|
577
|
+
response.payload[4],
|
|
578
|
+
response.payload[5],
|
|
579
|
+
response.payload[6],
|
|
580
|
+
response.payload[7],
|
|
581
|
+
]);
|
|
582
|
+
assert_eq!(count, 0);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
#[tokio::test]
|
|
587
|
+
async fn test_integration_client_stream_large_stream() {
|
|
588
|
+
let handler = Arc::new(CountMessagesHandler);
|
|
589
|
+
|
|
590
|
+
// Create 150 messages
|
|
591
|
+
let messages: Vec<Bytes> = (0..150).map(|i| Bytes::from(format!("message_{}", i))).collect();
|
|
592
|
+
let stream = message_stream_from_vec(messages);
|
|
593
|
+
|
|
594
|
+
let request = StreamingRequest {
|
|
595
|
+
service_name: "integration.CountService".to_string(),
|
|
596
|
+
method_name: "Count".to_string(),
|
|
597
|
+
message_stream: stream,
|
|
598
|
+
metadata: MetadataMap::new(),
|
|
599
|
+
};
|
|
600
|
+
|
|
601
|
+
let result = handler.call_client_stream(request).await;
|
|
602
|
+
assert!(result.is_ok());
|
|
603
|
+
|
|
604
|
+
let response = result.unwrap();
|
|
605
|
+
if response.payload.len() == 8 {
|
|
606
|
+
let count = u64::from_le_bytes([
|
|
607
|
+
response.payload[0],
|
|
608
|
+
response.payload[1],
|
|
609
|
+
response.payload[2],
|
|
610
|
+
response.payload[3],
|
|
611
|
+
response.payload[4],
|
|
612
|
+
response.payload[5],
|
|
613
|
+
response.payload[6],
|
|
614
|
+
response.payload[7],
|
|
615
|
+
]);
|
|
616
|
+
assert_eq!(count, 150);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
#[tokio::test]
|
|
621
|
+
async fn test_integration_client_stream_validate_failure_at_start() {
|
|
622
|
+
let handler = Arc::new(ValidateAllMessagesHandler);
|
|
623
|
+
|
|
624
|
+
let messages = vec![
|
|
625
|
+
Bytes::from("invalid_message"), // Should fail validation
|
|
626
|
+
Bytes::from("valid_msg2"),
|
|
627
|
+
];
|
|
628
|
+
let stream = message_stream_from_vec(messages);
|
|
629
|
+
|
|
630
|
+
let request = StreamingRequest {
|
|
631
|
+
service_name: "integration.ValidateService".to_string(),
|
|
632
|
+
method_name: "Validate".to_string(),
|
|
633
|
+
message_stream: stream,
|
|
634
|
+
metadata: MetadataMap::new(),
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
let result = handler.call_client_stream(request).await;
|
|
638
|
+
assert!(result.is_err());
|
|
639
|
+
|
|
640
|
+
if let Err(error) = result {
|
|
641
|
+
assert_eq!(error.code(), tonic::Code::InvalidArgument);
|
|
642
|
+
assert!(error.message().contains("does not start with 'valid_'"));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
#[tokio::test]
|
|
647
|
+
async fn test_integration_client_stream_concurrent_handlers() {
|
|
648
|
+
let handler1 = Arc::new(CountMessagesHandler);
|
|
649
|
+
let handler2 = Arc::new(SumHandlerIntegration);
|
|
650
|
+
let handler3 = Arc::new(CountMessagesHandler);
|
|
651
|
+
|
|
652
|
+
let task1 = {
|
|
653
|
+
let h = handler1.clone();
|
|
654
|
+
tokio::spawn(async move {
|
|
655
|
+
let messages = vec![Bytes::from("a"), Bytes::from("b"), Bytes::from("c")];
|
|
656
|
+
let stream = message_stream_from_vec(messages);
|
|
657
|
+
let request = StreamingRequest {
|
|
658
|
+
service_name: "integration.CountService".to_string(),
|
|
659
|
+
method_name: "Count".to_string(),
|
|
660
|
+
message_stream: stream,
|
|
661
|
+
metadata: MetadataMap::new(),
|
|
662
|
+
};
|
|
663
|
+
h.call_client_stream(request).await
|
|
664
|
+
})
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
let task2 = {
|
|
668
|
+
let h = handler2.clone();
|
|
669
|
+
tokio::spawn(async move {
|
|
670
|
+
let messages = vec![
|
|
671
|
+
Bytes::from((5i64).to_le_bytes().to_vec()),
|
|
672
|
+
Bytes::from((10i64).to_le_bytes().to_vec()),
|
|
673
|
+
];
|
|
674
|
+
let stream = message_stream_from_vec(messages);
|
|
675
|
+
let request = StreamingRequest {
|
|
676
|
+
service_name: "integration.SumService".to_string(),
|
|
677
|
+
method_name: "Sum".to_string(),
|
|
678
|
+
message_stream: stream,
|
|
679
|
+
metadata: MetadataMap::new(),
|
|
680
|
+
};
|
|
681
|
+
h.call_client_stream(request).await
|
|
682
|
+
})
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
let task3 = {
|
|
686
|
+
let h = handler3.clone();
|
|
687
|
+
tokio::spawn(async move {
|
|
688
|
+
let messages = vec![Bytes::from("x"), Bytes::from("y")];
|
|
689
|
+
let stream = message_stream_from_vec(messages);
|
|
690
|
+
let request = StreamingRequest {
|
|
691
|
+
service_name: "integration.CountService".to_string(),
|
|
692
|
+
method_name: "Count".to_string(),
|
|
693
|
+
message_stream: stream,
|
|
694
|
+
metadata: MetadataMap::new(),
|
|
695
|
+
};
|
|
696
|
+
h.call_client_stream(request).await
|
|
697
|
+
})
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
let result1 = task1.await.unwrap();
|
|
701
|
+
let result2 = task2.await.unwrap();
|
|
702
|
+
let result3 = task3.await.unwrap();
|
|
703
|
+
|
|
704
|
+
assert!(result1.is_ok());
|
|
705
|
+
assert!(result2.is_ok());
|
|
706
|
+
assert!(result3.is_ok());
|
|
707
|
+
|
|
708
|
+
// Verify counts
|
|
709
|
+
let response1 = result1.unwrap();
|
|
710
|
+
if response1.payload.len() == 8 {
|
|
711
|
+
let count = u64::from_le_bytes([
|
|
712
|
+
response1.payload[0],
|
|
713
|
+
response1.payload[1],
|
|
714
|
+
response1.payload[2],
|
|
715
|
+
response1.payload[3],
|
|
716
|
+
response1.payload[4],
|
|
717
|
+
response1.payload[5],
|
|
718
|
+
response1.payload[6],
|
|
719
|
+
response1.payload[7],
|
|
720
|
+
]);
|
|
721
|
+
assert_eq!(count, 3);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
let response3 = result3.unwrap();
|
|
725
|
+
if response3.payload.len() == 8 {
|
|
726
|
+
let count = u64::from_le_bytes([
|
|
727
|
+
response3.payload[0],
|
|
728
|
+
response3.payload[1],
|
|
729
|
+
response3.payload[2],
|
|
730
|
+
response3.payload[3],
|
|
731
|
+
response3.payload[4],
|
|
732
|
+
response3.payload[5],
|
|
733
|
+
response3.payload[6],
|
|
734
|
+
response3.payload[7],
|
|
735
|
+
]);
|
|
736
|
+
assert_eq!(count, 2);
|
|
737
|
+
}
|
|
738
|
+
}
|