From 230a012f0090c496fc5cdb011bcc8ed732fd0f5c Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 3 Mar 2019 22:18:23 +0100 Subject: Add polls (#10111) * Add polls Fix #1629 * Add tests * Fixes * Change API for creating polls * Use name instead of content for votes * Remove poll validation for remote polls * Add polls to public pages * When updating the poll, update options just in case they were changed * Fix public pages showing both poll and other media --- spec/controllers/api/v1/filter_controller_spec.rb | 87 ---------------------- spec/controllers/api/v1/filters_controller_spec.rb | 87 ++++++++++++++++++++++ .../api/v1/polls/votes_controller_spec.rb | 34 +++++++++ spec/controllers/api/v1/polls_controller_spec.rb | 23 ++++++ spec/fabricators/poll_fabricator.rb | 8 ++ spec/fabricators/poll_vote_fabricator.rb | 5 ++ spec/models/poll_spec.rb | 5 ++ spec/models/poll_vote_spec.rb | 5 ++ 8 files changed, 167 insertions(+), 87 deletions(-) delete mode 100644 spec/controllers/api/v1/filter_controller_spec.rb create mode 100644 spec/controllers/api/v1/filters_controller_spec.rb create mode 100644 spec/controllers/api/v1/polls/votes_controller_spec.rb create mode 100644 spec/controllers/api/v1/polls_controller_spec.rb create mode 100644 spec/fabricators/poll_fabricator.rb create mode 100644 spec/fabricators/poll_vote_fabricator.rb create mode 100644 spec/models/poll_spec.rb create mode 100644 spec/models/poll_vote_spec.rb (limited to 'spec') diff --git a/spec/controllers/api/v1/filter_controller_spec.rb b/spec/controllers/api/v1/filter_controller_spec.rb deleted file mode 100644 index 5948809e3..000000000 --- a/spec/controllers/api/v1/filter_controller_spec.rb +++ /dev/null @@ -1,87 +0,0 @@ -require 'rails_helper' - -RSpec.describe Api::V1::FiltersController, type: :controller do - render_views - - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'GET #index' do - let(:scopes) { 'read:filters' } - let!(:filter) { Fabricate(:custom_filter, account: user.account) } - - it 'returns http success' do - get :index - expect(response).to have_http_status(200) - end - end - - describe 'POST #create' do - let(:scopes) { 'write:filters' } - - before do - post :create, params: { phrase: 'magic', context: %w(home), irreversible: true } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'creates a filter' do - filter = user.account.custom_filters.first - expect(filter).to_not be_nil - expect(filter.phrase).to eq 'magic' - expect(filter.context).to eq %w(home) - expect(filter.irreversible?).to be true - expect(filter.expires_at).to be_nil - end - end - - describe 'GET #show' do - let(:scopes) { 'read:filters' } - let(:filter) { Fabricate(:custom_filter, account: user.account) } - - it 'returns http success' do - get :show, params: { id: filter.id } - expect(response).to have_http_status(200) - end - end - - describe 'PUT #update' do - let(:scopes) { 'write:filters' } - let(:filter) { Fabricate(:custom_filter, account: user.account) } - - before do - put :update, params: { id: filter.id, phrase: 'updated' } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'updates the filter' do - expect(filter.reload.phrase).to eq 'updated' - end - end - - describe 'DELETE #destroy' do - let(:scopes) { 'write:filters' } - let(:filter) { Fabricate(:custom_filter, account: user.account) } - - before do - delete :destroy, params: { id: filter.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'removes the filter' do - expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound - end - end -end diff --git a/spec/controllers/api/v1/filters_controller_spec.rb b/spec/controllers/api/v1/filters_controller_spec.rb new file mode 100644 index 000000000..5948809e3 --- /dev/null +++ b/spec/controllers/api/v1/filters_controller_spec.rb @@ -0,0 +1,87 @@ +require 'rails_helper' + +RSpec.describe Api::V1::FiltersController, type: :controller do + render_views + + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + describe 'GET #index' do + let(:scopes) { 'read:filters' } + let!(:filter) { Fabricate(:custom_filter, account: user.account) } + + it 'returns http success' do + get :index + expect(response).to have_http_status(200) + end + end + + describe 'POST #create' do + let(:scopes) { 'write:filters' } + + before do + post :create, params: { phrase: 'magic', context: %w(home), irreversible: true } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'creates a filter' do + filter = user.account.custom_filters.first + expect(filter).to_not be_nil + expect(filter.phrase).to eq 'magic' + expect(filter.context).to eq %w(home) + expect(filter.irreversible?).to be true + expect(filter.expires_at).to be_nil + end + end + + describe 'GET #show' do + let(:scopes) { 'read:filters' } + let(:filter) { Fabricate(:custom_filter, account: user.account) } + + it 'returns http success' do + get :show, params: { id: filter.id } + expect(response).to have_http_status(200) + end + end + + describe 'PUT #update' do + let(:scopes) { 'write:filters' } + let(:filter) { Fabricate(:custom_filter, account: user.account) } + + before do + put :update, params: { id: filter.id, phrase: 'updated' } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'updates the filter' do + expect(filter.reload.phrase).to eq 'updated' + end + end + + describe 'DELETE #destroy' do + let(:scopes) { 'write:filters' } + let(:filter) { Fabricate(:custom_filter, account: user.account) } + + before do + delete :destroy, params: { id: filter.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'removes the filter' do + expect { filter.reload }.to raise_error ActiveRecord::RecordNotFound + end + end +end diff --git a/spec/controllers/api/v1/polls/votes_controller_spec.rb b/spec/controllers/api/v1/polls/votes_controller_spec.rb new file mode 100644 index 000000000..0ee3aa040 --- /dev/null +++ b/spec/controllers/api/v1/polls/votes_controller_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +RSpec.describe Api::V1::Polls::VotesController, type: :controller do + render_views + + let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } + let(:scopes) { 'write:statuses' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + + before { allow(controller).to receive(:doorkeeper_token) { token } } + + describe 'POST #create' do + let(:poll) { Fabricate(:poll) } + + before do + post :create, params: { poll_id: poll.id, choices: %w(1) } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'creates a vote' do + vote = poll.votes.where(account: user.account).first + + expect(vote).to_not be_nil + expect(vote.choice).to eq 1 + end + + it 'updates poll tallies' do + expect(poll.reload.cached_tallies).to eq [0, 1] + end + end +end diff --git a/spec/controllers/api/v1/polls_controller_spec.rb b/spec/controllers/api/v1/polls_controller_spec.rb new file mode 100644 index 000000000..2b8d5f3ef --- /dev/null +++ b/spec/controllers/api/v1/polls_controller_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +RSpec.describe Api::V1::PollsController, type: :controller do + render_views + + let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) } + let(:scopes) { 'read:statuses' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + + before { allow(controller).to receive(:doorkeeper_token) { token } } + + describe 'GET #show' do + let(:poll) { Fabricate(:poll) } + + before do + get :show, params: { id: poll.id } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/fabricators/poll_fabricator.rb b/spec/fabricators/poll_fabricator.rb new file mode 100644 index 000000000..746610f7c --- /dev/null +++ b/spec/fabricators/poll_fabricator.rb @@ -0,0 +1,8 @@ +Fabricator(:poll) do + account + status + expires_at { 7.days.from_now } + options %w(Foo Bar) + multiple false + hide_totals false +end diff --git a/spec/fabricators/poll_vote_fabricator.rb b/spec/fabricators/poll_vote_fabricator.rb new file mode 100644 index 000000000..51f9b006e --- /dev/null +++ b/spec/fabricators/poll_vote_fabricator.rb @@ -0,0 +1,5 @@ +Fabricator(:poll_vote) do + account + poll + choice 0 +end diff --git a/spec/models/poll_spec.rb b/spec/models/poll_spec.rb new file mode 100644 index 000000000..666f8ca68 --- /dev/null +++ b/spec/models/poll_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Poll, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/poll_vote_spec.rb b/spec/models/poll_vote_spec.rb new file mode 100644 index 000000000..354afd535 --- /dev/null +++ b/spec/models/poll_vote_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe PollVote, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end -- cgit From 0e6998da3cdc0ac73845d1c3c3c4c75972ea28ee Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 4 Mar 2019 01:13:42 +0100 Subject: Add tests for ActivityPub poll processing (#10143) --- app/lib/activitypub/activity/create.rb | 3 +- spec/lib/activitypub/activity/create_spec.rb | 42 +++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) (limited to 'spec') diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index 08c46be44..fc4c45692 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -231,8 +231,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity items = @object['oneOf'] end - Poll.new( - account: @account, + @account.polls.new( multiple: multiple, expires_at: expires_at, options: items.map { |item| item['name'].presence || item['content'] }, diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 26cb84871..ac6237c86 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' RSpec.describe ActivityPub::Activity::Create do - let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers') } + let(:sender) { Fabricate(:account, followers_url: 'http://example.com/followers', domain: 'example.com', uri: 'https://example.com/actor') } let(:json) do { @@ -407,6 +407,46 @@ RSpec.describe ActivityPub::Activity::Create do expect(status).to_not be_nil end end + + context 'with poll' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Question', + content: 'Which color was the submarine?', + oneOf: [ + { + name: 'Yellow', + replies: { + type: 'Collection', + totalItems: 10, + }, + }, + { + name: 'Blue', + replies: { + type: 'Collection', + totalItems: 3, + } + }, + ], + } + end + + it 'creates status' do + status = sender.statuses.first + expect(status).to_not be_nil + expect(status.poll).to_not be_nil + end + + it 'creates a poll' do + poll = sender.polls.first + expect(poll).to_not be_nil + expect(poll.status).to_not be_nil + expect(poll.options).to eq %w(Yellow Blue) + expect(poll.cached_tallies).to eq [10, 3] + end + end end context 'when sender is followed by local users' do -- cgit From 833ffce2df68ae3b673e230fcb273da5d8c4681f Mon Sep 17 00:00:00 2001 From: ThibG Date: Mon, 4 Mar 2019 22:51:23 +0100 Subject: Store remote votes URI (#10158) * Store remote votes URI * Add spec for accepting remote votes * Make poll vote id generation work the same way as follows --- app/lib/activitypub/activity/create.rb | 2 +- app/models/poll_vote.rb | 3 +++ app/serializers/activitypub/vote_serializer.rb | 2 +- db/migrate/20190304152020_add_uri_to_poll_votes.rb | 5 +++++ db/schema.rb | 3 ++- spec/lib/activitypub/activity/create_spec.rb | 21 +++++++++++++++++++++ 6 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20190304152020_add_uri_to_poll_votes.rb (limited to 'spec') diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb index fc4c45692..07ef16bf3 100644 --- a/app/lib/activitypub/activity/create.rb +++ b/app/lib/activitypub/activity/create.rb @@ -241,7 +241,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity def poll_vote? return false if replied_to_status.nil? || replied_to_status.poll.nil? || !replied_to_status.local? || !replied_to_status.poll.options.include?(@object['name']) - replied_to_status.poll.votes.create!(account: @account, choice: replied_to_status.poll.options.index(@object['name'])) + replied_to_status.poll.votes.create!(account: @account, choice: replied_to_status.poll.options.index(@object['name']), uri: @object['id']) end def resolve_thread(status) diff --git a/app/models/poll_vote.rb b/app/models/poll_vote.rb index 57781d616..aec678968 100644 --- a/app/models/poll_vote.rb +++ b/app/models/poll_vote.rb @@ -9,6 +9,7 @@ # choice :integer default(0), not null # created_at :datetime not null # updated_at :datetime not null +# uri :string # class PollVote < ApplicationRecord @@ -20,6 +21,8 @@ class PollVote < ApplicationRecord after_create_commit :increment_counter_cache + delegate :local?, to: :account + private def increment_counter_cache diff --git a/app/serializers/activitypub/vote_serializer.rb b/app/serializers/activitypub/vote_serializer.rb index 655d04d22..248190404 100644 --- a/app/serializers/activitypub/vote_serializer.rb +++ b/app/serializers/activitypub/vote_serializer.rb @@ -6,7 +6,7 @@ class ActivityPub::VoteSerializer < ActiveModel::Serializer :in_reply_to, :to def id - [ActivityPub::TagManager.instance.uri_for(object.account), '#votes/', object.id].join + ActivityPub::TagManager.instance.uri_for(object) || [ActivityPub::TagManager.instance.uri_for(object.account), '#votes/', object.id].join end def type diff --git a/db/migrate/20190304152020_add_uri_to_poll_votes.rb b/db/migrate/20190304152020_add_uri_to_poll_votes.rb new file mode 100644 index 000000000..f6b81f1ba --- /dev/null +++ b/db/migrate/20190304152020_add_uri_to_poll_votes.rb @@ -0,0 +1,5 @@ +class AddUriToPollVotes < ActiveRecord::Migration[5.2] + def change + add_column :poll_votes, :uri, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index d5d516e06..161619dcf 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_02_26_003449) do +ActiveRecord::Schema.define(version: 2019_03_04_152020) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -447,6 +447,7 @@ ActiveRecord::Schema.define(version: 2019_02_26_003449) do t.integer "choice", default: 0, null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "uri" t.index ["account_id"], name: "index_poll_votes_on_account_id" t.index ["poll_id"], name: "index_poll_votes_on_poll_id" end diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index ac6237c86..4780c29c8 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -447,6 +447,27 @@ RSpec.describe ActivityPub::Activity::Create do expect(poll.cached_tallies).to eq [10, 3] end end + + context 'when a vote to a local poll' do + let(:poll) { Fabricate(:poll, options: %w(Yellow Blue)) } + let!(:local_status) { Fabricate(:status, owned_poll: poll) } + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + name: 'Yellow', + inReplyTo: ActivityPub::TagManager.instance.uri_for(local_status) + } + end + + it 'adds a vote to the poll with correct uri' do + vote = poll.votes.first + expect(vote).to_not be_nil + expect(vote.uri).to eq object_json[:id] + expect(poll.reload.cached_tallies).to eq [1, 0] + end + end end context 'when sender is followed by local users' do -- cgit From 0a39c81dd87242feb84da68b8907c3e45620eadf Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Tue, 5 Mar 2019 03:46:36 +0100 Subject: Add test ensuring that unknown object types are rejected (#10166) --- spec/lib/activitypub/activity/create_spec.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'spec') diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 4780c29c8..56c7bfc61 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -28,6 +28,20 @@ RSpec.describe ActivityPub::Activity::Create do subject.perform end + context 'unknown object type' do + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Banana', + content: 'Lorem ipsum', + } + end + + it 'does not create a status' do + expect(sender.statuses.count).to be_zero + end + end + context 'standalone' do let(:object_json) do { -- cgit