diff options
Diffstat (limited to 'spec')
-rw-r--r-- | spec/controllers/api/v1/accounts/credentials_controller_spec.rb | 6 | ||||
-rw-r--r-- | spec/controllers/api/v1/timelines/direct_controller_spec.rb | 17 | ||||
-rw-r--r-- | spec/controllers/api/v1/timelines/public_controller_spec.rb | 4 | ||||
-rw-r--r-- | spec/controllers/application_controller_spec.rb | 35 | ||||
-rw-r--r-- | spec/controllers/settings/flavours_controller_spec.rb | 39 | ||||
-rw-r--r-- | spec/lib/activitypub/activity/create_spec.rb | 46 | ||||
-rw-r--r-- | spec/lib/advanced_text_formatter_spec.rb | 300 | ||||
-rw-r--r-- | spec/lib/feed_manager_spec.rb | 7 | ||||
-rw-r--r-- | spec/lib/sanitize_config_spec.rb | 25 | ||||
-rw-r--r-- | spec/models/concerns/account_interactions_spec.rb | 9 | ||||
-rw-r--r-- | spec/models/follow_request_spec.rb | 7 | ||||
-rw-r--r-- | spec/models/public_feed_spec.rb | 62 | ||||
-rw-r--r-- | spec/models/status_spec.rb | 87 | ||||
-rw-r--r-- | spec/models/tag_feed_spec.rb | 14 | ||||
-rw-r--r-- | spec/policies/status_policy_spec.rb | 12 | ||||
-rw-r--r-- | spec/presenters/instance_presenter_spec.rb | 4 | ||||
-rw-r--r-- | spec/validators/status_length_validator_spec.rb | 23 |
17 files changed, 665 insertions, 32 deletions
diff --git a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb index b5d5c37a9..a677aaad0 100644 --- a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb @@ -75,9 +75,11 @@ describe Api::V1::Accounts::CredentialsController do end end - describe 'with invalid data' do + describe 'with a too long profile bio' do before do - patch :update, params: { note: 'This is too long. ' * 30 } + note = 'This is too long. ' + note += 'a' * (Account::MAX_NOTE_LENGTH - note.length + 1) + patch :update, params: { note: note } end it 'returns http unprocessable entity' do diff --git a/spec/controllers/api/v1/timelines/direct_controller_spec.rb b/spec/controllers/api/v1/timelines/direct_controller_spec.rb new file mode 100644 index 000000000..def67a0fe --- /dev/null +++ b/spec/controllers/api/v1/timelines/direct_controller_spec.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Api::V1::Timelines::DirectController do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses') } + + describe 'GET #show' do + it 'returns 200' do + allow(controller).to receive(:doorkeeper_token) { token } + get :show + + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/controllers/api/v1/timelines/public_controller_spec.rb b/spec/controllers/api/v1/timelines/public_controller_spec.rb index 31e594d22..0892d5db6 100644 --- a/spec/controllers/api/v1/timelines/public_controller_spec.rb +++ b/spec/controllers/api/v1/timelines/public_controller_spec.rb @@ -44,6 +44,10 @@ describe Api::V1::Timelines::PublicController do context 'without a user context' do let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil) } + before do + Setting.timeline_preview = true + end + describe 'GET #show' do it 'returns http success' do get :show diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index bc6c6c0c5..82455d874 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -73,35 +73,42 @@ describe ApplicationController, type: :controller do end end - describe 'helper_method :current_theme' do - it 'returns "default" when theme wasn\'t changed in admin settings' do - allow(Setting).to receive(:default_settings).and_return({ 'theme' => 'default' }) + describe 'helper_method :current_flavour' do + it 'returns "glitch" when theme wasn\'t changed in admin settings' do + allow(Setting).to receive(:default_settings).and_return({ 'skin' => 'default' }) + allow(Setting).to receive(:default_settings).and_return({ 'flavour' => 'glitch' }) - expect(controller.view_context.current_theme).to eq 'default' + expect(controller.view_context.current_flavour).to eq 'glitch' end - it 'returns instances\'s theme when user is not signed in' do - allow(Setting).to receive(:[]).with('theme').and_return 'contrast' + it 'returns instances\'s flavour when user is not signed in' do + allow(Setting).to receive(:[]).with('skin').and_return 'default' + allow(Setting).to receive(:[]).with('flavour').and_return 'vanilla' - expect(controller.view_context.current_theme).to eq 'contrast' + expect(controller.view_context.current_flavour).to eq 'vanilla' end - it 'returns instances\'s default theme when user didn\'t set theme' do + it 'returns instances\'s default flavour when user didn\'t set theme' do current_user = Fabricate(:user) - current_user.settings.update(theme: 'contrast', noindex: false) - current_user.save sign_in current_user - expect(controller.view_context.current_theme).to eq 'contrast' + allow(Setting).to receive(:[]).with('skin').and_return 'default' + allow(Setting).to receive(:[]).with('flavour').and_return 'vanilla' + allow(Setting).to receive(:[]).with('noindex').and_return false + + expect(controller.view_context.current_flavour).to eq 'vanilla' end - it 'returns user\'s theme when it is set' do + it 'returns user\'s flavour when it is set' do current_user = Fabricate(:user) - current_user.settings.update(theme: 'mastodon-light') + current_user.settings.update(flavour: 'glitch') current_user.save sign_in current_user - expect(controller.view_context.current_theme).to eq 'mastodon-light' + allow(Setting).to receive(:[]).with('skin').and_return 'default' + allow(Setting).to receive(:[]).with('flavour').and_return 'vanilla' + + expect(controller.view_context.current_flavour).to eq 'glitch' end end diff --git a/spec/controllers/settings/flavours_controller_spec.rb b/spec/controllers/settings/flavours_controller_spec.rb new file mode 100644 index 000000000..8c7d4a768 --- /dev/null +++ b/spec/controllers/settings/flavours_controller_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe Settings::FlavoursController do + let(:user) { Fabricate(:user) } + + before do + sign_in user, scope: :user + end + + describe 'PUT #update' do + describe 'without a user[setting_skin] parameter' do + it 'sets the selected flavour' do + put :update, params: { flavour: 'schnozzberry' } + + user.reload + + expect(user.setting_flavour).to eq 'schnozzberry' + end + end + + describe 'with a user[setting_skin] parameter' do + before do + put :update, params: { flavour: 'schnozzberry', user: { setting_skin: 'wallpaper' } } + + user.reload + end + + it 'sets the selected flavour' do + expect(user.setting_flavour).to eq 'schnozzberry' + end + + it 'sets the selected skin' do + expect(user.setting_skin).to eq 'wallpaper' + end + end + end +end diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb index 933eff225..1226cfd8e 100644 --- a/spec/lib/activitypub/activity/create_spec.rb +++ b/spec/lib/activitypub/activity/create_spec.rb @@ -294,6 +294,31 @@ RSpec.describe ActivityPub::Activity::Create do end end + context 'limited when direct message assertion is false' do + let(:recipient) { Fabricate(:account) } + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + directMessage: false, + to: ActivityPub::TagManager.instance.uri_for(recipient), + tag: { + type: 'Mention', + href: ActivityPub::TagManager.instance.uri_for(recipient), + }, + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'limited' + end + end + context 'direct' do let(:recipient) { Fabricate(:account) } @@ -318,6 +343,27 @@ RSpec.describe ActivityPub::Activity::Create do end end + context 'direct when direct message assertion is true' do + let(:recipient) { Fabricate(:account) } + + let(:object_json) do + { + id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join, + type: 'Note', + content: 'Lorem ipsum', + to: ActivityPub::TagManager.instance.uri_for(recipient), + directMessage: true, + } + end + + it 'creates status' do + status = sender.statuses.first + + expect(status).to_not be_nil + expect(status.visibility).to eq 'direct' + end + end + context 'as a reply' do let(:original_status) { Fabricate(:status) } diff --git a/spec/lib/advanced_text_formatter_spec.rb b/spec/lib/advanced_text_formatter_spec.rb new file mode 100644 index 000000000..8b27b56a1 --- /dev/null +++ b/spec/lib/advanced_text_formatter_spec.rb @@ -0,0 +1,300 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe AdvancedTextFormatter do + describe '#to_s' do + subject { described_class.new(text, preloaded_accounts: preloaded_accounts, content_type: content_type).to_s } + + let(:preloaded_accounts) { nil } + let(:content_type) { 'text/markdown' } + + context 'given a markdown source' do + let(:content_type) { 'text/markdown' } + + context 'given text containing plain text' do + let(:text) { 'text' } + + it 'paragraphizes the text' do + expect(subject).to eq '<p>text</p>' + end + end + + context 'given text containing line feeds' do + let(:text) { "line\nfeed" } + + it 'removes line feeds' do + expect(subject).to_not include "\n" + end + end + + context 'given some inline code using backticks' do + let(:text) { 'test `foo` bar' } + + it 'formats code using <code>' do + expect(subject).to include 'test <code>foo</code> bar' + end + end + + context 'given a block code' do + let(:text) { "test\n\n```\nint main(void) {\n return 0; // https://joinmastodon.org/foo\n}\n```\n" } + + it 'formats code using <pre> and <code>' do + expect(subject).to include '<pre><code>int main' + end + + it 'does not strip leading spaces' do + expect(subject).to include '> return 0' + end + + it 'does not format links' do + expect(subject).to include 'return 0; // https://joinmastodon.org/foo' + end + end + + context 'given a link in inline code using backticks' do + let(:text) { 'test `https://foo.bar/bar` bar' } + + it 'does not rewrite the link' do + expect(subject).to include 'test <code>https://foo.bar/bar</code> bar' + end + end + + context 'given text with a local-domain mention' do + let(:text) { 'foo https://cb6e6126.ngrok.io/about/more' } + + it 'creates a link' do + expect(subject).to include '<a href="https://cb6e6126.ngrok.io/about/more"' + end + end + + context 'given text containing linkable mentions' do + let(:preloaded_accounts) { [Fabricate(:account, username: 'alice')] } + let(:text) { '@alice' } + + it 'creates a mention link' do + expect(subject).to include '<a href="https://cb6e6126.ngrok.io/@alice" class="u-url mention">@<span>alice</span></a></span>' + end + end + + context 'given text containing unlinkable mentions' do + let(:preloaded_accounts) { [] } + let(:text) { '@alice' } + + it 'does not create a mention link' do + expect(subject).to include '@alice' + end + end + + context 'given a stand-alone medium URL' do + let(:text) { 'https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4' } + + it 'matches the full URL' do + expect(subject).to include 'href="https://hackernoon.com/the-power-to-build-communities-a-response-to-mark-zuckerberg-3f2cac9148a4"' + end + end + + context 'given a stand-alone google URL' do + let(:text) { 'http://google.com' } + + it 'matches the full URL' do + expect(subject).to include 'href="http://google.com"' + end + end + + context 'given a stand-alone URL with a newer TLD' do + let(:text) { 'http://example.gay' } + + it 'matches the full URL' do + expect(subject).to include 'href="http://example.gay"' + end + end + + context 'given a stand-alone IDN URL' do + let(:text) { 'https://nic.みんな/' } + + it 'matches the full URL' do + expect(subject).to include 'href="https://nic.みんな/"' + end + + it 'has display URL' do + expect(subject).to include '<span class="">nic.みんな/</span>' + end + end + + context 'given a URL with a trailing period' do + let(:text) { 'http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona. ' } + + it 'matches the full URL but not the period' do + expect(subject).to include 'href="http://www.mcmansionhell.com/post/156408871451/50-states-of-mcmansion-hell-scottsdale-arizona"' + end + end + + context 'given a URL enclosed with parentheses' do + let(:text) { '(http://google.com/)' } + + it 'matches the full URL but not the parentheses' do + expect(subject).to include 'href="http://google.com/"' + end + end + + context 'given a URL with a trailing exclamation point' do + let(:text) { 'http://www.google.com!' } + + it 'matches the full URL but not the exclamation point' do + expect(subject).to include 'href="http://www.google.com"' + end + end + + context 'given a URL with a trailing single quote' do + let(:text) { "http://www.google.com'" } + + it 'matches the full URL but not the single quote' do + expect(subject).to include 'href="http://www.google.com"' + end + end + end + + context 'given a URL with a trailing angle bracket' do + let(:text) { 'http://www.google.com>' } + + it 'matches the full URL but not the angle bracket' do + expect(subject).to include 'href="http://www.google.com"' + end + end + + context 'given a URL with a query string' do + context 'with escaped unicode character' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink' } + + it 'matches the full URL' do + expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&q=autolink"' + end + end + + context 'with unicode character' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓&q=autolink' } + + it 'matches the full URL' do + expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=✓&q=autolink"' + end + end + + context 'with unicode character at the end' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=✓' } + + it 'matches the full URL' do + expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=✓"' + end + end + + context 'with escaped and not escaped unicode characters' do + let(:text) { 'https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink' } + + it 'preserves escaped unicode characters' do + expect(subject).to include 'href="https://www.ruby-toolbox.com/search?utf8=%E2%9C%93&utf81=✓&q=autolink"' + end + end + + context 'given a URL with parentheses in it' do + let(:text) { 'https://en.wikipedia.org/wiki/Diaspora_(software)' } + + it 'matches the full URL' do + expect(subject).to include 'href="https://en.wikipedia.org/wiki/Diaspora_(software)"' + end + end + + context 'given a URL in quotation marks' do + let(:text) { '"https://example.com/"' } + + it 'does not match the quotation marks' do + expect(subject).to include 'href="https://example.com/"' + end + end + + context 'given a URL in angle brackets' do + let(:text) { '<https://example.com/>' } + + it 'does not match the angle brackets' do + expect(subject).to include 'href="https://example.com/"' + end + end + + context 'given a URL containing unsafe code (XSS attack, invisible part)' do + let(:text) { 'http://example.com/blahblahblahblah/a<script>alert("Hello")</script>' } + + it 'does not include the HTML in the URL' do + expect(subject).to include '"http://example.com/blahblahblahblah/a"' + end + + it 'does not include a script tag' do + expect(subject).to_not include '<script>' + end + end + + context 'given text containing HTML code (script tag)' do + let(:text) { '<script>alert("Hello")</script>' } + + it 'does not include a script tag' do + expect(subject).to_not include '<script>' + end + end + + context 'given text containing HTML (XSS attack)' do + let(:text) { %q{<img src="javascript:alert('XSS');">} } + + it 'does not include the javascript' do + expect(subject).to_not include 'href="javascript:' + end + end + + context 'given an invalid URL' do + let(:text) { 'http://www\.google\.com' } + + it 'outputs the raw URL' do + expect(subject).to eq '<p>http://www\.google\.com</p>' + end + end + + context 'given text containing a hashtag' do + let(:text) { '#hashtag' } + + it 'creates a hashtag link' do + expect(subject).to include '/tags/hashtag" class="mention hashtag" rel="tag">#<span>hashtag</span></a>' + end + end + + context 'given text containing a hashtag with Unicode chars' do + let(:text) { '#hashtagタグ' } + + it 'creates a hashtag link' do + expect(subject).to include '/tags/hashtag%E3%82%BF%E3%82%B0" class="mention hashtag" rel="tag">#<span>hashtagタグ</span></a>' + end + end + + context 'given text with a stand-alone xmpp: URI' do + let(:text) { 'xmpp:user@instance.com' } + + it 'matches the full URI' do + expect(subject).to include 'href="xmpp:user@instance.com"' + end + end + + context 'given text with an xmpp: URI with a query-string' do + let(:text) { 'please join xmpp:muc@instance.com?join right now' } + + it 'matches the full URI' do + expect(subject).to include 'href="xmpp:muc@instance.com?join"' + end + end + + context 'given text containing a magnet: URI' do + let(:text) { 'wikipedia gives this example of a magnet uri: magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a' } + + it 'matches the full URI' do + expect(subject).to include 'href="magnet:?xt=urn:btih:c12fe1c06bba254a9dc9f519b335aa7c1367a88a"' + end + end + end + end +end diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb index 418bdf089..d1e0d60e0 100644 --- a/spec/lib/feed_manager_spec.rb +++ b/spec/lib/feed_manager_spec.rb @@ -134,6 +134,13 @@ RSpec.describe FeedManager do expect(FeedManager.instance.filter?(:home, status, bob)).to be true end + it 'returns true for status by followee mentioning muted account' do + bob.mute!(jeff) + bob.follow!(alice) + status = PostStatusService.new.call(alice, text: 'Hey @jeff') + expect(FeedManager.instance.filter?(:home, status, bob)).to be true + end + it 'returns true for reblog of a personally blocked domain' do alice.block_domain!('example.com') alice.follow!(jeff) diff --git a/spec/lib/sanitize_config_spec.rb b/spec/lib/sanitize_config_spec.rb index a01122bed..29344476f 100644 --- a/spec/lib/sanitize_config_spec.rb +++ b/spec/lib/sanitize_config_spec.rb @@ -3,11 +3,9 @@ require 'rails_helper' describe Sanitize::Config do - describe '::MASTODON_STRICT' do - subject { Sanitize::Config::MASTODON_STRICT } - - it 'converts h1 to p strong' do - expect(Sanitize.fragment('<h1>Foo</h1>', subject)).to eq '<p><strong>Foo</strong></p>' + shared_examples 'common HTML sanitization' do + it 'keeps h1' do + expect(Sanitize.fragment('<h1>Foo</h1>', subject)).to eq '<h1>Foo</h1>' end it 'keeps ul' do @@ -46,4 +44,21 @@ describe Sanitize::Config do expect(Sanitize.fragment('<a href="dweb:/a/foo">Test</a>', subject)).to eq '<a href="dweb:/a/foo" rel="nofollow noopener noreferrer" target="_blank">Test</a>' end end + + describe '::MASTODON_OUTGOING' do + subject { Sanitize::Config::MASTODON_OUTGOING } + + around do |example| + original_web_domain = Rails.configuration.x.web_domain + example.run + Rails.configuration.x.web_domain = original_web_domain + end + + it_behaves_like 'common HTML sanitization' + + it 'keeps a with href and rel tag, not adding to rel or target if url is local' do + Rails.configuration.x.web_domain = 'domain.test' + expect(Sanitize.fragment('<a href="http://domain.test/tags/foo" rel="tag">Test</a>', subject)).to eq '<a href="http://domain.test/tags/foo" rel="tag">Test</a>' + end + end end diff --git a/spec/models/concerns/account_interactions_spec.rb b/spec/models/concerns/account_interactions_spec.rb index 7396af6df..32e08d5f7 100644 --- a/spec/models/concerns/account_interactions_spec.rb +++ b/spec/models/concerns/account_interactions_spec.rb @@ -14,12 +14,19 @@ describe AccountInteractions do subject { Account.following_map(target_account_ids, account_id) } context 'account with Follow' do - it 'returns { target_account_id => true }' do + it 'returns { target_account_id => { reblogs: true } }' do Fabricate(:follow, account: account, target_account: target_account) expect(subject).to eq(target_account_id => { reblogs: true, notify: false, languages: nil }) end end + context 'account with Follow but with reblogs disabled' do + it 'returns { target_account_id => { reblogs: false } }' do + Fabricate(:follow, account: account, target_account: target_account, show_reblogs: false) + expect(subject).to eq(target_account_id => { reblogs: false, notify: false, languages: nil }) + end + end + context 'account without Follow' do it 'returns {}' do expect(subject).to eq({}) diff --git a/spec/models/follow_request_spec.rb b/spec/models/follow_request_spec.rb index 569c160ae..ff81cd78d 100644 --- a/spec/models/follow_request_spec.rb +++ b/spec/models/follow_request_spec.rb @@ -15,6 +15,13 @@ RSpec.describe FollowRequest, type: :model do follow_request.authorize! end + it 'generates a Follow' do + follow_request = Fabricate.create(:follow_request) + follow_request.authorize! + target = follow_request.target_account + expect(follow_request.account.following?(target)).to be true + end + it 'correctly passes show_reblogs when true' do follow_request = Fabricate.create(:follow_request, show_reblogs: true) follow_request.authorize! diff --git a/spec/models/public_feed_spec.rb b/spec/models/public_feed_spec.rb index 5653aee18..d31aba084 100644 --- a/spec/models/public_feed_spec.rb +++ b/spec/models/public_feed_spec.rb @@ -50,6 +50,7 @@ RSpec.describe PublicFeed, type: :model do let!(:remote_account) { Fabricate(:account, domain: 'test.com') } let!(:local_status) { Fabricate(:status, account: local_account) } let!(:remote_status) { Fabricate(:status, account: remote_account) } + let!(:local_only_status) { Fabricate(:status, account: local_account, local_only: true) } context 'without a viewer' do let(:viewer) { nil } @@ -61,6 +62,10 @@ RSpec.describe PublicFeed, type: :model do it 'includes local statuses' do expect(subject).to include(local_status.id) end + + it 'does not include local-only statuses' do + expect(subject).to_not include(local_only_status.id) + end end context 'with a viewer' do @@ -73,6 +78,54 @@ RSpec.describe PublicFeed, type: :model do it 'includes local statuses' do expect(subject).to include(local_status.id) end + + it 'does not include local-only statuses' do + expect(subject).to_not include(local_only_status.id) + end + end + end + + context 'without local_only option but allow_local_only' do + subject { described_class.new(viewer, allow_local_only: true).get(20).map(&:id) } + + let(:viewer) { nil } + + let!(:local_account) { Fabricate(:account, domain: nil) } + let!(:remote_account) { Fabricate(:account, domain: 'test.com') } + let!(:local_status) { Fabricate(:status, account: local_account) } + let!(:remote_status) { Fabricate(:status, account: remote_account) } + let!(:local_only_status) { Fabricate(:status, account: local_account, local_only: true) } + + context 'without a viewer' do + let(:viewer) { nil } + + it 'includes remote instances statuses' do + expect(subject).to include(remote_status.id) + end + + it 'includes local statuses' do + expect(subject).to include(local_status.id) + end + + it 'does not include local-only statuses' do + expect(subject).to_not include(local_only_status.id) + end + end + + context 'with a viewer' do + let(:viewer) { Fabricate(:account, username: 'viewer') } + + it 'includes remote instances statuses' do + expect(subject).to include(remote_status.id) + end + + it 'includes local statuses' do + expect(subject).to include(local_status.id) + end + + it 'includes local-only statuses' do + expect(subject).to include(local_only_status.id) + end end end @@ -83,6 +136,7 @@ RSpec.describe PublicFeed, type: :model do let!(:remote_account) { Fabricate(:account, domain: 'test.com') } let!(:local_status) { Fabricate(:status, account: local_account) } let!(:remote_status) { Fabricate(:status, account: remote_account) } + let!(:local_only_status) { Fabricate(:status, account: local_account, local_only: true) } context 'without a viewer' do let(:viewer) { nil } @@ -91,6 +145,10 @@ RSpec.describe PublicFeed, type: :model do expect(subject).to include(local_status.id) expect(subject).to_not include(remote_status.id) end + + it 'does not include local-only statuses' do + expect(subject).to_not include(local_only_status.id) + end end context 'with a viewer' do @@ -106,6 +164,10 @@ RSpec.describe PublicFeed, type: :model do expect(subject).to include(local_status.id) expect(subject).to_not include(remote_status.id) end + + it 'includes local-only statuses' do + expect(subject).to include(local_only_status.id) + end end end diff --git a/spec/models/status_spec.rb b/spec/models/status_spec.rb index 1e58c6d0d..04e5c26af 100644 --- a/spec/models/status_spec.rb +++ b/spec/models/status_spec.rb @@ -205,6 +205,43 @@ RSpec.describe Status, type: :model do end end + describe 'on create' do + subject { Status.new } + + let(:local_account) { Fabricate(:account, username: 'local', domain: nil) } + let(:remote_account) { Fabricate(:account, username: 'remote', domain: 'example.com') } + + describe 'on a status that ends with the local-only emoji' do + before do + subject.text = "A toot #{subject.local_only_emoji}" + end + + context 'if the status originates from this instance' do + before do + subject.account = local_account + end + + it 'is marked local-only' do + subject.save! + + expect(subject).to be_local_only + end + end + + context 'if the status is remote' do + before do + subject.account = remote_account + end + + it 'is not marked local-only' do + subject.save! + + expect(subject).to_not be_local_only + end + end + end + end + describe '.mutes_map' do subject { Status.mutes_map([status.conversation.id], account) } @@ -253,6 +290,56 @@ RSpec.describe Status, type: :model do end end + describe '.as_direct_timeline' do + subject(:results) { Status.as_direct_timeline(account) } + + let(:account) { Fabricate(:account) } + let(:followed) { Fabricate(:account) } + let(:not_followed) { Fabricate(:account) } + + let!(:self_public_status) { Fabricate(:status, account: account, visibility: :public) } + let!(:self_direct_status) { Fabricate(:status, account: account, visibility: :direct) } + let!(:followed_public_status) { Fabricate(:status, account: followed, visibility: :public) } + let!(:followed_direct_status) { Fabricate(:status, account: followed, visibility: :direct) } + let!(:not_followed_direct_status) { Fabricate(:status, account: not_followed, visibility: :direct) } + + before do + account.follow!(followed) + end + + it 'does not include public statuses from self' do + expect(results).to_not include(self_public_status) + end + + it 'includes direct statuses from self' do + expect(results).to include(self_direct_status) + end + + it 'does not include public statuses from followed' do + expect(results).to_not include(followed_public_status) + end + + it 'does not include direct statuses not mentioning recipient from followed' do + expect(results).to_not include(followed_direct_status) + end + + it 'does not include direct statuses not mentioning recipient from non-followed' do + expect(results).to_not include(not_followed_direct_status) + end + + it 'includes direct statuses mentioning recipient from followed' do + Fabricate(:mention, account: account, status: followed_direct_status) + results2 = Status.as_direct_timeline(account) + expect(results2).to include(followed_direct_status) + end + + it 'includes direct statuses mentioning recipient from non-followed' do + Fabricate(:mention, account: account, status: not_followed_direct_status) + results2 = Status.as_direct_timeline(account) + expect(results2).to include(not_followed_direct_status) + end + end + describe '.tagged_with' do let(:tag1) { Fabricate(:tag) } let(:tag2) { Fabricate(:tag) } diff --git a/spec/models/tag_feed_spec.rb b/spec/models/tag_feed_spec.rb index a498bcf46..d8683b86f 100644 --- a/spec/models/tag_feed_spec.rb +++ b/spec/models/tag_feed_spec.rb @@ -66,5 +66,19 @@ describe TagFeed, type: :service do results = described_class.new(tag1, nil).get(20) expect(results).to include(status) end + + context 'when the feed contains a local-only status' do + let!(:status) { Fabricate(:status, tags: [tag1], local_only: true) } + + it 'does not show local-only statuses without a viewer' do + results = described_class.new(tag1, nil).get(20) + expect(results).to_not include(status) + end + + it 'shows local-only statuses given a viewer' do + results = described_class.new(tag1, account).get(20) + expect(results).to include(status) + end + end end end diff --git a/spec/policies/status_policy_spec.rb b/spec/policies/status_policy_spec.rb index 9ae54780e..38b9c4fdb 100644 --- a/spec/policies/status_policy_spec.rb +++ b/spec/policies/status_policy_spec.rb @@ -81,6 +81,18 @@ RSpec.describe StatusPolicy, type: :model do expect(subject).to_not permit(viewer, status) end + + it 'denies access when local-only and the viewer is not logged in' do + allow(status).to receive(:local_only?).and_return(true) + + expect(subject).to_not permit(nil, status) + end + + it 'denies access when local-only and the viewer is from another domain' do + viewer = Fabricate(:account, domain: 'remote-domain') + allow(status).to receive(:local_only?).and_return(true) + expect(subject).to_not permit(viewer, status) + end end permissions :reblog? do diff --git a/spec/presenters/instance_presenter_spec.rb b/spec/presenters/instance_presenter_spec.rb index 2a1d668ce..f20dce593 100644 --- a/spec/presenters/instance_presenter_spec.rb +++ b/spec/presenters/instance_presenter_spec.rb @@ -108,8 +108,8 @@ describe InstancePresenter do end end - it 'defaults to the core mastodon repo URL' do - expect(instance_presenter.source_url).to eq('https://github.com/mastodon/mastodon') + it 'defaults to the core glitch-soc repo URL' do + expect(instance_presenter.source_url).to eq('https://github.com/glitch-soc/mastodon') end end end diff --git a/spec/validators/status_length_validator_spec.rb b/spec/validators/status_length_validator_spec.rb index e132b5618..7e06b9bd9 100644 --- a/spec/validators/status_length_validator_spec.rb +++ b/spec/validators/status_length_validator_spec.rb @@ -16,26 +16,31 @@ describe StatusLengthValidator do expect(status).to_not receive(:errors) end - it 'adds an error when content warning is over 500 characters' do - status = double(spoiler_text: 'a' * 520, text: '', errors: double(add: nil), local?: true, reblog?: false) + it 'adds an error when content warning is over MAX_CHARS characters' do + chars = StatusLengthValidator::MAX_CHARS + 1 + status = double(spoiler_text: 'a' * chars, text: '', errors: double(add: nil), local?: true, reblog?: false) subject.validate(status) expect(status.errors).to have_received(:add) end - it 'adds an error when text is over 500 characters' do - status = double(spoiler_text: '', text: 'a' * 520, errors: double(add: nil), local?: true, reblog?: false) + it 'adds an error when text is over MAX_CHARS characters' do + chars = StatusLengthValidator::MAX_CHARS + 1 + status = double(spoiler_text: '', text: 'a' * chars, errors: double(add: nil), local?: true, reblog?: false) subject.validate(status) expect(status.errors).to have_received(:add) end - it 'adds an error when text and content warning are over 500 characters total' do - status = double(spoiler_text: 'a' * 250, text: 'b' * 251, errors: double(add: nil), local?: true, reblog?: false) + it 'adds an error when text and content warning are over MAX_CHARS characters total' do + chars1 = 20 + chars2 = StatusLengthValidator::MAX_CHARS + 1 - chars1 + status = double(spoiler_text: 'a' * chars1, text: 'b' * chars2, errors: double(add: nil), local?: true, reblog?: false) subject.validate(status) expect(status.errors).to have_received(:add) end it 'counts URLs as 23 characters flat' do - text = ('a' * 476) + " http://#{'b' * 30}.com/example" + chars = StatusLengthValidator::MAX_CHARS - 1 - 23 + text = ('a' * chars) + " http://#{'b' * 30}.com/example" status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false) subject.validate(status) @@ -58,7 +63,9 @@ describe StatusLengthValidator do end it 'counts only the front part of remote usernames' do - text = ('a' * 475) + " @alice@#{'b' * 30}.com" + username = '@alice' + chars = StatusLengthValidator::MAX_CHARS - 1 - username.length + text = ('a' * chars) + " #{username}@#{'b' * 30}.com" status = double(spoiler_text: '', text: text, errors: double(add: nil), local?: true, reblog?: false) subject.validate(status) |