connectrpc-testing 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +136 -0
- data/lib/connectrpc/testing/transport.rb +88 -0
- data/lib/connectrpc/testing/version.rb +7 -0
- data/lib/connectrpc/testing.rb +6 -0
- data/sig/connectrpc-testing.rbs +9 -0
- data/sig/manifest.yaml +2 -0
- metadata +63 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: ace3966903eb60c52dd074d1204311c7903f41c4a6d726ea3831e844962631cd
|
|
4
|
+
data.tar.gz: d4e8e92888ba3206f2801bd27a7e1e99a04644cc78fc116167cb899d018ddf1a
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 53a9fbfabd5692eeea6c8b6ca162f29a1e75a72e58bb5ea849d13839f963c8542aca9f5173a3589674a13867cb8aedbba11cef4ff2332bb6003abdc95ef262bf
|
|
7
|
+
data.tar.gz: 86e731569e68980795a8f608e15b799878bc50a440bbbeb5b24aa87338db0cb121986038adafbf21d1087272d4350d3cfe61b49cfebbdf9aecadcae055d93b47
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jim Myhrberg
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# connectrpc-testing
|
|
2
|
+
|
|
3
|
+
A synchronous in-process transport for real Connect unary round trips. The
|
|
4
|
+
application supplies a core `Server`, its binding, services and handlers. The
|
|
5
|
+
ordinary core or generated client still encodes requests and decodes responses,
|
|
6
|
+
including protocol errors, metadata, safe unary GET and compression. No sockets are opened.
|
|
7
|
+
|
|
8
|
+
Add `gem "connectrpc-testing", path: "gems/connectrpc-testing"` to a development
|
|
9
|
+
or test bundle in this repository. For another local application, point `path:`
|
|
10
|
+
at this package and provide the core gem through its own local path. Released
|
|
11
|
+
versions are published to RubyGems.org with the whole
|
|
12
|
+
[release train](../../docs/releasing.md). The runtime dependency is only `connectrpc ~> 0.1.0`; Google protobuf
|
|
13
|
+
and a test framework are application choices.
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
require "connectrpc/testing"
|
|
17
|
+
require "connectrpc/bindings/google_protobuf" # application's optional binding
|
|
18
|
+
require "echo_connect" # application's generated messages and service
|
|
19
|
+
|
|
20
|
+
service = Example::V1::EchoService
|
|
21
|
+
binding = ConnectRPC::Bindings::GoogleProtobuf.new
|
|
22
|
+
server = ConnectRPC::Server.new(
|
|
23
|
+
services: [service::SERVICE], binding: binding,
|
|
24
|
+
handlers: service.handlers(
|
|
25
|
+
echo: ->(request, context) {
|
|
26
|
+
context.response_trailers.add("x-tested", "yes")
|
|
27
|
+
request
|
|
28
|
+
},
|
|
29
|
+
fail: ->(_request, _context) {
|
|
30
|
+
raise ConnectRPC::Error.new(:invalid_argument, "safe message")
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
transport = ConnectRPC::Testing::Transport.new(server: server)
|
|
35
|
+
core = ConnectRPC::Client.new(
|
|
36
|
+
base_url: "https://in-process.invalid", binding: binding, transport: transport
|
|
37
|
+
)
|
|
38
|
+
client = service::Client.new(client: core)
|
|
39
|
+
response = client.echo(Example::V1::EchoMessage.new(name: "hello"), timeout: nil)
|
|
40
|
+
raise "Unexpected response" unless response.message.name == "hello"
|
|
41
|
+
raise "Missing trailer" unless response.trailers["x-tested"] == ["yes"]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The hostname is validated but never resolved. URLs must be absolute HTTP(S) URIs
|
|
45
|
+
without credentials or fragments. Raw URI queries are forwarded to core, including safe unary GET. The literal URI path
|
|
46
|
+
identifies the method: a base URL prefix is retained and will normally produce
|
|
47
|
+
core's unknown-method response. There is no mount-prefix stripping or Rack
|
|
48
|
+
fallthrough. The actual HTTP method and repeated leading header arrays pass to
|
|
49
|
+
core unchanged. Malformed protocol header values retain core classification;
|
|
50
|
+
structurally unsupported Ruby inputs raise `ArgumentError` before dispatch.
|
|
51
|
+
Frozen caller URI, headers and body are supported and preserved.
|
|
52
|
+
|
|
53
|
+
The private byte reader uses `byteslice` over the original body under core's
|
|
54
|
+
bounded `MessageIO` reads, including multibyte JSON. It does not decode or
|
|
55
|
+
recompress payloads. `TransportRequest.response_limit` caps encoded bytes,
|
|
56
|
+
including error bodies and gzip framing. Core's server materializes a response
|
|
57
|
+
before the transport checks its cap. That check cannot undo the allocation and
|
|
58
|
+
does not establish a total memory bound. Server and Client message and wire
|
|
59
|
+
limits remain separate.
|
|
60
|
+
|
|
61
|
+
A supplied original `Deadline` is checked before and after dispatch. Immediately
|
|
62
|
+
before dispatch, the transport copies headers and refreshes `connect-timeout-ms`
|
|
63
|
+
from the remaining budget. Core reconstructs its cooperative server deadline
|
|
64
|
+
from rounded/clamped wire milliseconds. The original deadline remains
|
|
65
|
+
authoritative and expiry takes precedence over the response wire cap. The
|
|
66
|
+
Client's omitted timeout remains 30 seconds; `timeout: nil` adds no deadline or
|
|
67
|
+
timeout header. A low-level request with nil deadline can still explicitly
|
|
68
|
+
supply a core timeout header.
|
|
69
|
+
|
|
70
|
+
Execution is synchronous and cooperative. This transport cannot interrupt a
|
|
71
|
+
blocked handler or codec. It makes no network, DNS, TLS, fiber, backpressure,
|
|
72
|
+
concurrency, lifecycle, retry or cancellation compatibility promise. Use real
|
|
73
|
+
hosts and independent peers for those checks. These round trips are not
|
|
74
|
+
interoperability or official conformance evidence.
|
|
75
|
+
|
|
76
|
+
From the repository root:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
mise run testing:setup
|
|
80
|
+
mise run testing:test -- spec/transport_spec.rb
|
|
81
|
+
mise run testing:check
|
|
82
|
+
mise run testing:example
|
|
83
|
+
mise run testing:package
|
|
84
|
+
MISE_RUBY_VERSION=4.0.9 mise run testing:verify
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Pinned Ruby and Bundler must already be installed; these tasks never install
|
|
88
|
+
Bundler globally. The independent checksummed bundle, caches and archives use
|
|
89
|
+
`vendor/testing/`; SimpleCov starts before library requires, includes every
|
|
90
|
+
package library file and writes `coverage/testing/<Ruby>/`. `testing:check`
|
|
91
|
+
parses every maintained Ruby file and uses the repository RuboCop policy.
|
|
92
|
+
`testing:package` builds its own core archive, installs only the testing archive
|
|
93
|
+
with normal offline resolution from an adjacent core archive in a fresh external
|
|
94
|
+
GEM_HOME, checks public require without optional bindings/hosts, then installs
|
|
95
|
+
the cached Google dependency closure and runs the generated example in both
|
|
96
|
+
codecs and encodings. Root verification remains independent.
|
|
97
|
+
|
|
98
|
+
Licensed under the [MIT License](LICENSE). It is released with the whole
|
|
99
|
+
[release train](../../docs/releasing.md); its package tasks never publish it.
|
|
100
|
+
|
|
101
|
+
See the [supported 0.x contract](../../docs/support.md) for the target support
|
|
102
|
+
profile, matched package versions, compatibility policy and adoption gates.
|
|
103
|
+
|
|
104
|
+
## RPC observation
|
|
105
|
+
|
|
106
|
+
Configure `instrumentation:` on the core Client/Server to enable the fixed
|
|
107
|
+
neutral per-call lifecycle or the optional [OpenTelemetry adapter](../connectrpc-otel/README.md).
|
|
108
|
+
The server always extracts serialized propagation headers, even while a client
|
|
109
|
+
context is ambient. Direct processing ends with delivery `unobserved`.
|
|
110
|
+
|
|
111
|
+
## Static signatures
|
|
112
|
+
|
|
113
|
+
This package owns `sig/` and its discovery manifest. Add
|
|
114
|
+
`library "connectrpc-testing"` to a strict Steep target with this gem installed.
|
|
115
|
+
See the [RBS guide](../../docs/rbs.md) for supported APIs, extension contracts,
|
|
116
|
+
partial records, runtime validation and framework-signature limits.
|
|
117
|
+
`mise run rbs:check` checks application callers for this package; strict core
|
|
118
|
+
source checking is a separate target from backend implementation checking.
|
|
119
|
+
|
|
120
|
+
## Explicit unary cancellation
|
|
121
|
+
|
|
122
|
+
Pass `cancellation: source.token` to core or generated client calls, where
|
|
123
|
+
`source = ConnectRPC::CancellationSource.new`. The source is permanent and
|
|
124
|
+
shareable; only `source.cancel` initiates cancellation. `timeout: nil` keeps no
|
|
125
|
+
RPC deadline or wire timeout. Context offers cooperative `cancelled?`,
|
|
126
|
+
`cancellation_reason` and `check!`. See the [core guide](../../README.md) for
|
|
127
|
+
listener, arbitration and registration lifetime rules.
|
|
128
|
+
|
|
129
|
+
Testing forwards the read-only cancellation seam into a distinct server Context.
|
|
130
|
+
Completing server processing cannot complete its client or shared source. Checks
|
|
131
|
+
are cooperative; Testing does not interrupt a blocked handler.
|
|
132
|
+
|
|
133
|
+
## gRPC-Web
|
|
134
|
+
|
|
135
|
+
Manual encoded TransportRequest values can exercise explicitly enabled binary/text unary gRPC-Web. This adds no Web Client mode, serializer, network or browser conformance claim.
|
|
136
|
+
See the [Web guide](../../docs/grpc-web.md) for protocol, CORS, bounds and completion rules.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConnectRPC
|
|
4
|
+
module Testing
|
|
5
|
+
class Transport
|
|
6
|
+
def initialize(server:)
|
|
7
|
+
raise ArgumentError, "server must be a ConnectRPC::Server" unless server.is_a?(Server)
|
|
8
|
+
|
|
9
|
+
@server = server
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def call(request)
|
|
13
|
+
validate!(request)
|
|
14
|
+
CallCancellation.with_request(request) { |outgoing| perform_call(outgoing) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def perform_call(request)
|
|
20
|
+
request.cancellation ? request.cancellation.check! : request.deadline&.check!
|
|
21
|
+
response = dispatch(request)
|
|
22
|
+
request.cancellation ? request.cancellation.check! : request.deadline&.check!
|
|
23
|
+
Protocol.check_size!(response.body, request.response_limit)
|
|
24
|
+
response
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def dispatch(request)
|
|
28
|
+
headers = request.headers.dup
|
|
29
|
+
headers["connect-timeout-ms"] = [request.deadline.timeout_ms] if request.deadline
|
|
30
|
+
@server.call(path: request.url.path, http_method: request.http_method,
|
|
31
|
+
headers: headers, input: BytesReader.new(request.body), query: request.url.query || "",
|
|
32
|
+
cancellation: request.cancellation)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def validate!(request)
|
|
36
|
+
raise ArgumentError, "request must be a ConnectRPC::TransportRequest" unless request.is_a?(TransportRequest)
|
|
37
|
+
unless valid_url?(request.url)
|
|
38
|
+
raise ArgumentError,
|
|
39
|
+
"url must be an absolute HTTP(S) URI without credentials or fragment"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
validate_fields!(request)
|
|
43
|
+
unless request.deadline.nil? || request.deadline.is_a?(Deadline)
|
|
44
|
+
raise ArgumentError, "deadline must be a ConnectRPC::Deadline or nil"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
Protocol.limit!(request.response_limit)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def validate_fields!(request)
|
|
51
|
+
unless request.http_method.is_a?(String) && request.body.is_a?(String)
|
|
52
|
+
raise ArgumentError, "method and body must be Strings"
|
|
53
|
+
end
|
|
54
|
+
return if valid_headers?(request.headers)
|
|
55
|
+
|
|
56
|
+
raise ArgumentError, "headers must contain lower-case String keys and String arrays"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def valid_url?(url)
|
|
60
|
+
url.is_a?(URI::HTTP) && %w[http https].include?(url.scheme) && url.host && !url.host.empty? &&
|
|
61
|
+
!url.userinfo && !url.fragment
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def valid_headers?(headers)
|
|
65
|
+
headers.is_a?(Hash) && headers.all? do |key, values|
|
|
66
|
+
key.is_a?(String) && key == key.downcase && values.is_a?(Array) && values.all?(String)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# MessageIO controls read sizes. Offsets are bytes even for UTF-8 JSON.
|
|
71
|
+
class BytesReader
|
|
72
|
+
def initialize(bytes)
|
|
73
|
+
@bytes = bytes
|
|
74
|
+
@offset = 0
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def read(length)
|
|
78
|
+
return if @offset >= @bytes.bytesize
|
|
79
|
+
|
|
80
|
+
chunk = @bytes.byteslice(@offset, length)
|
|
81
|
+
@offset += chunk.bytesize
|
|
82
|
+
chunk
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
private_constant :BytesReader
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/sig/manifest.yaml
ADDED
metadata
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: connectrpc-testing
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- jimeh
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-11-15 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: connectrpc
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: 0.1.0
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: 0.1.0
|
|
26
|
+
description: Optional synchronous testing transport using an application-owned core
|
|
27
|
+
Server.
|
|
28
|
+
executables: []
|
|
29
|
+
extensions: []
|
|
30
|
+
extra_rdoc_files: []
|
|
31
|
+
files:
|
|
32
|
+
- LICENSE
|
|
33
|
+
- README.md
|
|
34
|
+
- lib/connectrpc/testing.rb
|
|
35
|
+
- lib/connectrpc/testing/transport.rb
|
|
36
|
+
- lib/connectrpc/testing/version.rb
|
|
37
|
+
- sig/connectrpc-testing.rbs
|
|
38
|
+
- sig/manifest.yaml
|
|
39
|
+
homepage: https://github.com/jimeh/connectrpc-ruby
|
|
40
|
+
licenses:
|
|
41
|
+
- MIT
|
|
42
|
+
metadata:
|
|
43
|
+
allowed_push_host: https://rubygems.org
|
|
44
|
+
rubygems_mfa_required: 'true'
|
|
45
|
+
source_code_uri: https://github.com/jimeh/connectrpc-ruby
|
|
46
|
+
rdoc_options: []
|
|
47
|
+
require_paths:
|
|
48
|
+
- lib
|
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.4'
|
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0'
|
|
59
|
+
requirements: []
|
|
60
|
+
rubygems_version: 3.6.9
|
|
61
|
+
specification_version: 4
|
|
62
|
+
summary: In-process transport for real ConnectRPC unary round trips
|
|
63
|
+
test_files: []
|