Skip to content

Commit 58825f7

Browse files
authored
fix: Integration test for NodeJS Typescript client (#17)
* Revert to using a fresh rack app instance on every test * Working node client * Basic working integration spec, still need to add auth * Add node compilation to the rakefile * Update apparition gem to avoid warning during spec suite * Rubocop fixes * Rubocop (non auto) * Revert "Update apparition gem to avoid warning during spec suite" This reverts commit 12cf4e8. * Add basic auth for node client * Remove unneeded ts-node dependency * ANY_CONTENT_TYPES -> UNSPECIFIED_CONTENT_TYPES * Learning
1 parent 729c3e9 commit 58825f7

13 files changed

+222
-7
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
spec/pb-ruby/*.rb
2+
spec/pb-ts/*.[j|t]s
23
spec/pb-js-grpc-web/*.js
34
spec/pb-js-grpc-web-text/*.js
4-
spec/js-client-src/node_modules
55
spec/js-client/main.js
6+
spec/node-client/dist
67
coverage
78

89
node_modules

Rakefile

+37-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ RSpec::Core::RakeTask.new(:spec)
88
CLEAN.include('spec/pb-ruby/*.rb')
99
CLEAN.include('spec/pb-js-grpc-web/*.js')
1010
CLEAN.include('spec/pb-js-grpc-web-text/*.js')
11+
CLEAN.include('spec/pb-ts/*.js')
12+
CLEAN.include('spec/pb-ts/*.ts')
1113
CLEAN.include('spec/js-client/main.js')
14+
CLEAN.include('spec/node-client/dist/*')
1215

1316
module RakeHelpers
1417
def self.compile_protos_js_cmd(mode, output_dir)
@@ -41,6 +44,23 @@ task :compile_protos_ruby do
4144
].join(' ')
4245
end
4346

47+
task :compile_protos_ts do
48+
defs_dir = File.expand_path('spec', __dir__)
49+
proto_files = Dir[File.join(defs_dir, 'pb-src/**/*.proto')]
50+
proto_input_files = proto_files.map { |f| f.gsub(defs_dir, '/defs') }
51+
sh [
52+
'docker run',
53+
"-v \"#{defs_dir}:/defs\"",
54+
'--entrypoint protoc',
55+
'namely/protoc-all',
56+
'--plugin=protoc-gen-ts=/usr/bin/protoc-gen-ts',
57+
'--js_out=import_style=commonjs,binary:/defs/pb-ts',
58+
'--ts_out=service=grpc-web:/defs/pb-ts',
59+
'-I /defs/pb-src',
60+
proto_input_files.join(' '),
61+
].join(' ')
62+
end
63+
4464
task compile_js_client: [:compile_protos_js] do
4565
compile_js_cmd = '"cd spec/js-client-src; yarn install; yarn run webpack"'
4666
sh [
@@ -51,6 +71,16 @@ task compile_js_client: [:compile_protos_js] do
5171
].join(' && ')
5272
end
5373

74+
task compile_node_client: [:compile_protos_ts] do
75+
compile_node_cmd = '"cd spec/node-client; yarn install; yarn build"'
76+
sh [
77+
'docker-compose down',
78+
'docker-compose build',
79+
"docker-compose run --use-aliases ruby #{compile_node_cmd}",
80+
'docker-compose down',
81+
].join(' && ')
82+
end
83+
5484
task :run_specs_in_docker do
5585
sh [
5686
'docker-compose down',
@@ -60,4 +90,10 @@ task :run_specs_in_docker do
6090
].join(' && ')
6191
end
6292

63-
task default: %i[clean compile_protos_ruby compile_js_client run_specs_in_docker]
93+
task default: %i[
94+
clean
95+
compile_protos_ruby
96+
compile_js_client
97+
compile_node_client
98+
run_specs_in_docker
99+
]

lib/grpc_web/content_types.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ module GRPCWeb::ContentTypes
1515
TEXT_CONTENT_TYPE,
1616
TEXT_PROTO_CONTENT_TYPE,
1717
].freeze
18-
ANY_CONTENT_TYPES = ['*/*', ''].freeze
18+
UNSPECIFIED_CONTENT_TYPES = ['*/*', '', nil].freeze
1919
end

lib/grpc_web/server/grpc_request_processor.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def execute_request(request)
4242

4343
# Use Accept header value if specified, otherwise use request content type
4444
def response_content_type(request)
45-
if request.accept.nil? || ANY_CONTENT_TYPES.include?(request.accept)
45+
if UNSPECIFIED_CONTENT_TYPES.include?(request.accept)
4646
request.content_type
4747
else
4848
request.accept

