Skip to content

Commit

Permalink
fix: Integration test for NodeJS Typescript client (#17)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
shkolnik authored Feb 21, 2020
1 parent 729c3e9 commit 58825f7
Show file tree
Hide file tree
Showing 13 changed files with 222 additions and 7 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
spec/pb-ruby/*.rb
spec/pb-ts/*.[j|t]s
spec/pb-js-grpc-web/*.js
spec/pb-js-grpc-web-text/*.js
spec/js-client-src/node_modules
spec/js-client/main.js
spec/node-client/dist
coverage

node_modules
Expand Down
38 changes: 37 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ RSpec::Core::RakeTask.new(:spec)
CLEAN.include('spec/pb-ruby/*.rb')
CLEAN.include('spec/pb-js-grpc-web/*.js')
CLEAN.include('spec/pb-js-grpc-web-text/*.js')
CLEAN.include('spec/pb-ts/*.js')
CLEAN.include('spec/pb-ts/*.ts')
CLEAN.include('spec/js-client/main.js')
CLEAN.include('spec/node-client/dist/*')

module RakeHelpers
def self.compile_protos_js_cmd(mode, output_dir)
Expand Down Expand Up @@ -41,6 +44,23 @@ task :compile_protos_ruby do
].join(' ')
end

task :compile_protos_ts do
defs_dir = File.expand_path('spec', __dir__)
proto_files = Dir[File.join(defs_dir, 'pb-src/**/*.proto')]
proto_input_files = proto_files.map { |f| f.gsub(defs_dir, '/defs') }
sh [
'docker run',
"-v \"#{defs_dir}:/defs\"",
'--entrypoint protoc',
'namely/protoc-all',
'--plugin=protoc-gen-ts=/usr/bin/protoc-gen-ts',
'--js_out=import_style=commonjs,binary:/defs/pb-ts',
'--ts_out=service=grpc-web:/defs/pb-ts',
'-I /defs/pb-src',
proto_input_files.join(' '),
].join(' ')
end

task compile_js_client: [:compile_protos_js] do
compile_js_cmd = '"cd spec/js-client-src; yarn install; yarn run webpack"'
sh [
Expand All @@ -51,6 +71,16 @@ task compile_js_client: [:compile_protos_js] do
].join(' && ')
end

task compile_node_client: [:compile_protos_ts] do
compile_node_cmd = '"cd spec/node-client; yarn install; yarn build"'
sh [
'docker-compose down',
'docker-compose build',
"docker-compose run --use-aliases ruby #{compile_node_cmd}",
'docker-compose down',
].join(' && ')
end

task :run_specs_in_docker do
sh [
'docker-compose down',
Expand All @@ -60,4 +90,10 @@ task :run_specs_in_docker do
].join(' && ')
end

task default: %i[clean compile_protos_ruby compile_js_client run_specs_in_docker]
task default: %i[
clean
compile_protos_ruby
compile_js_client
compile_node_client
run_specs_in_docker
]
2 changes: 1 addition & 1 deletion lib/grpc_web/content_types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ module GRPCWeb::ContentTypes
TEXT_CONTENT_TYPE,
TEXT_PROTO_CONTENT_TYPE,
].freeze
ANY_CONTENT_TYPES = ['*/*', ''].freeze
UNSPECIFIED_CONTENT_TYPES = ['*/*', '', nil].freeze
end
2 changes: 1 addition & 1 deletion lib/grpc_web/server/grpc_request_processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def execute_request(request)

# Use Accept header value if specified, otherwise use request content type
def response_content_type(request)
if request.accept.nil? || ANY_CONTENT_TYPES.include?(request.accept)
if UNSPECIFIED_CONTENT_TYPES.include?(request.accept)
request.content_type
else
request.accept
Expand Down
2 changes: 1 addition & 1 deletion lib/grpc_web/server/rack_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def valid_content_types?(rack_request)
return false unless ALL_CONTENT_TYPES.include?(rack_request.content_type)

