diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb index 5b1c27d16..877a3a451 100644 --- a/app/controllers/graphql_controller.rb +++ b/app/controllers/graphql_controller.rb @@ -6,52 +6,56 @@ class GraphqlController < ApplicationController skip_before_action :verify_authenticity_token skip_authorization_check - class QueryStringError < StandardError - end + class QueryStringError < StandardError; end - def query + def execute begin - if query_string.nil? then raise GraphqlController::QueryStringError end + raise GraphqlController::QueryStringError if query_string.nil? - response = consul_schema.execute query_string, variables: query_variables - render json: response, status: :ok + result = ConsulSchema.execute(query_string, + variables: prepare_variables, + context: {}, + operation_name: params[:operationName] + ) + render json: result rescue GraphqlController::QueryStringError render json: { message: "Query string not present" }, status: :bad_request rescue JSON::ParserError render json: { message: "Error parsing JSON" }, status: :bad_request rescue GraphQL::ParseError render json: { message: "Query string is not valid JSON" }, status: :bad_request - rescue - unless Rails.env.production? then raise end + rescue ArgumentError => e + render json: { message: e.message }, status: :bad_request end end private - def consul_schema - api_types = GraphQL::ApiTypesCreator.create - query_type = GraphQL::QueryTypeCreator.create(api_types) - - GraphQL::Schema.define do - query query_type - max_depth 8 - max_complexity 2500 - end - end - def query_string if request.headers["CONTENT_TYPE"] == "application/graphql" - request.body.string # request.body.class => StringIO + request.body.string else params[:query] end end - def query_variables - if params[:variables].blank? || params[:variables] == "null" + # Handle variables in URL query string and JSON body + def prepare_variables + case variables_param = params[:variables] + # URL query string + when String + if variables_param.present? + JSON.parse(variables_param) || {} + else + {} + end + # JSON object in request body gets converted to ActionController::Parameters + when ActionController::Parameters + variables_param.to_unsafe_hash # GraphQL-Ruby will validate name and type of incoming variables. + when nil {} else - JSON.parse(params[:variables]) + raise ArgumentError, "Unexpected parameter: #{variables_param}" end end end diff --git a/app/graphql/consul_schema.rb b/app/graphql/consul_schema.rb new file mode 100644 index 000000000..13faaaeae --- /dev/null +++ b/app/graphql/consul_schema.rb @@ -0,0 +1,11 @@ +class ConsulSchema < GraphQL::Schema + mutation(Types::MutationType) + query(Types::QueryType) + + # Opt in to the new runtime (default in future graphql-ruby versions) + use GraphQL::Execution::Interpreter + use GraphQL::Analysis::AST + + # Add built-in connections for pagination + use GraphQL::Pagination::Connections +end diff --git a/app/graphql/mutations/base_mutation.rb b/app/graphql/mutations/base_mutation.rb new file mode 100644 index 000000000..e57726c73 --- /dev/null +++ b/app/graphql/mutations/base_mutation.rb @@ -0,0 +1,5 @@ +module Mutations + class BaseMutation < GraphQL::Schema::RelayClassicMutation + object_class Types::BaseObject + end +end diff --git a/app/graphql/types/base_object.rb b/app/graphql/types/base_object.rb new file mode 100644 index 000000000..a86f4ff40 --- /dev/null +++ b/app/graphql/types/base_object.rb @@ -0,0 +1,29 @@ +module Types + class BaseObject < GraphQL::Schema::Object + def self.field(*args, **kwargs, &block) + super(*args, **kwargs, &block) + + # The old api contained non-camelized fields + # We want to support these for now, but throw a deprecation warning + # + # Example: + # proposal_notifications => Deprecation warning (Old api) + # proposalNotifications => No deprecation warning (New api) + field_name = args[0] + + if field_name.to_s.include?("_") + reason = "Snake case fields are deprecated. Please use #{field_name.to_s.camelize(:lower)}." + kwargs = kwargs.merge({ camelize: false, deprecation_reason: reason }) + super(*args, **kwargs, &block) + end + + # Make sure associations only return public records + # by automatically calling 'public_for_api' + type_class = args[1] + + if type_class.is_a?(Class) && type_class.ancestors.include?(GraphQL::Types::Relay::BaseConnection) + define_method(field_name) { object.send(field_name).public_for_api } + end + end + end +end diff --git a/app/graphql/types/comment_type.rb b/app/graphql/types/comment_type.rb new file mode 100644 index 000000000..f022d1f0c --- /dev/null +++ b/app/graphql/types/comment_type.rb @@ -0,0 +1,16 @@ +module Types + class CommentType < Types::BaseObject + field :ancestry, String, null: true + field :body, String, null: true + field :cached_votes_down, Integer, null: true + field :cached_votes_total, Integer, null: true + field :cached_votes_up, Integer, null: true + field :commentable_id, Integer, null: true + field :commentable_type, String, null: true + field :confidence_score, Integer, null: false + field :id, ID, null: false + field :public_author, Types::UserType, null: true + field :public_created_at, String, null: true + field :votes_for, Types::VoteType.connection_type, null: true + end +end diff --git a/app/graphql/types/debate_type.rb b/app/graphql/types/debate_type.rb new file mode 100644 index 000000000..a5edaf313 --- /dev/null +++ b/app/graphql/types/debate_type.rb @@ -0,0 +1,22 @@ +module Types + class DebateType < Types::BaseObject + field :cached_votes_down, Integer, null: true + field :cached_votes_total, Integer, null: true + field :cached_votes_up, Integer, null: true + field :comments, Types::CommentType.connection_type, null: true + field :comments_count, Integer, null: true + field :confidence_score, Integer, null: true + field :description, String, null: true + field :hot_score, Integer, null: true + field :id, ID, null: false + field :public_author, Types::UserType, null: true + field :public_created_at, String, null: true + field :tags, Types::TagType.connection_type, null: true + field :title, String, null: true + field :votes_for, Types::VoteType.connection_type, null: true + + def tags + object.tags.public_for_api + end + end +end diff --git a/app/graphql/types/geozone_type.rb b/app/graphql/types/geozone_type.rb new file mode 100644 index 000000000..3f63621fd --- /dev/null +++ b/app/graphql/types/geozone_type.rb @@ -0,0 +1,6 @@ +module Types + class GeozoneType < Types::BaseObject + field :id, ID, null: false + field :name, String, null: true + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb new file mode 100644 index 000000000..113861978 --- /dev/null +++ b/app/graphql/types/mutation_type.rb @@ -0,0 +1,4 @@ +module Types + class MutationType < Types::BaseObject + end +end diff --git a/app/graphql/types/proposal_notification_type.rb b/app/graphql/types/proposal_notification_type.rb new file mode 100644 index 000000000..cd4e4afb3 --- /dev/null +++ b/app/graphql/types/proposal_notification_type.rb @@ -0,0 +1,14 @@ +module Types + class ProposalNotificationType < Types::BaseObject + field :body, String, null: true + field :id, ID, null: false + field :proposal, Types::ProposalType, null: true + field :proposal_id, Integer, null: true + field :public_created_at, String, null: true + field :title, String, null: true + + def proposal + Proposal.public_for_api.find_by(id: object.proposal) + end + end +end diff --git a/app/graphql/types/proposal_type.rb b/app/graphql/types/proposal_type.rb new file mode 100644 index 000000000..40b39cd01 --- /dev/null +++ b/app/graphql/types/proposal_type.rb @@ -0,0 +1,32 @@ +module Types + class ProposalType < Types::BaseObject + field :cached_votes_up, Integer, null: true + field :comments, Types::CommentType.connection_type, null: true + field :comments_count, Integer, null: true + field :confidence_score, Integer, null: true + field :description, String, null: true + field :geozone, Types::GeozoneType, null: true + field :geozone_id, Integer, null: true + field :hot_score, Integer, null: true + field :id, ID, null: false + field :proposal_notifications, Types::ProposalNotificationType.connection_type, null: true + field :public_author, Types::UserType, null: true + field :public_created_at, String, null: true + field :retired_at, GraphQL::Types::ISO8601DateTime, null: true + field :retired_explanation, String, null: true + field :retired_reason, String, null: true + field :summary, String, null: true + field :tags, Types::TagType.connection_type, null: true + field :title, String, null: true + field :video_url, String, null: true + field :votes_for, Types::VoteType.connection_type, null: true + + def tags + object.tags.public_for_api + end + + def geozone + Geozone.public_for_api.find_by(id: object.geozone) + end + end +end diff --git a/app/graphql/types/query_type.rb b/app/graphql/types/query_type.rb new file mode 100644 index 000000000..06d855355 --- /dev/null +++ b/app/graphql/types/query_type.rb @@ -0,0 +1,107 @@ +module Types + class QueryType < Types::BaseObject + field :comments, Types::CommentType.connection_type, "Returns all comments", null: false + field :comment, Types::CommentType, "Returns comment for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :debates, Types::DebateType.connection_type, "Returns all debates", null: false + field :debate, Types::DebateType, "Returns debate for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :geozones, Types::GeozoneType.connection_type, "Returns all geozones", null: false + field :geozone, Types::GeozoneType, "Returns geozone for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :proposals, Types::ProposalType.connection_type, "Returns all proposals", null: false + field :proposal, Types::ProposalType, "Returns proposal for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :proposal_notifications, Types::ProposalNotificationType.connection_type, "Returns all proposal notifications", null: false + field :proposal_notification, Types::ProposalNotificationType, "Returns proposal notification for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :tags, Types::TagType.connection_type, "Returns all tags", null: false + field :tag, Types::TagType, "Returns tag for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :users, Types::UserType.connection_type, "Returns all users", null: false + field :user, Types::UserType, "Returns user for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + field :votes, Types::VoteType.connection_type, "Returns all votes", null: false + field :vote, Types::VoteType, "Returns vote for ID", null: false do + argument :id, ID, required: true, default_value: false + end + + def comments + Comment.public_for_api + end + + def comment(id:) + Comment.find(id) + end + + def debates + Debate.public_for_api + end + + def debate(id:) + Debate.find(id) + end + + def geozones + Geozone.public_for_api + end + + def geozone(id:) + Geozone.find(id) + end + + def proposals + Proposal.public_for_api + end + + def proposal(id:) + Proposal.find(id) + end + + def proposal_notifications + ProposalNotification.public_for_api + end + + def proposal_notification(id:) + ProposalNotification.find(id) + end + + def tags + Tag.public_for_api + end + + def tag(id:) + Tag.find(id) + end + + def users + User.public_for_api + end + + def user(id:) + User.find(id) + end + + def votes + Vote.public_for_api + end + + def vote(id:) + Vote.find(id) + end + end +end diff --git a/app/graphql/types/tag_type.rb b/app/graphql/types/tag_type.rb new file mode 100644 index 000000000..3ad42f189 --- /dev/null +++ b/app/graphql/types/tag_type.rb @@ -0,0 +1,8 @@ +module Types + class TagType < Types::BaseObject + field :id, ID, null: false + field :kind, String, null: true + field :name, String, null: true + field :taggings_count, Integer, null: true + end +end diff --git a/app/graphql/types/user_type.rb b/app/graphql/types/user_type.rb new file mode 100644 index 000000000..57713ed92 --- /dev/null +++ b/app/graphql/types/user_type.rb @@ -0,0 +1,9 @@ +module Types + class UserType < Types::BaseObject + field :id, ID, null: false + field :public_comments, Types::CommentType.connection_type, null: true + field :public_debates, Types::DebateType.connection_type, null: true + field :public_proposals, Types::ProposalType.connection_type, null: true + field :username, String, null: true + end +end diff --git a/app/graphql/types/vote_type.rb b/app/graphql/types/vote_type.rb new file mode 100644 index 000000000..16b0d7f50 --- /dev/null +++ b/app/graphql/types/vote_type.rb @@ -0,0 +1,9 @@ +module Types + class VoteType < Types::BaseObject + field :id, ID, null: false + field :public_created_at, String, null: true + field :votable_id, Integer, null: true + field :votable_type, String, null: true + field :vote_flag, Boolean, null: true + end +end diff --git a/app/models/concerns/graphqlable.rb b/app/models/concerns/graphqlable.rb index 2284838da..7cbb06199 100644 --- a/app/models/concerns/graphqlable.rb +++ b/app/models/concerns/graphqlable.rb @@ -1,32 +1,6 @@ module Graphqlable extend ActiveSupport::Concern - class_methods do - def graphql_field_name - name.gsub("::", "_").underscore.to_sym - end - - def graphql_field_description - "Find one #{model_name.human} by ID" - end - - def graphql_pluralized_field_name - name.gsub("::", "_").underscore.pluralize.to_sym - end - - def graphql_pluralized_field_description - "Find all #{model_name.human.pluralize}" - end - - def graphql_type_name - name.gsub("::", "_") - end - - def graphql_type_description - model_name.human.to_s - end - end - def public_created_at created_at.change(min: 0) end diff --git a/app/models/concerns/has_public_author.rb b/app/models/concerns/has_public_author.rb index 5132c33c9..edd1e45bb 100644 --- a/app/models/concerns/has_public_author.rb +++ b/app/models/concerns/has_public_author.rb @@ -1,5 +1,5 @@ module HasPublicAuthor def public_author - author.public_activity? ? author : nil + author.public_activity? ? User.public_for_api.find_by(id: author) : nil end end diff --git a/config/api.yml b/config/api.yml deleted file mode 100644 index 6e47c090d..000000000 --- a/config/api.yml +++ /dev/null @@ -1,82 +0,0 @@ -User: - fields: - id: integer - username: string - public_debates: [Debate] - public_proposals: [Proposal] - public_comments: [Comment] -Debate: - fields: - id: integer - title: string - description: string - public_created_at: string - cached_votes_total: integer - cached_votes_up: integer - cached_votes_down: integer - comments_count: integer - hot_score: integer - confidence_score: integer - comments: [Comment] - public_author: User - votes_for: [Vote] - tags: [Tag] -Proposal: - fields: - id: integer - title: string - description: string - cached_votes_up: integer - comments_count: integer - hot_score: integer - confidence_score: integer - public_created_at: string - summary: string - video_url: string - geozone_id: integer - retired_at: string - retired_reason: string - retired_explanation: string - geozone: Geozone - comments: [Comment] - proposal_notifications: [ProposalNotification] - public_author: User - votes_for: [Vote] - tags: [Tag] -Comment: - fields: - id: integer - commentable_id: integer - commentable_type: string - body: string - public_created_at: string - cached_votes_total: integer - cached_votes_up: integer - cached_votes_down: integer - ancestry: string - confidence_score: integer - public_author: User - votes_for: [Vote] -Geozone: - fields: - id: integer - name: string -ProposalNotification: - fields: - title: string - body: string - proposal_id: integer - public_created_at: string - proposal: Proposal -Tag: - fields: - id: integer - name: string - taggings_count: integer - kind: string -Vote: - fields: - votable_id: integer - votable_type: string - public_created_at: string - vote_flag: boolean diff --git a/config/initializers/acts_as_taggable_on.rb b/config/initializers/acts_as_taggable_on.rb index d9ec982a4..a3ebeb853 100644 --- a/config/initializers/acts_as_taggable_on.rb +++ b/config/initializers/acts_as_taggable_on.rb @@ -64,18 +64,6 @@ module ActsAsTaggableOn Tag.category.pluck(:name) end - def self.graphql_field_name - :tag - end - - def self.graphql_pluralized_field_name - :tags - end - - def self.graphql_type_name - "Tag" - end - private def custom_counter_field_name_for(taggable_type) diff --git a/config/routes/graphql.rb b/config/routes/graphql.rb index 83e4594fc..c62d6ead2 100644 --- a/config/routes/graphql.rb +++ b/config/routes/graphql.rb @@ -1,3 +1,3 @@ -get "/graphql", to: "graphql#query" -post "/graphql", to: "graphql#query" +post "/graphql", to: "graphql#execute" +get "/graphql", to: "graphql#execute" mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" diff --git a/lib/graph_ql/api_types_creator.rb b/lib/graph_ql/api_types_creator.rb deleted file mode 100644 index 3fbb3d704..000000000 --- a/lib/graph_ql/api_types_creator.rb +++ /dev/null @@ -1,85 +0,0 @@ -require "graphql" - -module GraphQL - class ApiTypesCreator - SCALAR_TYPES = { - integer: GraphQL::INT_TYPE, - boolean: GraphQL::BOOLEAN_TYPE, - float: GraphQL::FLOAT_TYPE, - double: GraphQL::FLOAT_TYPE, - string: GraphQL::STRING_TYPE - }.freeze - - def self.create - created_types = {} - api_types_definitions.each do |model, info| - create_type(model, info[:fields], created_types) - end - created_types - end - - def self.api_types_definitions - @api_types_definitions ||= parse_api_config_file(YAML.load_file(Rails.root.join("config/api.yml"))) - end - - def self.type_kind(type) - if SCALAR_TYPES[type] - :scalar - elsif type.class == Class - :singular_association - elsif type.class == Array - :multiple_association - end - end - - def self.create_type(model, fields, created_types) - created_types[model] = GraphQL::ObjectType.define do - name model.graphql_type_name - description model.graphql_type_description - - # Make a field for each column, association or method - fields.each do |field_name, field_type| - case ApiTypesCreator.type_kind(field_type) - when :scalar - field(field_name, SCALAR_TYPES[field_type], model.human_attribute_name(field_name)) - when :singular_association - field(field_name, -> { created_types[field_type] }) do - resolve ->(object, arguments, context) do - association_target = object.send(field_name) - association_target.present? ? field_type.public_for_api.find_by(id: association_target.id) : nil - end - end - when :multiple_association - field_type = field_type.first - connection(field_name, -> { created_types[field_type].connection_type }, max_page_size: 50, complexity: 1000) do - resolve ->(object, arguments, context) { object.send(field_name).public_for_api } - end - end - end - end - end - - def self.parse_api_config_file(file) - api_type_definitions = {} - - file.each do |api_type_model, api_type_info| - model = api_type_model.constantize - fields = {} - - api_type_info["fields"].each do |field_name, field_type| - if field_type.is_a?(Array) # paginated association - fields[field_name.to_sym] = [field_type.first.constantize] - elsif SCALAR_TYPES[field_type.to_sym] - fields[field_name.to_sym] = field_type.to_sym - else # simple association - fields[field_name.to_sym] = field_type.constantize - end - end - - api_type_definitions[model] = { fields: fields } - end - - api_type_definitions - end - end -end diff --git a/lib/graph_ql/query_type_creator.rb b/lib/graph_ql/query_type_creator.rb deleted file mode 100644 index 35c2b4d2c..000000000 --- a/lib/graph_ql/query_type_creator.rb +++ /dev/null @@ -1,28 +0,0 @@ -require "graphql" - -module GraphQL - class QueryTypeCreator - def self.create(api_types) - GraphQL::ObjectType.define do - name "QueryType" - description "The root query for the schema" - - api_types.each do |model, created_type| - if created_type.fields["id"] - field model.graphql_field_name do - type created_type - description model.graphql_field_description - argument :id, !types.ID - resolve ->(object, arguments, context) { model.public_for_api.find_by(id: arguments["id"]) } - end - end - - connection(model.graphql_pluralized_field_name, created_type.connection_type, max_page_size: 50, complexity: 1000) do - description model.graphql_pluralized_field_description - resolve ->(object, arguments, context) { model.public_for_api } - end - end - end - end - end -end diff --git a/spec/lib/graph_ql/api_types_creator_spec.rb b/spec/lib/graph_ql/api_types_creator_spec.rb deleted file mode 100644 index 019254aa5..000000000 --- a/spec/lib/graph_ql/api_types_creator_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -require "rails_helper" - -describe GraphQL::ApiTypesCreator do - let(:created_types) { {} } - - describe "::create_type" do - it "creates fields for Int attributes" do - debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { id: :integer }, created_types) - created_field = debate_type.fields["id"] - - expect(created_field).to be_a(GraphQL::Field) - expect(created_field.type).to be_a(GraphQL::ScalarType) - expect(created_field.type.name).to eq("Int") - end - - it "creates fields for String attributes" do - debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { title: :string }, created_types) - created_field = debate_type.fields["title"] - - expect(created_field).to be_a(GraphQL::Field) - expect(created_field.type).to be_a(GraphQL::ScalarType) - expect(created_field.type.name).to eq("String") - end - - it "creates connections for :belongs_to associations" do - user_type = GraphQL::ApiTypesCreator.create_type(User, { id: :integer }, created_types) - debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { author: User }, created_types) - - connection = debate_type.fields["author"] - - expect(connection).to be_a(GraphQL::Field) - expect(connection.type).to eq(user_type) - expect(connection.name).to eq("author") - end - - it "creates connections for :has_one associations" do - user_type = GraphQL::ApiTypesCreator.create_type(User, { organization: Organization }, created_types) - organization_type = GraphQL::ApiTypesCreator.create_type(Organization, { id: :integer }, created_types) - - connection = user_type.fields["organization"] - - expect(connection).to be_a(GraphQL::Field) - expect(connection.type).to eq(organization_type) - expect(connection.name).to eq("organization") - end - - it "creates connections for :has_many associations" do - comment_type = GraphQL::ApiTypesCreator.create_type(Comment, { id: :integer }, created_types) - debate_type = GraphQL::ApiTypesCreator.create_type(Debate, { comments: [Comment] }, created_types) - - connection = debate_type.fields["comments"] - - expect(connection).to be_a(GraphQL::Field) - expect(connection.type).to eq(comment_type.connection_type) - expect(connection.name).to eq("comments") - end - end -end diff --git a/spec/lib/graph_ql/query_type_creator_spec.rb b/spec/lib/graph_ql/query_type_creator_spec.rb deleted file mode 100644 index 36d671c5c..000000000 --- a/spec/lib/graph_ql/query_type_creator_spec.rb +++ /dev/null @@ -1,37 +0,0 @@ -require "rails_helper" - -describe GraphQL::QueryTypeCreator do - before do - allow(GraphQL::ApiTypesCreator).to receive(:api_types_definitions).and_return( - { - ProposalNotification => { fields: { title: :string }}, - Proposal => { fields: { id: :integer, title: :string }} - } - ) - end - let(:api_types) { GraphQL::ApiTypesCreator.create } - - describe "::create" do - let(:query_type) { GraphQL::QueryTypeCreator.create(api_types) } - - it "creates a QueryType with fields to retrieve single objects whose model fields included an ID" do - field = query_type.fields["proposal"] - - expect(field).to be_a(GraphQL::Field) - expect(field.type).to eq(api_types[Proposal]) - expect(field.name).to eq("proposal") - end - - it "creates a QueryType without fields to retrieve single objects whose model fields did not include an ID" do - expect(query_type.fields["proposal_notification"]).to be_nil - end - - it "creates a QueryType with connections to retrieve collections of objects" do - connection = query_type.fields["proposals"] - - expect(connection).to be_a(GraphQL::Field) - expect(connection.type).to eq(api_types[Proposal].connection_type) - expect(connection.name).to eq("proposals") - end - end -end diff --git a/spec/lib/graphql_spec.rb b/spec/lib/graphql_spec.rb index 0675c0a66..de8b80c61 100644 --- a/spec/lib/graphql_spec.rb +++ b/spec/lib/graphql_spec.rb @@ -1,12 +1,5 @@ require "rails_helper" -api_types = GraphQL::ApiTypesCreator.create -query_type = GraphQL::QueryTypeCreator.create(api_types) -ConsulSchema = GraphQL::Schema.define do - query query_type - max_depth 12 -end - def execute(query_string, context = {}, variables = {}) ConsulSchema.execute(query_string, context: context, variables: variables) end @@ -40,8 +33,8 @@ describe "Consul Schema" do let(:proposal) { create(:proposal, author: user) } it "returns fields of Int type" do - response = execute("{ proposal(id: #{proposal.id}) { id } }") - expect(dig(response, "data.proposal.id")).to eq(proposal.id) + response = execute("{ proposal(id: #{proposal.id}) { cached_votes_up } }") + expect(dig(response, "data.proposal.cached_votes_up")).to eq(proposal.cached_votes_up) end it "returns fields of String type" do @@ -373,6 +366,17 @@ describe "Consul Schema" do expect(received_comments).not_to include(not_public_poll_comment.body) end + it "only links public comments" do + user = create(:administrator).user + create(:comment, author: user, body: "Public") + create(:budget_investment_comment, author: user, valuation: true, body: "Valuation") + + response = execute("{ user(id: #{user.id}) { public_comments { edges { node { body } } } } }") + received_comments = dig(response, "data.user.public_comments.edges") + + expect(received_comments).to eq [{ "node" => { "body" => "Public" }}] + end + it "only returns date and hour for created_at" do created_at = Time.zone.parse("2017-12-31 9:30:15") create(:comment, created_at: created_at)