lib/grpc_web/server/rack_handler.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def valid_content_types?(rack_request)
4343
return false unless ALL_CONTENT_TYPES.include?(rack_request.content_type)
4444

4545
accept = rack_request.get_header(ACCEPT_HEADER)
46-
return true if ANY_CONTENT_TYPES.include?(accept)
46+
return true if UNSPECIFIED_CONTENT_TYPES.include?(accept)
4747

4848
ALL_CONTENT_TYPES.include?(accept)
4949
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
require 'json'
5+
require 'hello_services_pb'
6+
7+
RSpec.describe 'connecting to a ruby server from a nodejs client', type: :feature do
8+
subject(:json_result) { `#{node_cmd}` }
9+
10+
let(:node_client) { File.expand_path('../node-client/dist/client.js', __dir__) }
11+
let(:node_cmd) do
12+
[
13+
'node',
14+
node_client,
15+
server_url,
16+
name,
17+
basic_username,
18+
basic_password,
19+
].join(' ')
20+
end
21+
let(:result) { JSON.parse(json_result) }
22+
23+
let(:basic_password) { 'supersecretpassword' }
24+
let(:basic_username) { 'supermanuser' }
25+
let(:service) { TestHelloService }
26+
let(:rack_app) do
27+
app = TestGRPCWebApp.build(service)
28+
app.use Rack::Auth::Basic do |username, password|
29+
[username, password] == [basic_username, basic_password]
30+
end
31+
app
32+
end
33+
34+
let(:browser) { Capybara::Session.new(Capybara.default_driver, rack_app) }
35+
let(:server) { browser.server }
36+
let(:server_url) { "http://#{server.host}:#{server.port}" }
37+
let(:name) { "James\u1f61d" }
38+
39+
it 'returns the expected response from the service' do
40+
expect(result['response']).to eq('message' => "Hello #{name}")
41+
end
42+
43+
context 'with a service that raises a standard gRPC error' do
44+
let(:service) do
45+
Class.new(TestHelloService) do
46+
def say_hello(_request, _metadata = nil)
47+
raise ::GRPC::InvalidArgument, 'Test message'
48+
end
49+
end
50+
end
51+
52+
it 'raises an error' do
53+
expect(result['error']).to include('grpc-message' => ['Test message'], 'grpc-status' => ['3'])
54+
end
55+
end
56+
57+
context 'with a service that raises a custom error' do
58+
let(:service) do
59+
Class.new(TestHelloService) do
60+
def say_hello(_request, _metadata = nil)
61+
raise 'Some random error'
62+
end
63+
end
64+
end
65+
66+
it 'raises an error' do
67+
expect(result['error']).to include(
68+
'grpc-message' => ['RuntimeError: Some random error'],
69+
'grpc-status' => ['2'],
70+
)
71+
end
72+
end
73+
end

spec/node-client/client.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { grpc } from "@improbable-eng/grpc-web";
2+
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";
3+
4+
import {HelloServiceClient} from './pb-ts/hello_pb_service';
5+
import {HelloRequest} from './pb-ts/hello_pb';
6+
7+
// Required for grpc-web in a NodeJS environment (vs. browser)
8+
grpc.setDefaultTransport(NodeHttpTransport());
9+
10+
// Usage: node client.js http://server:port nameParam username password
11+
const serverUrl = process.argv[2];
12+
const helloName = process.argv[3];
13+
const username = process.argv[4];
14+
const password = process.argv[5];
15+
16+
const client = new HelloServiceClient(serverUrl);
17+
const headers = new grpc.Metadata();
18+
19+
if (username && password) {
20+
const encodedCredentials = Buffer.from(`${username}:${password}`).toString("base64");
21+
headers.set("Authorization", `Basic ${encodedCredentials}`);
22+
}
23+
24+
const req = new HelloRequest();
25+
req.setName(helloName);
26+
27+
client.sayHello(req, headers, (err, resp) => {
28+
var result = {
29+
response: resp && resp.toObject(),
30+
error: err && err.metadata && err.metadata.headersMap
31+
}
32+
// Emit response and/or error as JSON so it can be parsed from Ruby
33+
console.log(JSON.stringify(result));
34+
});

