diff --git a/Gemfile b/Gemfile index 8b723aa66..8b5eb2854 100644 --- a/Gemfile +++ b/Gemfile @@ -27,7 +27,7 @@ gem "foundation_rails_helper", "~> 4.0.1" gem "globalize", "~> 6.3.0" gem "globalize-accessors", "~> 0.3.0" gem "graphiql-rails", "~> 1.8.0" -gem "graphql", "~> 2.0.0" +gem "graphql", "~> 2.3.18" gem "groupdate", "~> 6.5.1" gem "image_processing", "~> 1.13.0" gem "invisible_captcha", "~> 2.3.0" diff --git a/Gemfile.lock b/Gemfile.lock index ed82a996c..8f6909ae6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -228,6 +228,7 @@ GEM faraday-net_http (3.3.0) net-http ffi (1.17.0) + fiber-storage (1.0.0) file_validators (3.0.0) activemodel (>= 3.2) mime-types (>= 1.0) @@ -255,8 +256,9 @@ GEM graphiql-rails (1.8.0) railties sprockets-rails - graphql (2.0.31) + graphql (2.3.18) base64 + fiber-storage groupdate (6.5.1) activesupport (>= 7) gyoku (1.4.0) @@ -740,7 +742,7 @@ DEPENDENCIES globalize (~> 6.3.0) globalize-accessors (~> 0.3.0) graphiql-rails (~> 1.8.0) - graphql (~> 2.0.0) + graphql (~> 2.3.18) groupdate (~> 6.5.1) i18n-tasks (~> 0.9.37) image_processing (~> 1.13.0) diff --git a/spec/controllers/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb index eeed8c578..c542593dc 100644 --- a/spec/controllers/graphql_controller_spec.rb +++ b/spec/controllers/graphql_controller_spec.rb @@ -2,12 +2,6 @@ require "rails_helper" # Useful resource: http://graphql.org/learn/serving-over-http/ -def parser_error_raised?(response) - data_is_empty = response["data"].nil? - error_is_present = (JSON.parse(response.body)["errors"].first["message"] =~ /^Parse error on/) - data_is_empty && error_is_present -end - describe GraphqlController, type: :request do let(:proposal) { create(:proposal) } @@ -23,7 +17,8 @@ describe GraphqlController, type: :request do get "/graphql", params: { query: "Malformed query string" } expect(response).to have_http_status(:ok) - expect(parser_error_raised?(response)).to be_truthy + expect(response.parsed_body["data"]).to be nil + expect(response.parsed_body["errors"]).to be_present end specify "without query string" do @@ -58,7 +53,8 @@ describe GraphqlController, type: :request do post "/graphql", params: { query: "Malformed query string" }.to_json, headers: json_headers expect(response).to have_http_status(:ok) - expect(parser_error_raised?(response)).to be_truthy + expect(response.parsed_body["data"]).to be nil + expect(response.parsed_body["errors"]).to be_present end it "without query string" do diff --git a/spec/graphql/types/query_type_spec.rb b/spec/graphql/types/query_type_spec.rb index 6e050fd0c..37888ef32 100644 --- a/spec/graphql/types/query_type_spec.rb +++ b/spec/graphql/types/query_type_spec.rb @@ -5,18 +5,18 @@ describe Types::QueryType do let(:proposal) { create(:proposal, author: user) } it "returns fields of Int type" do - response = execute("{ proposal(id: #{proposal.id}) { cached_votes_up } }") - expect(dig(response, "data.proposal.cached_votes_up")).to eq(proposal.cached_votes_up) + response = run_graphql_field("Proposal.cached_votes_up", proposal) + expect(response).to eq(proposal.cached_votes_up) end it "returns fields of String type" do - response = execute("{ proposal(id: #{proposal.id}) { title } }") - expect(dig(response, "data.proposal.title")).to eq(proposal.title) + response = run_graphql_field("Proposal.title", proposal) + expect(response).to eq(proposal.title) end it "returns belongs_to associations" do - response = execute("{ proposal(id: #{proposal.id}) { public_author { username } } }") - expect(dig(response, "data.proposal.public_author.username")).to eq(proposal.public_author.username) + response = run_graphql_field("Proposal.public_author.username", proposal) + expect(response).to eq(proposal.public_author.username) end it "returns has_many associations" do @@ -32,36 +32,46 @@ describe Types::QueryType do end it "hides confidential fields of Int type" do - response = execute("{ user(id: #{user.id}) { failed_census_calls_count } }") - expect(hidden_field?(response, "failed_census_calls_count")).to be_truthy + expect do + run_graphql_field("User.failed_census_calls_count", user) + end.to raise_error GraphQL::Testing::Helpers::FieldNotDefinedError end it "hides confidential fields of String type" do - response = execute("{ user(id: #{user.id}) { encrypted_password } }") - expect(hidden_field?(response, "encrypted_password")).to be_truthy + expect do + run_graphql_field("User.encrypted_password", user) + end.to raise_error GraphQL::Testing::Helpers::FieldNotDefinedError end it "hides confidential has_one associations" do user.administrator = create(:administrator) - response = execute("{ user(id: #{user.id}) { administrator { id } } }") - expect(hidden_field?(response, "administrator")).to be_truthy + + expect do + run_graphql_field("User.administrator.id", user) + end.to raise_error GraphQL::Testing::Helpers::FieldNotDefinedError, /no field named `administrator`/ end it "hides confidential belongs_to associations" do - create(:failed_census_call, user: user) - response = execute("{ user(id: #{user.id}) { failed_census_calls { id } } }") - expect(hidden_field?(response, "failed_census_calls")).to be_truthy + user.geozone = create(:geozone) + + expect do + run_graphql_field("User.geozone.id", user) + end.to raise_error GraphQL::Testing::Helpers::FieldNotDefinedError, /no field named `geozone`/ end it "hides confidential has_many associations" do create(:direct_message, sender: user) - response = execute("{ user(id: #{user.id}) { direct_messages_sent { id } } }") - expect(hidden_field?(response, "direct_messages_sent")).to be_truthy + + expect do + run_graphql_field("User.direct_messages_sent.id", user) + end.to raise_error GraphQL::Testing::Helpers::FieldNotDefinedError, + /no field named `direct_messages_sent`/ end it "hides confidential fields inside deeply nested queries" do - response = execute("{ proposals(first: 1) { edges { node { public_author { encrypted_password } } } } }") - expect(hidden_field?(response, "encrypted_password")).to be_truthy + expect do + run_graphql_field("Proposal.public_author.encrypted_password", proposal) + end.to raise_error GraphQL::Testing::Helpers::FieldNotDefinedError, /no field named `encrypted_password`/ end describe "#comments" do diff --git a/spec/graphql/types/user_type_spec.rb b/spec/graphql/types/user_type_spec.rb index e81baaf90..3b8f60716 100644 --- a/spec/graphql/types/user_type_spec.rb +++ b/spec/graphql/types/user_type_spec.rb @@ -7,28 +7,25 @@ describe Types::UserType do it "does not link debates" do create(:debate, author: user) - response = execute("{ user(id: #{user.id}) { public_debates { edges { node { title } } } } }") - received_debates = dig(response, "data.user.public_debates.edges") + response = run_graphql_field("User.public_debates", user) - expect(received_debates).to eq [] + expect(response.items).to eq [] end it "does not link proposals" do create(:proposal, author: user) - response = execute("{ user(id: #{user.id}) { public_proposals { edges { node { title } } } } }") - received_proposals = dig(response, "data.user.public_proposals.edges") + response = run_graphql_field("User.public_proposals", user) - expect(received_proposals).to eq [] + expect(response.items).to eq [] end it "does not link comments" do create(:comment, author: user) - response = execute("{ user(id: #{user.id}) { public_comments { edges { node { body } } } } }") - received_comments = dig(response, "data.user.public_comments.edges") + response = run_graphql_field("User.public_comments", user) - expect(received_comments).to eq [] + expect(response.items).to eq [] end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2ee4b259b..c7d2e2079 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -21,6 +21,7 @@ RSpec.configure do |config| config.include(EmailSpec::Matchers) config.include(CommonActions) config.include(ActiveSupport::Testing::TimeHelpers) + config.include GraphQL::Testing::Helpers.for(ConsulSchema) config.define_derived_metadata(file_path: Regexp.new("/spec/components/")) do |metadata| metadata[:type] = :component diff --git a/spec/support/common_actions/graphql_api.rb b/spec/support/common_actions/graphql_api.rb index 452433f87..aa40350a6 100644 --- a/spec/support/common_actions/graphql_api.rb +++ b/spec/support/common_actions/graphql_api.rb @@ -7,14 +7,6 @@ module GraphQLAPI response.dig(*path.split(".")) end - def hidden_field?(response, field_name) - data_is_empty = response["data"].nil? - error_message = /Field '#{field_name}' doesn't exist on type '[[:alnum:]]*'/ - - error_is_present = ((response["errors"].first["message"] =~ error_message) == 0) - data_is_empty && error_is_present - end - def extract_fields(response, collection_name, field_chain) fields = field_chain.split(".") dig(response, "data.#{collection_name}.edges").map do |node|