diff --git a/Gemfile b/Gemfile index 6d7f660f3..9180e62fb 100644 --- a/Gemfile +++ b/Gemfile @@ -96,6 +96,7 @@ group :test do gem 'poltergeist' gem 'coveralls', require: false gem 'email_spec' + gem 'http' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 9638fae74..eaef01916 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -143,6 +143,8 @@ GEM railties (>= 3.2.6, < 5.0) diff-lcs (1.2.5) docile (1.1.5) + domain_name (0.5.20161021) + unf (>= 0.0.5, < 1.0.0) easy_translate (0.5.0) json thread @@ -189,6 +191,15 @@ GEM hashie (3.4.3) highline (1.7.8) htmlentities (4.3.4) + http (2.1.0) + addressable (~> 2.3) + http-cookie (~> 1.0) + http-form_data (~> 1.0.1) + http_parser.rb (~> 0.6.0) + http-cookie (1.0.3) + domain_name (~> 0.5) + http-form_data (1.0.1) + http_parser.rb (0.6.0) httpi (2.4.1) rack i18n (0.7.0) @@ -423,6 +434,9 @@ GEM thread_safe (~> 0.1) uglifier (3.0.3) execjs (>= 0.3.0, < 3) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.2) unicode-display_width (1.1.1) unicorn (5.1.0) kgio (~> 2.6) @@ -484,6 +498,7 @@ DEPENDENCIES graphiql-rails graphql groupdate (~> 3.1.0) + http i18n-tasks initialjs-rails (= 0.2.0.4) invisible_captcha (~> 0.9.1) diff --git a/config/routes.rb b/config/routes.rb index 63be98384..b530df52c 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -264,7 +264,6 @@ Rails.application.routes.draw do # GraphQL mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql' - get '/graphql', to: 'graphql#query' post '/graphql', to: 'graphql#query' if Rails.env.development? diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb index 3504de501..2b53cd0fb 100644 --- a/spec/controllers/graphql_controller_spec.rb +++ b/spec/controllers/graphql_controller_spec.rb @@ -1,63 +1,73 @@ require 'rails_helper' +require 'http' describe GraphqlController do - let!(:uri) { URI::HTTP.build(host: 'localhost', path: '/graphql', port: 3000) } + let(:uri) { URI::HTTP.build(host: 'localhost', path: '/graphql', port: 3000) } + let(:query_string) { "" } + let(:body) { {query: query_string}.to_json } - describe "GET request" do - it "is accepted when valid" do - # Like POST requests but the query string goes in the URL - # More info at: http://graphql.org/learn/serving-over-http/#get-request - skip - end + describe "POST requests" do + let(:author) { create(:user) } + let(:proposal) { create(:proposal, author: author) } + let(:response) { HTTP.headers('Content-Type' => 'application/json').post(uri, body: body) } + let(:response_body) { JSON.parse(response.body) } - it "is rejected when not valid" do - skip - # Just doing this to trigger Travis CI build - end - end + context "when query string is valid" do + let(:query_string) { "{ proposal(id: #{proposal.id}) { title, author { username } } }" } + let(:returned_proposal) { response_body['data']['proposal'] } - describe "POST request" do - let(:post_request) { - req = Net::HTTP::Post.new(uri) - req['Content-Type'] = 'application/json' - req - } - - it "succeeds when valid" do - body = { query: "{ proposals(first: 2) { edges { node { id } } } }" }.to_json - response = Net::HTTP.start(uri.host, uri.port) do |http| - post_request.body = body - http.request(post_request) + it "returns HTTP 200 OK" do + expect(response.code).to eq(200) end - # Is it enough to check the status code or should I also check the body? - expect(response.code.to_i).to eq(200) - end - - it "succeeds and returns an error when disclosed attributes are requested" do - body = { query: "{ user(id: 1) { encrypted_password } }" }.to_json - response = Net::HTTP.start(uri.host, uri.port) do |http| - post_request.body = body - http.request(post_request) + it "returns first-level fields" do + expect(returned_proposal['title']).to eq(proposal.title) end - body_hash = JSON.parse(response.body) - expect(body_hash['errors']).to be_present + it "returns nested fields" do + expect(returned_proposal['author']['username']).to eq(author.username) + end end - it "fails when no query string is provided" do - body = {}.to_json - response = Net::HTTP.start(uri.host, uri.port) do |http| - post_request.body = body - http.request(post_request) + context "when query string asks for invalid fields" do + let(:query_string) { "{ proposal(id: #{proposal.id}) { missing_field } }" } + + it "returns HTTP 200 OK" do + expect(response.code).to eq(200) end - # TODO: I must find a way to handle this better. Right now it shows a 500 - # Internal Server Error, I think I should always return a valid (but empty) - # JSON document like '{}' - expect(response.code.to_i).not_to eq(200) + it "doesn't return any data" do + expect(response_body['data']).to be_nil + end + + it "returns error inside body" do + expect(response_body['errors']).to be_present + end + end + + context "when query string is not valid" do + let(:query_string) { "invalid" } + + it "returns HTTP 400 Bad Request" do + expect(response.code).to eq(400) + end + end + + context "when query string is missing" do + let(:query_string) { nil } + + it "returns HTTP 400 Bad Request" do + expect(response.code).to eq(400) + end + end + + context "when body is missing" do + let(:body) { nil } + + it "returns HTTP 400 Bad Request" do + expect(response.code).to eq(400) + end end end - end