spec/node-client/package.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "grpc-web-node-ts-client",
3+
"version": "0.1.0",
4+
"description": "gRPC-Web Node Typescript client example",
5+
"license": "Apache-2.0",
6+
"scripts": {
7+
"build": "tsc --pretty"
8+
},
9+
"dependencies": {
10+
"@improbable-eng/grpc-web": "0.12.0",
11+
"@improbable-eng/grpc-web-node-http-transport": "0.12.0",
12+
"@types/google-protobuf": "^3.7.2",
13+
"@types/node": "^13.7.4",
14+
"google-protobuf": "^3.8.0"
15+
},
16+
"devDependencies": {
17+
"typescript": "^3.7.2"
18+
}
19+
}

spec/node-client/pb-ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../pb-ts/

spec/node-client/tsconfig.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
4+
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
5+
"outDir": "dist", /* Redirect output structure to the directory. */
6+
"strict": true, /* Enable all strict type-checking options. */
7+
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
8+
"allowJs": true, /* Allow javascript files to be compiled. */
9+
}
10+
}

spec/node-client/yarn.lock

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2+
# yarn lockfile v1
3+
4+
5+
"@improbable-eng/[email protected]":
6+
version "0.12.0"
7+
resolved "https://registry.yarnpkg.com/@improbable-eng/grpc-web-node-http-transport/-/grpc-web-node-http-transport-0.12.0.tgz#827138160d2f945620e103473042025529c00c8e"
8+
integrity sha512-+Kjz+Dktfz5LKTZA9ZW/Vlww6HF9KaKz4x2mVe1O8CJdOP2WfzC+KY8L6EWMqVLrV4MvdBuQdSgDmvSJz+OGuA==
9+
10+
"@improbable-eng/[email protected]":
11+
version "0.12.0"
12+
resolved "https://registry.yarnpkg.com/@improbable-eng/grpc-web/-/grpc-web-0.12.0.tgz#9b10a7edf2a1d7672f8997e34a60e7b70e49738f"
13+
integrity sha512-uJjgMPngreRTYPBuo6gswMj1gK39Wbqre/RgE0XnSDXJRg6ST7ZhuS53dFE6Vc2CX4jxgl+cO+0B3op8LA4Q0Q==
14+
dependencies:
15+
browser-headers "^0.4.0"
16+
17+
"@types/google-protobuf@^3.7.2":
18+
version "3.7.2"
19+
resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.7.2.tgz#cd8a360c193ce4d672575a20a79f49ba036d38d2"
20+
integrity sha512-ifFemzjNchFBCtHS6bZNhSZCBu7tbtOe0e8qY0z2J4HtFXmPJjm6fXSaQsTG7yhShBEZtt2oP/bkwu5k+emlkQ==
21+
22+
"@types/node@^13.7.4":
23+
version "13.7.4"
24+
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.4.tgz#76c3cb3a12909510f52e5dc04a6298cdf9504ffd"
25+
integrity sha512-oVeL12C6gQS/GAExndigSaLxTrKpQPxewx9bOcwfvJiJge4rr7wNaph4J+ns5hrmIV2as5qxqN8YKthn9qh0jw==
26+
27+
browser-headers@^0.4.0:
28+
version "0.4.1"
29+
resolved "https://registry.yarnpkg.com/browser-headers/-/browser-headers-0.4.1.tgz#4308a7ad3b240f4203dbb45acedb38dc2d65dd02"
30+
integrity sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==
31+
32+
google-protobuf@^3.8.0:
33+
version "3.11.4"
34+
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.11.4.tgz#598ca405a3cfa917a2132994d008b5932ef42014"
35+
integrity sha512-lL6b04rDirurUBOgsY2+LalI6Evq8eH5TcNzi7TYQ3BsIWelT0KSOQSBsXuavEkNf+odQU6c0lgz3UsZXeNX9Q==
36+
37+
typescript@^3.7.2:
38+
version "3.7.5"
39+
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
40+
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==

spec/pb-ts/.gitkeep

Whitespace-only changes.

spec/support/test_grpc_web_app.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
# Used to build a Rack app hosting the HelloService for integration testing.
1010
module TestGRPCWebApp
1111
def self.build(service_class = TestHelloService)
12-
GRPCWeb.handle(service_class)
12+
grpc_app = GRPCWeb::RackApp.new
13+
grpc_app.handle(service_class)
1314

1415
Rack::Builder.new do
1516
use Rack::Cors do
@@ -20,7 +21,7 @@ def self.build(service_class = TestHelloService)
2021
end
2122
use Rack::Lint
2223

23-
run GRPCWeb.rack_app
24+
run grpc_app
2425
end
2526
end
2627
end

0 commit comments

Comments
 (0)