From 258a29ffde47abb6b1e3edefffd1d2087ec44e87 Mon Sep 17 00:00:00 2001 From: KMY Date: Thu, 14 Sep 2023 18:39:13 +0900 Subject: [PATCH] Add unlisted-public tag/circle/list tests --- app/models/public_feed.rb | 7 +- app/models/tag_feed.rb | 3 +- .../v1/circles/accounts_controller_spec.rb | 92 ++++++++++++++ .../api/v1/circles_controller_spec.rb | 50 ++++++++ .../api/v1/lists_controller_spec.rb | 50 ++++++++ spec/models/tag_feed_spec.rb | 42 +++++++ .../services/fan_out_on_write_service_spec.rb | 119 +++++++++++++++++- 7 files changed, 360 insertions(+), 3 deletions(-) create mode 100644 spec/controllers/api/v1/circles/accounts_controller_spec.rb create mode 100644 spec/controllers/api/v1/circles_controller_spec.rb create mode 100644 spec/controllers/api/v1/lists_controller_spec.rb diff --git a/app/models/public_feed.rb b/app/models/public_feed.rb index 1ed68e45e9a0cf..a641e77039194d 100644 --- a/app/models/public_feed.rb +++ b/app/models/public_feed.rb @@ -28,7 +28,8 @@ def get(limit, max_id = nil, since_id = nil, min_id = nil) scope.merge!(account_filters_scope) if account? scope.merge!(media_only_scope) if media_only? scope.merge!(language_scope) if account&.chosen_languages.present? - scope.merge!(anonymous_scope) unless account? + # scope.merge!(anonymous_scope) unless account? + scope = to_anonymous_scope(scope) unless account? scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id) end @@ -105,6 +106,10 @@ def anonymous_scope local_only? ? Status.where(visibility: [:public, :public_unlisted]) : Status.where(visibility: :public) end + def to_anonymous_scope(scope) + scope.where.not(visibility: :login) + end + def account_filters_scope Status.not_excluded_by_account(account).tap do |scope| scope.merge!(Status.not_domain_blocked_by_account(account)) unless local_only? diff --git a/app/models/tag_feed.rb b/app/models/tag_feed.rb index 14f4afe73700fd..9c679a02495453 100644 --- a/app/models/tag_feed.rb +++ b/app/models/tag_feed.rb @@ -32,7 +32,8 @@ def get(limit, max_id = nil, since_id = nil, min_id = nil) scope.merge!(remote_only_scope) if remote_only? || hide_local_users? scope.merge!(account_filters_scope) if account? scope.merge!(media_only_scope) if media_only? - scope.merge!(anonymous_scope) unless account? + # scope.merge!(anonymous_scope) unless account? + scope = to_anonymous_scope(scope) unless account? scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id) end diff --git a/spec/controllers/api/v1/circles/accounts_controller_spec.rb b/spec/controllers/api/v1/circles/accounts_controller_spec.rb new file mode 100644 index 00000000000000..bddd8aa2115cfb --- /dev/null +++ b/spec/controllers/api/v1/circles/accounts_controller_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Api::V1::Circles::AccountsController do + render_views + + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:circle) { Fabricate(:circle, account: user.account) } + let(:follow) { Fabricate(:follow, target_account: user.account) } + + before do + circle.accounts << follow.account + allow(controller).to receive(:doorkeeper_token) { token } + end + + describe 'GET #index' do + let(:scopes) { 'read:lists' } + + it 'returns http success' do + get :show, params: { circle_id: circle.id } + + expect(response).to have_http_status(200) + end + end + + describe 'POST #create' do + let(:scopes) { 'write:lists' } + let(:bob) { Fabricate(:account, username: 'bob') } + + context 'when the added account is followed' do + before do + bob.follow!(user.account) + post :create, params: { circle_id: circle.id, account_ids: [bob.id] } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'adds account to the circle' do + expect(circle.accounts.include?(bob)).to be true + end + end + + context 'when the added account has been sent a follow request' do + before do + bob.follow_requests.create!(target_account: user.account) + post :create, params: { circle_id: circle.id, account_ids: [bob.id] } + end + + it 'returns http success' do + expect(response).to have_http_status(404) + end + + it 'adds account to the circle' do + expect(circle.accounts.include?(bob)).to be false + end + end + + context 'when the added account is not followed' do + before do + post :create, params: { circle_id: circle.id, account_ids: [bob.id] } + end + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + + it 'does not add the account to the circle' do + expect(circle.accounts.include?(bob)).to be false + end + end + end + + describe 'DELETE #destroy' do + let(:scopes) { 'write:lists' } + + before do + delete :destroy, params: { circle_id: circle.id, account_ids: [circle.accounts.first.id] } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'removes account from the circle' do + expect(circle.accounts.count).to eq 0 + end + end +end diff --git a/spec/controllers/api/v1/circles_controller_spec.rb b/spec/controllers/api/v1/circles_controller_spec.rb new file mode 100644 index 00000000000000..1aff55e65bc54a --- /dev/null +++ b/spec/controllers/api/v1/circles_controller_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Api::V1::CirclesController do + render_views + + let(:user) { Fabricate(:user) } + let(:circle) { Fabricate(:circle, account: user.account) } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + context 'with a user context' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:lists') } + + describe 'GET #show' do + it 'returns http success' do + get :show, params: { id: circle.id } + expect(response).to have_http_status(200) + end + end + end + + context 'with the wrong user context' do + let(:other_user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: other_user.id, scopes: 'read') } + + describe 'GET #show' do + it 'returns http not found' do + get :show, params: { id: circle.id } + expect(response).to have_http_status(404) + end + end + end + + context 'without a user context' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read') } + + describe 'GET #show' do + it 'returns http unprocessable entity' do + get :show, params: { id: circle.id } + + expect(response).to have_http_status(422) + expect(response.headers['Link']).to be_nil + end + end + end +end diff --git a/spec/controllers/api/v1/lists_controller_spec.rb b/spec/controllers/api/v1/lists_controller_spec.rb new file mode 100644 index 00000000000000..b54f3f70fa6560 --- /dev/null +++ b/spec/controllers/api/v1/lists_controller_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Api::V1::ListsController do + render_views + + let(:user) { Fabricate(:user) } + let(:list) { Fabricate(:list, account: user.account) } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + context 'with a user context' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:lists') } + + describe 'GET #show' do + it 'returns http success' do + get :show, params: { id: list.id } + expect(response).to have_http_status(200) + end + end + end + + context 'with the wrong user context' do + let(:other_user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: other_user.id, scopes: 'read') } + + describe 'GET #show' do + it 'returns http not found' do + get :show, params: { id: list.id } + expect(response).to have_http_status(404) + end + end + end + + context 'without a user context' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read') } + + describe 'GET #show' do + it 'returns http unprocessable entity' do + get :show, params: { id: list.id } + + expect(response).to have_http_status(422) + expect(response.headers['Link']).to be_nil + end + end + end +end diff --git a/spec/models/tag_feed_spec.rb b/spec/models/tag_feed_spec.rb index 6f5e1eb307ee4b..270797ccd8cccb 100644 --- a/spec/models/tag_feed_spec.rb +++ b/spec/models/tag_feed_spec.rb @@ -66,5 +66,47 @@ results = described_class.new(tag_cats, nil).get(20) expect(results).to include(status) end + + it 'public_unlisted post returns' do + status_tagged_with_cats.update(visibility: :public_unlisted) + results = described_class.new(tag_cats, nil).get(20) + expect(results).to include status_tagged_with_cats + end + + it 'unlisted post not returns' do + status_tagged_with_cats.update(visibility: :unlisted) + results = described_class.new(tag_cats, nil).get(20) + expect(results).to_not include status_tagged_with_cats + end + + it 'unlisted post not returns with account' do + status_tagged_with_cats.update(visibility: :unlisted) + results = described_class.new(tag_cats, account).get(20) + expect(results).to_not include status_tagged_with_cats + end + + it 'unlisted/public_searchability post returns' do + status_tagged_with_cats.update(visibility: :unlisted, searchability: :public) + results = described_class.new(tag_cats, nil).get(20) + expect(results).to include status_tagged_with_cats + end + + it 'unlisted/public_searchability post returns with account' do + status_tagged_with_cats.update(visibility: :unlisted, searchability: :public) + results = described_class.new(tag_cats, account).get(20) + expect(results).to include status_tagged_with_cats + end + + it 'private post not returns' do + status_tagged_with_cats.update(visibility: :private, searchability: :public) + results = described_class.new(tag_cats, nil).get(20) + expect(results).to_not include status_tagged_with_cats + end + + it 'private post not returns with account' do + status_tagged_with_cats.update(visibility: :private, searchability: :public) + results = described_class.new(tag_cats, account).get(20) + expect(results).to_not include status_tagged_with_cats + end end end diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb index c15253c879cb5b..65db23214a4607 100644 --- a/spec/services/fan_out_on_write_service_spec.rb +++ b/spec/services/fan_out_on_write_service_spec.rb @@ -7,9 +7,10 @@ let(:last_active_at) { Time.now.utc } let(:searchability) { 'public' } + let(:dissubscribable) { false } let(:status) { Fabricate(:status, account: alice, visibility: visibility, searchability: searchability, text: 'Hello @bob #hoge') } - let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at).account } + let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { dissubscribable: dissubscribable }).account } let!(:bob) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'bob' }).account } let!(:tom) { Fabricate(:user, current_sign_in_at: last_active_at).account } let!(:ohagi) { Fabricate(:user, current_sign_in_at: last_active_at).account } @@ -100,6 +101,14 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(empty_antenna)).to_not include status.id end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is not added to the antenna feed' do + expect(antenna_feed_of(antenna)).to_not include status.id + end + end end context 'with STL antenna' do @@ -110,6 +119,14 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(empty_antenna)).to_not include status.id end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is added to the antenna feed' do + expect(antenna_feed_of(antenna)).to include status.id + end + end end context 'with LTL antenna' do @@ -120,6 +137,14 @@ def antenna_with_options(owner, **options) expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(empty_antenna)).to_not include status.id end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is added to the antenna feed' do + expect(antenna_feed_of(antenna)).to include status.id + end + end end end @@ -238,6 +263,89 @@ def antenna_with_options(owner, **options) end end + context 'when status is public_unlisted' do + let(:visibility) { 'public_unlisted' } + + it 'is added to the home feed of its author' do + expect(home_feed_of(alice)).to include status.id + end + + it 'is added to the home feed of a follower' do + expect(home_feed_of(bob)).to include status.id + expect(home_feed_of(tom)).to include status.id + end + + it 'is broadcast publicly' do + expect(redis).to have_received(:publish).with('timeline:hashtag:hoge', anything) + expect(redis).to have_received(:publish).with('timeline:public:local', anything) + expect(redis).to_not have_received(:publish).with('timeline:public', anything) + end + + context 'with list' do + let!(:list) { list_with_account(bob, alice) } + let!(:empty_list) { list_with_account(ohagi, bob) } + + it 'is added to the list feed of list follower' do + expect(list_feed_of(list)).to include status.id + expect(list_feed_of(empty_list)).to_not include status.id + end + end + + context 'with antenna' do + let!(:antenna) { antenna_with_account(bob, alice) } + let!(:empty_antenna) { antenna_with_account(tom, bob) } + + it 'is added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is not added to the antenna feed' do + expect(antenna_feed_of(antenna)).to_not include status.id + end + end + end + + context 'with STL antenna' do + let!(:antenna) { antenna_with_options(bob, stl: true) } + let!(:empty_antenna) { antenna_with_options(tom) } + + it 'is added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is added to the antenna feed' do + expect(antenna_feed_of(antenna)).to include status.id + end + end + end + + context 'with LTL antenna' do + let!(:antenna) { antenna_with_options(bob, ltl: true) } + let!(:empty_antenna) { antenna_with_options(tom) } + + it 'is added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is added to the antenna feed' do + expect(antenna_feed_of(antenna)).to include status.id + end + end + end + end + context 'when status is unlisted' do let(:visibility) { 'unlisted' } @@ -255,6 +363,15 @@ def antenna_with_options(owner, **options) expect(redis).to_not have_received(:publish).with('timeline:public', anything) end + context 'with searchability private' do + let(:searchability) { 'private' } + + it 'is not broadcast to the hashtag stream' do + expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything) + expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge:local', anything) + end + end + context 'with list' do let!(:list) { list_with_account(bob, alice) } let!(:empty_list) { list_with_account(ohagi, bob) }