accept = rack_request.get_header(ACCEPT_HEADER)
return true if ANY_CONTENT_TYPES.include?(accept)
return true if UNSPECIFIED_CONTENT_TYPES.include?(accept)

ALL_CONTENT_TYPES.include?(accept)
end
Expand Down
73 changes: 73 additions & 0 deletions spec/integration/ruby_server_nodejs_client_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

require 'spec_helper'
require 'json'
require 'hello_services_pb'

RSpec.describe 'connecting to a ruby server from a nodejs client', type: :feature do
subject(:json_result) { `#{node_cmd}` }

let(:node_client) { File.expand_path('../node-client/dist/client.js', __dir__) }
let(:node_cmd) do
[
'node',
node_client,
server_url,
name,
basic_username,
basic_password,
].join(' ')
end
let(:result) { JSON.parse(json_result) }

let(:basic_password) { 'supersecretpassword' }
let(:basic_username) { 'supermanuser' }
let(:service) { TestHelloService }
let(:rack_app) do
app = TestGRPCWebApp.build(service)
app.use Rack::Auth::Basic do |username, password|
[username, password] == [basic_username, basic_password]
end
app
end

let(:browser) { Capybara::Session.new(Capybara.default_driver, rack_app) }
let(:server) { browser.server }
let(:server_url) { "http://#{server.host}:#{server.port}" }
let(:name) { "James\u1f61d" }

it 'returns the expected response from the service' do
expect(result['response']).to eq('message' => "Hello #{name}")
end

context 'with a service that raises a standard gRPC error' do
let(:service) do
Class.new(TestHelloService) do
def say_hello(_request, _metadata = nil)
raise ::GRPC::InvalidArgument, 'Test message'
end
end
end

it 'raises an error' do
expect(result['error']).to include('grpc-message' => ['Test message'], 'grpc-status' => ['3'])
end
end

context 'with a service that raises a custom error' do
let(:service) do
Class.new(TestHelloService) do
def say_hello(_request, _metadata = nil)
raise 'Some random error'
end
end
end

it 'raises an error' do
expect(result['error']).to include(
'grpc-message' => ['RuntimeError: Some random error'],
'grpc-status' => ['2'],
)
end
end
end
34 changes: 34 additions & 0 deletions spec/node-client/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { grpc } from "@improbable-eng/grpc-web";
import { NodeHttpTransport } from "@improbable-eng/grpc-web-node-http-transport";

import {HelloServiceClient} from './pb-ts/hello_pb_service';
import {HelloRequest} from './pb-ts/hello_pb';

// Required for grpc-web in a NodeJS environment (vs. browser)
grpc.setDefaultTransport(NodeHttpTransport());

// Usage: node client.js http://server:port nameParam username password
const serverUrl = process.argv[2];
const helloName = process.argv[3];
const username = process.argv[4];
const password = process.argv[5];

const client = new HelloServiceClient(serverUrl);
const headers = new grpc.Metadata();

if (username && password) {
const encodedCredentials = Buffer.from(`${username}:${password}`).toString("base64");
headers.set("Authorization", `Basic ${encodedCredentials}`);
}

const req = new HelloRequest();
req.setName(helloName);

client.sayHello(req, headers, (err, resp) => {
var result = {
response: resp && resp.toObject(),
error: err && err.metadata && err.metadata.headersMap
}
// Emit response and/or error as JSON so it can be parsed from Ruby
console.log(JSON.stringify(result));
});
19 changes: 19 additions & 0 deletions spec/node-client/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "grpc-web-node-ts-client",
"version": "0.1.0",
"description": "gRPC-Web Node Typescript client example",
"license": "Apache-2.0",
"scripts": {
"build": "tsc --pretty"
},
"dependencies": {
"@improbable-eng/grpc-web": "0.12.0",
"@improbable-eng/grpc-web-node-http-transport": "0.12.0",
"@types/google-protobuf": "^3.7.2",
"@types/node": "^13.7.4",
"google-protobuf": "^3.8.0"
},
"devDependencies": {
"typescript": "^3.7.2"
}
}
1 change: 1 addition & 0 deletions spec/node-client/pb-ts
10 changes: 10 additions & 0 deletions spec/node-client/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"outDir": "dist", /* Redirect output structure to the directory. */
"strict": true, /* Enable all strict type-checking options. */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"allowJs": true, /* Allow javascript files to be compiled. */
}
}
40 changes: 40 additions & 0 deletions spec/node-client/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


