diff --git a/app/models/concerns/graphqlable.rb b/app/models/concerns/graphqlable.rb index b62d578081a..651a285b994 100644 --- a/app/models/concerns/graphqlable.rb +++ b/app/models/concerns/graphqlable.rb @@ -29,4 +29,8 @@ def graphql_type_description end + def public_created_at + self.created_at.change(min: 0) + end + end diff --git a/app/models/user.rb b/app/models/user.rb index 1c6d3d2eb15..f5a6586fd48 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -286,6 +286,18 @@ def ability end delegate :can?, :cannot?, to: :ability + def public_proposals + public_activity? ? proposals : [] + end + + def public_debates + public_activity? ? debates : [] + end + + def public_comments + public_activity? ? comments : [] + end + private def clean_document_number diff --git a/app/models/vote.rb b/app/models/vote.rb index eae53e9c341..49cc4cd86e6 100644 --- a/app/models/vote.rb +++ b/app/models/vote.rb @@ -1,15 +1,19 @@ class Vote < ActsAsVotable::Vote include Graphqlable - + def self.public_for_api joins("FULL OUTER JOIN debates ON votable_type = 'Debate' AND votable_id = debates.id"). joins("FULL OUTER JOIN proposals ON votable_type = 'Proposal' AND votable_id = proposals.id"). joins("FULL OUTER JOIN comments ON votable_type = 'Comment' AND votable_id = comments.id"). - where("votable_type = 'Proposal' AND proposals.hidden_at IS NULL OR votable_type = 'Debate' AND debates.hidden_at IS NULL OR votable_type = 'Comment' AND comments.hidden_at IS NULL") - end - - def public_timestamp - self.created_at.change(min: 0) + where("(votable_type = 'Proposal' AND proposals.hidden_at IS NULL) OR \ + (votable_type = 'Debate' AND debates.hidden_at IS NULL) OR \ + ( \ + (votable_type = 'Comment' AND comments.hidden_at IS NULL) AND \ + ( \ + (comments.commentable_type = 'Proposal' AND (comments.commentable_id IN (SELECT id FROM proposals WHERE hidden_at IS NULL GROUP BY id))) OR \ + (comments.commentable_type = 'Debate' AND (comments.commentable_id IN (SELECT id FROM debates WHERE hidden_at IS NULL GROUP BY id))) \ + ) \ + )") end end diff --git a/config/api.yml b/config/api.yml index d007202cb2a..0c668eac2ee 100644 --- a/config/api.yml +++ b/config/api.yml @@ -2,24 +2,22 @@ User: fields: id: integer username: string - debates: [Debate] - proposals: [Proposal] - comments: [Comment] - organization: Organization + public_debates: [Debate] + public_proposals: [Proposal] + public_comments: [Comment] +# organization: Organization Debate: fields: id: integer title: string description: string - created_at: 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 - geozone_id: integer - geozone: Geozone comments: [Comment] public_author: User votes_for: [Vote] @@ -34,7 +32,7 @@ Proposal: comments_count: integer hot_score: integer confidence_score: integer - created_at: string + public_created_at: string summary: string video_url: string geozone_id: integer @@ -53,7 +51,7 @@ Comment: commentable_id: integer commentable_type: string body: string - created_at: string + public_created_at: string cached_votes_total: integer cached_votes_up: integer cached_votes_down: integer @@ -67,11 +65,11 @@ Geozone: name: string ProposalNotification: fields: - title: string - body: string - proposal_id: integer - created_at: string - proposal: Proposal + title: string + body: string + proposal_id: integer + public_created_at: string + proposal: Proposal ActsAsTaggableOn::Tag: fields: id: integer @@ -80,12 +78,12 @@ ActsAsTaggableOn::Tag: kind: string Vote: fields: - votable_id: integer - votable_type: string - public_timestamp: string - vote_flag: boolean -Organization: - fields: - id: integer - user_id: integer - name: string + votable_id: integer + votable_type: string + public_created_at: string + vote_flag: boolean +# Organization: +# fields: +# id: integer +# user_id: integer +# name: string diff --git a/lib/graph_ql/api_types_creator.rb b/lib/graph_ql/api_types_creator.rb index c82a5e25e47..acf3e2d20c8 100644 --- a/lib/graph_ql/api_types_creator.rb +++ b/lib/graph_ql/api_types_creator.rb @@ -44,11 +44,7 @@ def self.create_type(model, fields, created_types) field(field_name, -> { created_types[field_type] }) do resolve -> (object, arguments, context) do association_target = object.send(field_name) - if association_target.nil? - nil - else - field_type.public_for_api.find_by(id: association_target.id) - end + association_target.present? ? field_type.public_for_api.find_by(id: association_target.id) : nil end end when :multiple_association diff --git a/spec/lib/graphql_spec.rb b/spec/lib/graphql_spec.rb index 06f89a3561a..8bf12245f2a 100644 --- a/spec/lib/graphql_spec.rb +++ b/spec/lib/graphql_spec.rb @@ -21,32 +21,46 @@ def hidden_field?(response, field_name) 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").collect do |node| + begin + if fields.size > 1 + node['node'][fields.first][fields.second] + else + node['node'][fields.first] + end + rescue NoMethodError + end + end.compact +end + describe 'ConsulSchema' do let(:user) { create(:user) } let(:proposal) { create(:proposal, author: user) } - it "returns fields of Int type" do + 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 + 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 + xit '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 + 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) end - it "returns has_many associations" do + 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) @@ -58,7 +72,7 @@ def hidden_field?(response, field_name) expect(comment_bodies).to match_array([comment_1.body, comment_2.body]) end - it "executes deeply nested queries" do + xit 'executes deeply nested queries' do org_user = create(:user) organization = create(:organization, user: org_user) org_proposal = create(:proposal, author: org_user) @@ -67,36 +81,594 @@ def hidden_field?(response, field_name) expect(dig(response, 'data.proposal.public_author.organization.name')).to eq(organization.name) end - it "hides confidential fields of Int type" do + 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 end - it "hides confidential fields of String type" do + 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 end - it "hides confidential has_one associations" do + xit '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 end - it "hides confidential belongs_to associations" do + 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 end - it "hides confidential has_many associations" do + 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 end - it "hides confidential fields inside deeply nested queries" do + 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 end + + describe 'Users' do + let(:user) { create(:user, public_activity: false) } + + it 'does not link debates if activity is not public' 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') + + expect(received_debates).to eq [] + end + + it 'does not link proposals if activity is not public' 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') + + expect(received_proposals).to eq [] + end + + it 'does not link comments if activity is not public' 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') + + expect(received_comments).to eq [] + end + + end + + describe 'Proposals' do + it 'does not include hidden proposals' do + visible_proposal = create(:proposal) + hidden_proposal = create(:proposal, :hidden) + + response = execute('{ proposals { edges { node { title } } } }') + received_titles = extract_fields(response, 'proposals', 'title') + + expect(received_titles).to match_array [visible_proposal.title] + end + + xit 'only returns proposals of the Human Rights proceeding' do + proposal = create(:proposal) + human_rights_proposal = create(:proposal, proceeding: 'Derechos Humanos', sub_proceeding: 'Right to have a job') + other_proceeding_proposal = create(:proposal) + other_proceeding_proposal.update_attribute(:proceeding, 'Another proceeding') + + response = execute('{ proposals { edges { node { title } } } }') + received_titles = extract_fields(response, 'proposals', 'title') + + expect(received_titles).to match_array [proposal.title, human_rights_proposal.title] + end + + it 'includes proposals of authors even if public activity is set to false' do + visible_author = create(:user, public_activity: true) + hidden_author = create(:user, public_activity: false) + + visible_proposal = create(:proposal, author: visible_author) + hidden_proposal = create(:proposal, author: hidden_author) + + response = execute('{ proposals { edges { node { title } } } }') + received_titles = extract_fields(response, 'proposals', 'title') + + expect(received_titles).to match_array [visible_proposal.title, hidden_proposal.title] + end + + it 'does not link author if public activity is set to false' do + visible_author = create(:user, public_activity: true) + hidden_author = create(:user, public_activity: false) + + visible_proposal = create(:proposal, author: visible_author) + hidden_proposal = create(:proposal, author: hidden_author) + + response = execute('{ proposals { edges { node { public_author { username } } } } }') + received_authors = extract_fields(response, 'proposals', 'public_author.username') + + expect(received_authors).to match_array [visible_author.username] + end + + it 'only returns date and hour for created_at' do + created_at = Time.new(2017, 12, 31, 9, 30, 15).in_time_zone(Time.zone) + create(:proposal, created_at: created_at) + + response = execute('{ proposals { edges { node { public_created_at } } } }') + received_timestamps = extract_fields(response, 'proposals', 'public_created_at') + + expect(received_timestamps.first).to include('09:00:00') + end + + it 'only retruns tags with kind nil or category' do + tag = create(:tag, name: 'Parks') + category_tag = create(:tag, name: 'Health', kind: 'category') + admin_tag = create(:tag, name: 'Admin tag', kind: 'admin') + + proposal = create(:proposal, tag_list: 'Parks, Health, Admin tag') + + response = execute("{ proposal(id: #{proposal.id}) { tags { edges { node { name } } } } }") + received_tags = dig(response, 'data.proposal.tags.edges').map { |node| node['node']['name'] } + + expect(received_tags).to match_array ['Parks', 'Health'] + end + + end + + describe 'Debates' do + it 'does not include hidden debates' do + visible_debate = create(:debate) + hidden_debate = create(:debate, :hidden) + + response = execute('{ debates { edges { node { title } } } }') + received_titles = extract_fields(response, 'debates', 'title') + + expect(received_titles).to match_array [visible_debate.title] + end + + it 'includes debates of authors even if public activity is set to false' do + visible_author = create(:user, public_activity: true) + hidden_author = create(:user, public_activity: false) + + visible_debate = create(:debate, author: visible_author) + hidden_debate = create(:debate, author: hidden_author) + + response = execute('{ debates { edges { node { title } } } }') + received_titles = extract_fields(response, 'debates', 'title') + + expect(received_titles).to match_array [visible_debate.title, hidden_debate.title] + end + + it 'does not link author if public activity is set to false' do + visible_author = create(:user, public_activity: true) + hidden_author = create(:user, public_activity: false) + + visible_debate = create(:debate, author: visible_author) + hidden_debate = create(:debate, author: hidden_author) + + response = execute('{ debates { edges { node { public_author { username } } } } }') + received_authors = extract_fields(response, 'debates', 'public_author.username') + + expect(received_authors).to match_array [visible_author.username] + end + + it 'only returns date and hour for created_at' do + created_at = Time.new(2017, 12, 31, 9, 30, 15).in_time_zone(Time.zone) + create(:debate, created_at: created_at) + + response = execute('{ debates { edges { node { public_created_at } } } }') + received_timestamps = extract_fields(response, 'debates', 'public_created_at') + + expect(received_timestamps.first).to include('09:00:00') + end + + it 'only retruns tags with kind nil or category' do + tag = create(:tag, name: 'Parks') + category_tag = create(:tag, name: 'Health', kind: 'category') + admin_tag = create(:tag, name: 'Admin tag', kind: 'admin') + + debate = create(:debate, tag_list: 'Parks, Health, Admin tag') + + response = execute("{ debate(id: #{debate.id}) { tags { edges { node { name } } } } }") + received_tags = dig(response, 'data.debate.tags.edges').map { |node| node['node']['name'] } + + expect(received_tags).to match_array ['Parks', 'Health'] + end + end + + describe 'Comments' do + it 'only returns comments from proposals and debates' do + proposal_comment = create(:comment, commentable: create(:proposal)) + debate_comment = create(:comment, commentable: create(:debate)) + spending_proposal_comment = build(:comment, commentable: create(:spending_proposal)).save(skip_validation: true) + + response = execute('{ comments { edges { node { commentable_type } } } }') + received_commentables = extract_fields(response, 'comments', 'commentable_type') + + expect(received_commentables).to match_array ['Proposal', 'Debate'] + end + + it 'displays comments of authors even if public activity is set to false' do + visible_author = create(:user, public_activity: true) + hidden_author = create(:user, public_activity: false) + + visible_comment = create(:comment, user: visible_author) + hidden_comment = create(:comment, user: hidden_author) + + response = execute('{ comments { edges { node { body } } } }') + received_comments = extract_fields(response, 'comments', 'body') + + expect(received_comments).to match_array [visible_comment.body, hidden_comment.body] + end + + it 'does not link author if public activity is set to false' do + visible_author = create(:user, public_activity: true) + hidden_author = create(:user, public_activity: false) + + visible_comment = create(:comment, author: visible_author) + hidden_comment = create(:comment, author: hidden_author) + + response = execute('{ comments { edges { node { public_author { username } } } } }') + received_authors = extract_fields(response, 'comments', 'public_author.username') + + expect(received_authors).to match_array [visible_author.username] + end + + it 'does not include hidden comments' do + visible_comment = create(:comment) + hidden_comment = create(:comment, hidden_at: Time.now) + + response = execute('{ comments { edges { node { body } } } }') + received_comments = extract_fields(response, 'comments', 'body') + + expect(received_comments).to match_array [visible_comment.body] + end + + it 'does not include comments from hidden proposals' do + visible_proposal = create(:proposal) + hidden_proposal = create(:proposal, hidden_at: Time.now) + + visible_proposal_comment = create(:comment, commentable: visible_proposal) + hidden_proposal_comment = create(:comment, commentable: hidden_proposal) + + response = execute('{ comments { edges { node { body } } } }') + received_comments = extract_fields(response, 'comments', 'body') + + expect(received_comments).to match_array [visible_proposal_comment.body] + end + + it 'does not include comments from hidden debates' do + visible_debate = create(:debate) + hidden_debate = create(:debate, hidden_at: Time.now) + + visible_debate_comment = create(:comment, commentable: visible_debate) + hidden_debate_comment = create(:comment, commentable: hidden_debate) + + response = execute('{ comments { edges { node { body } } } }') + received_comments = extract_fields(response, 'comments', 'body') + + expect(received_comments).to match_array [visible_debate_comment.body] + end + + it 'does not include comments of debates that are not public' do + not_public_debate = create(:debate, :hidden) + not_public_debate_comment = create(:comment, commentable: not_public_debate) + allow(Comment).to receive(:public_for_api).and_return([]) + + response = execute('{ comments { edges { node { body } } } }') + received_comments = extract_fields(response, 'comments', 'body') + + expect(received_comments).to_not include(not_public_debate_comment.body) + end + + it 'does not include comments of proposals that are not public' do + not_public_proposal = create(:proposal) + not_public_proposal_comment = create(:comment, commentable: not_public_proposal) + allow(Comment).to receive(:public_for_api).and_return([]) + + response = execute('{ comments { edges { node { body } } } }') + received_comments = extract_fields(response, 'comments', 'body') + + expect(received_comments).to_not include(not_public_proposal_comment.body) + end + + it 'only returns date and hour for created_at' do + created_at = Time.new(2017, 12, 31, 9, 30, 15).in_time_zone(Time.zone) + create(:comment, created_at: created_at) + + response = execute('{ comments { edges { node { public_created_at } } } }') + received_timestamps = extract_fields(response, 'comments', 'public_created_at') + + expect(received_timestamps.first).to include('09:00:00') + end + end + + describe 'Geozones' do + it 'returns geozones' do + geozone_names = [ create(:geozone), create(:geozone) ].map { |geozone| geozone.name } + + response = execute('{ geozones { edges { node { name } } } }') + received_names = extract_fields(response, 'geozones', 'name') + + expect(received_names).to match_array geozone_names + end + end + + describe 'Proposal notifications' do + + it 'does not include proposal notifications for hidden proposals' do + visible_proposal = create(:proposal) + hidden_proposal = create(:proposal, :hidden) + + visible_proposal_notification = create(:proposal_notification, proposal: visible_proposal) + hidden_proposal_notification = create(:proposal_notification, proposal: hidden_proposal) + + response = execute('{ proposal_notifications { edges { node { title } } } }') + received_notifications = extract_fields(response, 'proposal_notifications', 'title') + + expect(received_notifications).to match_array [visible_proposal_notification.title] + end + + it 'does not include proposal notifications for proposals that are not public' do + not_public_proposal = create(:proposal) + not_public_proposal_notification = create(:proposal_notification, proposal: not_public_proposal) + allow(ProposalNotification).to receive(:public_for_api).and_return([]) + + response = execute('{ proposal_notifications { edges { node { title } } } }') + received_notifications = extract_fields(response, 'proposal_notifications', 'title') + + expect(received_notifications).to_not include(not_public_proposal_notification.title) + end + + it 'only returns date and hour for created_at' do + created_at = Time.new(2017, 12, 31, 9, 30, 15).in_time_zone(Time.zone) + create(:proposal_notification, created_at: created_at) + + response = execute('{ proposal_notifications { edges { node { public_created_at } } } }') + received_timestamps = extract_fields(response, 'proposal_notifications', 'public_created_at') + + expect(received_timestamps.first).to include('09:00:00') + end + + it 'only links proposal if public' do + visible_proposal = create(:proposal) + hidden_proposal = create(:proposal, :hidden) + + visible_proposal_notification = create(:proposal_notification, proposal: visible_proposal) + hidden_proposal_notification = create(:proposal_notification, proposal: hidden_proposal) + + response = execute('{ proposal_notifications { edges { node { proposal { title } } } } }') + received_proposals = extract_fields(response, 'proposal_notifications', 'proposal.title') + + expect(received_proposals).to match_array [visible_proposal.title] + end + + end + + describe 'Tags' do + it 'only display tags with kind nil or category' do + tag = create(:tag, name: 'Parks') + category_tag = create(:tag, name: 'Health', kind: 'category') + admin_tag = create(:tag, name: 'Admin tag', kind: 'admin') + + proposal = create(:proposal, tag_list: 'Parks') + proposal = create(:proposal, tag_list: 'Health') + proposal = create(:proposal, tag_list: 'Admin tag') + + response = execute('{ tags { edges { node { name } } } }') + received_tags = extract_fields(response, 'tags', 'name') + + expect(received_tags).to match_array ['Parks', 'Health'] + end + + xit 'uppercase and lowercase tags work ok together for proposals' do + create(:tag, name: 'Health') + create(:tag, name: 'health') + create(:proposal, tag_list: 'health') + create(:proposal, tag_list: 'Health') + + response = execute('{ tags { edges { node { name } } } }') + received_tags = extract_fields(response, 'tags', 'name') + + expect(received_tags).to match_array ['Health', 'health'] + end + + xit 'uppercase and lowercase tags work ok together for debates' do + create(:tag, name: 'Health') + create(:tag, name: 'health') + create(:debate, tag_list: 'Health') + create(:debate, tag_list: 'health') + + response = execute('{ tags { edges { node { name } } } }') + received_tags = extract_fields(response, 'tags', 'name') + + expect(received_tags).to match_array ['Health', 'health'] + end + + it 'does not display tags for hidden proposals' do + proposal = create(:proposal, tag_list: 'Health') + hidden_proposal = create(:proposal, :hidden, tag_list: 'SPAM') + + response = execute('{ tags { edges { node { name } } } }') + received_tags = extract_fields(response, 'tags', 'name') + + expect(received_tags).to match_array ['Health'] + end + + it 'does not display tags for hidden debates' do + debate = create(:debate, tag_list: 'Health, Transportation') + hidden_debate = create(:debate, :hidden, tag_list: 'SPAM') + + response = execute('{ tags { edges { node { name } } } }') + received_tags = extract_fields(response, 'tags', 'name') + + expect(received_tags).to match_array ['Health', 'Transportation'] + end + + xit "does not display tags for proceeding's proposals" do + valid_proceeding_proposal = create(:proposal, proceeding: 'Derechos Humanos', sub_proceeding: 'Right to a Home', tag_list: 'Health') + invalid_proceeding_proposal = create(:proposal, tag_list: 'Animals') + invalid_proceeding_proposal.update_attribute('proceeding', 'Random') + + response = execute('{ tags { edges { node { name } } } }') + received_tags = extract_fields(response, 'tags', 'name') + + expect(received_tags).to match_array ['Health'] + end + + it 'does not display tags for taggings that are not public' do + proposal = create(:proposal, tag_list: 'Health') + allow(ActsAsTaggableOn::Tag).to receive(:public_for_api).and_return([]) + + response = execute('{ tags { edges { node { name } } } }') + received_tags = extract_fields(response, 'tags', 'name') + + expect(received_tags).to_not include('Health') + end + + end + + describe 'Votes' do + + it 'only returns votes from proposals, debates and comments' do + proposal = create(:proposal) + debate = create(:debate) + comment = create(:comment) + spending_proposal = create(:spending_proposal) + + proposal_vote = create(:vote, votable: proposal) + debate_vote = create(:vote, votable: debate) + comment_vote = create(:vote, votable: comment) + spending_proposal_vote = create(:vote, votable: spending_proposal) + + response = execute('{ votes { edges { node { votable_type } } } }') + received_votables = extract_fields(response, 'votes', 'votable_type') + + expect(received_votables).to match_array ['Proposal', 'Debate', 'Comment'] + end + + it 'does not include votes from hidden debates' do + visible_debate = create(:debate) + hidden_debate = create(:debate, :hidden) + + visible_debate_vote = create(:vote, votable: visible_debate) + hidden_debate_vote = create(:vote, votable: hidden_debate) + + response = execute('{ votes { edges { node { votable_id } } } }') + received_debates = extract_fields(response, 'votes', 'votable_id') + + expect(received_debates).to match_array [visible_debate.id] + end + + it 'does not include votes of hidden proposals' do + visible_proposal = create(:proposal) + hidden_proposal = create(:proposal, hidden_at: Time.now) + + visible_proposal_vote = create(:vote, votable: visible_proposal) + hidden_proposal_vote = create(:vote, votable: hidden_proposal) + + response = execute('{ votes { edges { node { votable_id } } } }') + received_proposals = extract_fields(response, 'votes', 'votable_id') + + expect(received_proposals).to match_array [visible_proposal.id] + end + + it 'does not include votes of hidden comments' do + visible_comment = create(:comment) + hidden_comment = create(:comment, hidden_at: Time.now) + + visible_comment_vote = create(:vote, votable: visible_comment) + hidden_comment_vote = create(:vote, votable: hidden_comment) + + response = execute('{ votes { edges { node { votable_id } } } }') + received_comments = extract_fields(response, 'votes', 'votable_id') + + expect(received_comments).to match_array [visible_comment.id] + end + + it 'does not include votes of comments from a hidden proposal' do + visible_proposal = create(:proposal) + hidden_proposal = create(:proposal, :hidden) + + visible_proposal_comment = create(:comment, commentable: visible_proposal) + hidden_proposal_comment = create(:comment, commentable: hidden_proposal) + + visible_proposal_comment_vote = create(:vote, votable: visible_proposal_comment) + hidden_proposal_comment_vote = create(:vote, votable: hidden_proposal_comment) + + response = execute('{ votes { edges { node { votable_id } } } }') + received_votables = extract_fields(response, 'votes', 'votable_id') + + expect(received_votables).to match_array [visible_proposal_comment.id] + end + + it 'does not include votes of comments from a hidden debate' do + visible_debate = create(:debate) + hidden_debate = create(:debate, :hidden) + + visible_debate_comment = create(:comment, commentable: visible_debate) + hidden_debate_comment = create(:comment, commentable: hidden_debate) + + visible_debate_comment_vote = create(:vote, votable: visible_debate_comment) + hidden_debate_comment_vote = create(:vote, votable: hidden_debate_comment) + + response = execute('{ votes { edges { node { votable_id } } } }') + received_votables = extract_fields(response, 'votes', 'votable_id') + + expect(received_votables).to match_array [visible_debate_comment.id] + end + + it 'does not include votes of debates that are not public' do + not_public_debate = create(:debate) + allow(Vote).to receive(:public_for_api).and_return([]) + + not_public_debate_vote = create(:vote, votable: not_public_debate) + + response = execute('{ votes { edges { node { votable_id } } } }') + received_votables = extract_fields(response, 'votes', 'votable_id') + + expect(received_votables).to_not include(not_public_debate.id) + end + + it 'does not include votes of a hidden proposals' do + not_public_proposal = create(:proposal) + allow(Vote).to receive(:public_for_api).and_return([]) + + not_public_proposal_vote = create(:vote, votable: not_public_proposal) + + response = execute('{ votes { edges { node { votable_id } } } }') + received_votables = extract_fields(response, 'votes', 'votable_id') + + expect(received_votables).to_not include(not_public_proposal.id) + end + + it 'does not include votes of a hidden comments' do + not_public_comment = create(:comment) + allow(Vote).to receive(:public_for_api).and_return([]) + + not_public_comment_vote = create(:vote, votable: not_public_comment) + + response = execute('{ votes { edges { node { votable_id } } } }') + received_votables = extract_fields(response, 'votes', 'votable_id') + + expect(received_votables).to_not include(not_public_comment.id) + end + + it 'only returns date and hour for created_at' do + created_at = Time.new(2017, 12, 31, 9, 30, 15).in_time_zone(Time.zone) + create(:vote, created_at: created_at) + + response = execute('{ votes { edges { node { public_created_at } } } }') + received_timestamps = extract_fields(response, 'votes', 'public_created_at') + + expect(received_timestamps.first).to include('09:00:00') + end + + end + end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb index a8656673480..a060ef59c79 100644 --- a/spec/models/comment_spec.rb +++ b/spec/models/comment_spec.rb @@ -181,5 +181,11 @@ expect(Comment.public_for_api).not_to include(comment) end + + it 'does not return comments with no commentable' do + comment = build(:comment, commentable: nil).save!(validate: false) + + expect(Comment.public_for_api).to_not include(comment) + end end end diff --git a/spec/models/proposal_notification_spec.rb b/spec/models/proposal_notification_spec.rb index cd8bf346aa4..88a98ebeef1 100644 --- a/spec/models/proposal_notification_spec.rb +++ b/spec/models/proposal_notification_spec.rb @@ -30,12 +30,18 @@ expect(ProposalNotification.public_for_api).to include(notification) end - it "blocks notifications whose proposal is hidden" do + it "blocks proposal notifications whose proposal is hidden" do proposal = create(:proposal, :hidden) notification = create(:proposal_notification, proposal: proposal) expect(ProposalNotification.public_for_api).not_to include(notification) end + + it "blocks proposal notifications without proposal" do + proposal = build(:proposal_notification, proposal: nil).save!(validate: false) + + expect(ProposalNotification.public_for_api).not_to include(notification) + end end describe "minimum interval between notifications" do diff --git a/spec/models/vote_spec.rb b/spec/models/vote_spec.rb index 8e5db0ce7af..25fd069f2da 100644 --- a/spec/models/vote_spec.rb +++ b/spec/models/vote_spec.rb @@ -84,18 +84,33 @@ expect(Vote.public_for_api).not_to include(vote) end + it 'blocks votes on comments on hidden proposals' do + hidden_proposal = create(:proposal, :hidden) + comment_on_hidden_proposal = create(:comment, commentable: hidden_proposal) + vote = create(:vote, votable: comment_on_hidden_proposal) + + expect(Vote.public_for_api).to_not include(vote) + end + + it 'blocks votes on comments on hidden debates' do + hidden_debate = create(:debate, :hidden) + comment_on_hidden_debate = create(:comment, commentable: hidden_debate) + vote = create(:vote, votable: comment_on_hidden_debate) + + expect(Vote.public_for_api).to_not include(vote) + end + it 'blocks any other kind of votes' do spending_proposal = create(:spending_proposal) vote = create(:vote, votable: spending_proposal) expect(Vote.public_for_api).not_to include(vote) end - end - describe '#public_timestamp' do - it "truncates created_at timestamp up to minutes" do - vote = create(:vote, created_at: Time.zone.parse('2016-02-10 15:30:45')) - expect(vote.public_timestamp).to eq(Time.zone.parse('2016-02-10 15:00:00')) + it 'blocks votes without votable' do + vote = build(:vote, votable: nil).save!(validate: false) + + expect(Vote.public_for_api).not_to include(vote) end end end