From aef20aadf73ef438d93d7c8446af2d364be3adfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Miedes=20Garc=C3=A9s?= Date: Wed, 30 Nov 2016 13:44:31 +0100 Subject: [PATCH] Refactor specs for TypeCreator and ConsulSchema --- config/initializers/graphql.rb | 5 +- spec/lib/graphql_spec.rb | 283 ++++++++++++++++++--------------- spec/lib/type_creator_spec.rb | 24 ++- 3 files changed, 174 insertions(+), 138 deletions(-) diff --git a/config/initializers/graphql.rb b/config/initializers/graphql.rb index 3918d794a..306c01890 100644 --- a/config/initializers/graphql.rb +++ b/config/initializers/graphql.rb @@ -1,8 +1,9 @@ API_TYPE_DEFINITIONS = { - User => %I[ id username proposals ], + User => %I[ id username proposals organization ], Debate => %I[ id title description author_id author created_at comments ], Proposal => %I[ id title description author_id author created_at comments ], - Comment => %I[ id body user_id user commentable_id ] + Comment => %I[ id body user_id user commentable_id ], + Organization => %I[ id name ] } type_creator = GraphQL::TypeCreator.new diff --git a/spec/lib/graphql_spec.rb b/spec/lib/graphql_spec.rb index 8e2679f8f..5fac92e28 100644 --- a/spec/lib/graphql_spec.rb +++ b/spec/lib/graphql_spec.rb @@ -1,132 +1,157 @@ require 'rails_helper' -describe ConsulSchema do - let(:context) { {} } # should be overriden for specific queries - let(:variables) { {} } - let(:result) { ConsulSchema.execute(query_string, context: context, variables: variables) } - - describe "queries to single elements" do - let(:proposal) { create(:proposal) } - subject(:returned_proposal) { result['data']['proposal'] } - - describe "return fields of Int type" do - let(:query_string) { "{ proposal(id: #{proposal.id}) { id } }" } - specify { expect(returned_proposal['id']).to eq(proposal.id) } - end - - describe "return fields of String type" do - let(:query_string) { "{ proposal(id: #{proposal.id}) { title } }" } - specify { expect(returned_proposal['title']).to eq(proposal.title) } - end - - describe "support nested" do - let(:proposal_author) { create(:user) } - let(:comments_author) { create(:user) } - let(:proposal) { create(:proposal, author: proposal_author) } - let!(:comment_1) { create(:comment, author: comments_author, commentable: proposal) } - let!(:comment_2) { create(:comment, author: comments_author, commentable: proposal) } - let(:query_string) { "{ proposal(id: #{proposal.id}) { author { username }, comments { edges { node { body } } } } }" } - - it ":has_one associations" do - skip "I think this test isn't needed" - # TODO: the only has_one associations inside the project are in the User - # model (administrator, valuator, etc.). But since I think this data - # shouldn't be exposed to the API, there's no point in testing this. - end - - it ":belongs_to associations" do - expect(returned_proposal['author']['username']).to eq(proposal_author.username) - end - - it ":has_many associations" do - comments = returned_proposal['comments']['edges'].collect { |edge| edge['node'] } - comment_bodies = comments.collect { |comment| comment['body'] } - - expect(comment_bodies).to match_array([comment_1.body, comment_2.body]) - end - end - - describe "do not expose confidential" do - let(:user) { create(:user) } - subject(:data) { result['data'] } - subject(:errors) { result['errors'] } - subject(:error_msg) { errors.first['message'] } - - describe "fields of Int type" do - let(:query_string) { "{ user(id: #{user.id}) { failed_census_calls_count } }" } - - specify { expect(data).to be_nil } - specify { expect(error_msg).to eq("Field 'failed_census_calls_count' doesn't exist on type 'User'") } - end - - describe "fields of String type" do - let(:query_string) { "{ user(id: #{user.id}) { encrypted_password } }" } - - specify { expect(data).to be_nil } - specify { expect(error_msg).to eq("Field 'encrypted_password' doesn't exist on type 'User'") } - end - - describe "fields inside nested queries" do - let(:proposal) { create(:proposal, author: user) } - let(:query_string) { "{ proposal(id: #{proposal.id}) { author { reset_password_sent_at } } }" } - - specify { expect(data).to be_nil } - specify { expect(error_msg).to eq("Field 'reset_password_sent_at' doesn't exist on type 'User'") } - end - - describe ":has_one associations" do - let(:administrator) { create(:administrator) } - let(:query_string) { "{ user(id: #{user.id}) { administrator { id } } }" } - - specify { expect(data).to be_nil } - specify { expect(error_msg).to eq("Field 'administrator' doesn't exist on type 'User'") } - end - - describe ":belongs_to associations" do - let(:query_string) { "{ user(id: #{user.id}) { failed_census_calls { id } } }" } - let(:census_call) { create(:failed_census_call, user: user) } - - specify { expect(data).to be_nil } - specify { expect(error_msg).to eq("Field 'failed_census_calls' doesn't exist on type 'User'") } - end - - describe ":has_many associations" do - let(:message) { create(:direct_message, sender: user) } - let(:query_string) { "{ user(id: #{user.id}) { direct_messages_sent { id } } }" } - - specify { expect(data).to be_nil } - specify { expect(error_msg).to eq("Field 'direct_messages_sent' doesn't exist on type 'User'") } - end - end - end - - describe "queries to collections" do - let(:mrajoy) { create(:user, username: 'mrajoy') } - let(:dtrump) { create(:user, username: 'dtrump') } - let!(:proposal_1) { create(:proposal, id: 1, title: "Bajar el IVA", author: mrajoy) } - let!(:proposal_2) { create(:proposal, id: 2, title: "Censurar los memes", author: mrajoy) } - let!(:proposal_3) { create(:proposal, id: 3, title: "Construir un muro", author: dtrump) } - subject(:returned_proposals) { result['data']['proposals']["edges"].collect { |edge| edge['node'] } } - - describe "return fields of Int type" do - let(:query_string) { "{ proposals { edges { node { id } } } }" } - let(:ids) { returned_proposals.collect { |proposal| proposal['id'] } } - - specify { expect(ids).to match_array([3, 1, 2]) } - end - - describe "return fields of String type" do - let(:query_string) { "{ proposals { edges { node { title } } } }" } - let(:titles) { returned_proposals.collect { |proposal| proposal['title'] } } - - specify { expect(titles).to match_array(['Construir un muro', 'Censurar los memes', 'Bajar el IVA']) } - end - - describe "return nested fields" do - let(:query_string) { "{ proposals { edges { node { author { username } } } } }" } - let(:authors) { returned_proposals.collect { |proposal| proposal['author']['username'] } } - - specify { expect(authors).to match_array(['mrajoy', 'dtrump', 'mrajoy']) } - end - end +# TODO: uno por cada tipo escalar, uno por cada asociacion, uno con query anidada * 2 (uno para asegurarse de que se muestra y otro para cuando se oculta) +def execute(query_string, context = {}, variables = {}) + ConsulSchema.execute(query_string, context: context, variables: variables) +end + +def dig(response, path) + response.dig(*path.split('.')) +end + +describe ConsulSchema do + let(:proposal_author) { create(:user) } + let(:proposal) { create(:proposal, author: proposal_author) } + + it "returns fields of Int type" do + response = execute("{ proposal(id: #{proposal.id}) { id } }") + expect(dig(response, 'data.proposal.id')).to eq(proposal.id) + 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) + end + + it "returns has_one associations" do + organization = create(:organization) + response = execute("{ user(id: #{organization.user_id}) { organization { name } } }") + expect(dig(response, 'data.user.organization.name')).to eq(organization.name) + end + + it "returns belongs_to associations" do + response = execute("{ proposal(id: #{proposal.id}) { author { username } } }") + expect(dig(response, 'data.proposal.author.username')).to eq(proposal.author.username) + end + + it "returns has_many associations" do + comments_author = create(:user) + comment_1 = create(:comment, author: comments_author, commentable: proposal) + comment_2 = create(:comment, author: comments_author, commentable: proposal) + + response = execute("{ proposal(id: #{proposal.id}) { comments { edges { node { body } } } } }") + comments = dig(response, 'data.proposal.comments.edges').collect { |edge| edge['node'] } + comment_bodies = comments.collect { |comment| comment['body'] } + + expect(comment_bodies).to match_array([comment_1.body, comment_2.body]) + end + + # it "hides confidential fields of Int type" do + # response = execute("{ user(id: #{user.id}) { failed_census_calls_count } }") + # expect(response['data']).to be_nil + # expect(response[]) + # end + # + # it "hides confidential fields of String type" do + # skip + # response = execute("{ user(id: #{user.id}) { encrypted_password } }") + # end + # + # it "hides confidential has_one associations" do + # skip + # response = execute("{ user(id: #{user.id}) { administrator { id } } }") + # end + # + # it "hides confidential belongs_to associations" do + # skip + # response = execute("{ user(id: #{user.id}) { failed_census_calls { id } } }") + # end + # + # it "hides confidential has_many associations" do + # skip + # response = execute("{ user(id: #{user.id}) { direct_messages_sent { id } } }") + # end + # + # describe "do not expose confidential" do + # let(:user) { create(:user) } + # subject(:data) { response['data'] } + # subject(:errors) { response['errors'] } + # subject(:error_msg) { errors.first['message'] } + # + # describe "fields of Int type" do + # let(:query_string) { "{ user(id: #{user.id}) { failed_census_calls_count } }" } + # + # specify { expect(data).to be_nil } + # specify { expect(error_msg).to eq("Field 'failed_census_calls_count' doesn't exist on type 'User'") } + # end + # + # describe "fields of String type" do + # let(:query_string) { "{ user(id: #{user.id}) { encrypted_password } }" } + # + # specify { expect(data).to be_nil } + # specify { expect(error_msg).to eq("Field 'encrypted_password' doesn't exist on type 'User'") } + # end + # + # describe "fields inside nested queries" do + # let(:proposal) { create(:proposal, author: user) } + # let(:query_string) { "{ proposal(id: #{proposal.id}) { author { reset_password_sent_at } } }" } + # + # specify { expect(data).to be_nil } + # specify { expect(error_msg).to eq("Field 'reset_password_sent_at' doesn't exist on type 'User'") } + # end + # + # describe ":has_one associations" do + # let(:administrator) { create(:administrator) } + # let(:query_string) { "{ user(id: #{user.id}) { administrator { id } } }" } + # + # specify { expect(data).to be_nil } + # specify { expect(error_msg).to eq("Field 'administrator' doesn't exist on type 'User'") } + # end + # + # describe ":belongs_to associations" do + # let(:query_string) { "{ user(id: #{user.id}) { failed_census_calls { id } } }" } + # let(:census_call) { create(:failed_census_call, user: user) } + # + # specify { expect(data).to be_nil } + # specify { expect(error_msg).to eq("Field 'failed_census_calls' doesn't exist on type 'User'") } + # end + # + # describe ":has_many associations" do + # let(:message) { create(:direct_message, sender: user) } + # let(:query_string) { "{ user(id: #{user.id}) { direct_messages_sent { id } } }" } + # + # specify { expect(data).to be_nil } + # specify { expect(error_msg).to eq("Field 'direct_messages_sent' doesn't exist on type 'User'") } + # end + # end + # + # describe "queries to collections" do + # let(:mrajoy) { create(:user, username: 'mrajoy') } + # let(:dtrump) { create(:user, username: 'dtrump') } + # let!(:proposal_1) { create(:proposal, id: 1, title: "Bajar el IVA", author: mrajoy) } + # let!(:proposal_2) { create(:proposal, id: 2, title: "Censurar los memes", author: mrajoy) } + # let!(:proposal_3) { create(:proposal, id: 3, title: "Construir un muro", author: dtrump) } + # subject(:returned_proposals) { response['data']['proposals']["edges"].collect { |edge| edge['node'] } } + # + # describe "return fields of Int type" do + # let(:query_string) { "{ proposals { edges { node { id } } } }" } + # let(:ids) { returned_proposals.collect { |proposal| proposal['id'] } } + # + # specify { expect(ids).to match_array([3, 1, 2]) } + # end + # + # describe "return fields of String type" do + # let(:query_string) { "{ proposals { edges { node { title } } } }" } + # let(:titles) { returned_proposals.collect { |proposal| proposal['title'] } } + # + # specify { expect(titles).to match_array(['Construir un muro', 'Censurar los memes', 'Bajar el IVA']) } + # end + # + # describe "return nested fields" do + # let(:query_string) { "{ proposals { edges { node { author { username } } } } }" } + # let(:authors) { returned_proposals.collect { |proposal| proposal['author']['username'] } } + # + # specify { expect(authors).to match_array(['mrajoy', 'dtrump', 'mrajoy']) } + # end + # end end diff --git a/spec/lib/type_creator_spec.rb b/spec/lib/type_creator_spec.rb index 2369df348..4ad667812 100644 --- a/spec/lib/type_creator_spec.rb +++ b/spec/lib/type_creator_spec.rb @@ -1,10 +1,12 @@ require 'rails_helper' describe GraphQL::TypeCreator do - let!(:api_types) { {} } - let!(:user_type) { GraphQL::TypeCreator.create(User, %I[ id ], api_types) } - let!(:comment_type) { GraphQL::TypeCreator.create(Comment, %I[ id ], api_types) } - let!(:debate_type) { GraphQL::TypeCreator.create(Debate, %I[ id title author ], api_types) } + let(:type_creator) { GraphQL::TypeCreator.new } + + #let(:api_types) { {} } + #let!(:user_type) { GraphQL::TypeCreator.create(User, %I[ id ], api_types) } + #let!(:comment_type) { GraphQL::TypeCreator.create(Comment, %I[ id ], api_types) } + #let!(:debate_type) { GraphQL::TypeCreator.create(Debate, %I[ id title author ], api_types) } # TODO: no puedo añadir los comentarios a la field_list de Debate porque como # las conexiones se crean de forma lazy creo que provoca que falle la creación # del resto de tipos y provoca que fallen todos los tests. @@ -13,6 +15,7 @@ describe GraphQL::TypeCreator do describe "::create" do describe "creates fields" do it "for int attributes" do + debate_type = type_creator.create(Debate, %I[ id ]) created_field = debate_type.fields['id'] expect(created_field).to be_a(GraphQL::Field) @@ -21,6 +24,7 @@ describe GraphQL::TypeCreator do end it "for string attributes" do + skip created_field = debate_type.fields['title'] expect(created_field).to be_a(GraphQL::Field) @@ -31,6 +35,9 @@ describe GraphQL::TypeCreator do describe "creates connections for" do it ":belongs_to associations" do + user_type = type_creator.create(User, %I[ id ]) + debate_type = type_creator.create(Debate, %I[ author ]) + connection = debate_type.fields['author'] # TODO: because connection types are created and added lazily to the @@ -40,7 +47,8 @@ describe GraphQL::TypeCreator do # connection.inspect shows some weird info expect(connection).to be_a(GraphQL::Field) - # expect(connection.type).to be_a(api_types[User]) + #debugger + expect(connection.type).to eq(user_type) expect(connection.name).to eq('author') end @@ -49,7 +57,9 @@ describe GraphQL::TypeCreator do end it ":has_many associations" do - skip "still don't know how to handle relay connections inside RSpec" + #skip "still don't know how to handle relay connections inside RSpec" + comment_type = type_creator.create(Comment, %I[ id ]) + debate_type = type_creator.create(Debate, %I[ author ]) connection = debate_type.fields['comments'] @@ -60,7 +70,7 @@ describe GraphQL::TypeCreator do # connection.inspect shows some weird info expect(connection).to be_a(GraphQL::Field) - # expect(created_connection.type).to be_a(api_types[Comment]) + expect(connection.type).to be_a(api_types[Comment]) expect(connection.name).to eq('comments') end end