"@improbable-eng/grpc-web-node-http-transport@0.12.0":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@improbable-eng/grpc-web-node-http-transport/-/grpc-web-node-http-transport-0.12.0.tgz#827138160d2f945620e103473042025529c00c8e"
integrity sha512-+Kjz+Dktfz5LKTZA9ZW/Vlww6HF9KaKz4x2mVe1O8CJdOP2WfzC+KY8L6EWMqVLrV4MvdBuQdSgDmvSJz+OGuA==

"@improbable-eng/grpc-web@0.12.0":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@improbable-eng/grpc-web/-/grpc-web-0.12.0.tgz#9b10a7edf2a1d7672f8997e34a60e7b70e49738f"
integrity sha512-uJjgMPngreRTYPBuo6gswMj1gK39Wbqre/RgE0XnSDXJRg6ST7ZhuS53dFE6Vc2CX4jxgl+cO+0B3op8LA4Q0Q==
dependencies:
browser-headers "^0.4.0"

"@types/google-protobuf@^3.7.2":
version "3.7.2"
resolved "https://registry.yarnpkg.com/@types/google-protobuf/-/google-protobuf-3.7.2.tgz#cd8a360c193ce4d672575a20a79f49ba036d38d2"
integrity sha512-ifFemzjNchFBCtHS6bZNhSZCBu7tbtOe0e8qY0z2J4HtFXmPJjm6fXSaQsTG7yhShBEZtt2oP/bkwu5k+emlkQ==

"@types/node@^13.7.4":
version "13.7.4"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.4.tgz#76c3cb3a12909510f52e5dc04a6298cdf9504ffd"
integrity sha512-oVeL12C6gQS/GAExndigSaLxTrKpQPxewx9bOcwfvJiJge4rr7wNaph4J+ns5hrmIV2as5qxqN8YKthn9qh0jw==

browser-headers@^0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/browser-headers/-/browser-headers-0.4.1.tgz#4308a7ad3b240f4203dbb45acedb38dc2d65dd02"
integrity sha512-CA9hsySZVo9371qEHjHZtYxV2cFtVj5Wj/ZHi8ooEsrtm4vOnl9Y9HmyYWk9q+05d7K3rdoAE0j3MVEFVvtQtg==

google-protobuf@^3.8.0:
version "3.11.4"
resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.11.4.tgz#598ca405a3cfa917a2132994d008b5932ef42014"
integrity sha512-lL6b04rDirurUBOgsY2+LalI6Evq8eH5TcNzi7TYQ3BsIWelT0KSOQSBsXuavEkNf+odQU6c0lgz3UsZXeNX9Q==

typescript@^3.7.2:
version "3.7.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae"
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==
Empty file added spec/pb-ts/.gitkeep
Empty file.
5 changes: 3 additions & 2 deletions spec/support/test_grpc_web_app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
# Used to build a Rack app hosting the HelloService for integration testing.
module TestGRPCWebApp
def self.build(service_class = TestHelloService)
GRPCWeb.handle(service_class)
grpc_app = GRPCWeb::RackApp.new
grpc_app.handle(service_class)

Rack::Builder.new do
use Rack::Cors do
Expand All @@ -20,7 +21,7 @@ def self.build(service_class = TestHelloService)
end
use Rack::Lint

run GRPCWeb.rack_app
run grpc_app
end
end
end

0 comments on commit 58825f7

Please sign in to comment.