about summary refs log tree commit diff
diff options
context:
space:
mode:
authorThibaut Girka <thib@sitedethib.com>2020-09-07 09:21:38 +0200
committerThibaut Girka <thib@sitedethib.com>2020-09-07 09:21:38 +0200
commite5f934ddf0aa4ef9efbf45751bc00bebff768d99 (patch)
tree322231d7d50704edf8da762b69ee22c9850f2ce3
parentd967251fdc3826ad27d30e55258cfa4cdfd7c871 (diff)
parenta6121a159c5305ea9faa95743a50babb23ab41cd (diff)
Merge branch 'master' into glitch-soc/merge-upstream
Conflicts:
- `app/controllers/activitypub/collections_controller.rb`:
  Conflict caused because we have additional code to make sure pinned
  local-only toots don't get rendered on the ActivityPub endpoints.
  Ported upstream changes.
-rw-r--r--app/controllers/activitypub/collections_controller.rb32
-rw-r--r--app/controllers/activitypub/outboxes_controller.rb20
-rw-r--r--app/controllers/api/v1/accounts/featured_tags_controller.rb22
-rw-r--r--app/controllers/instance_actors_controller.rb2
-rw-r--r--app/javascript/mastodon/actions/accounts.js37
-rw-r--r--app/javascript/mastodon/actions/statuses.js64
-rw-r--r--app/javascript/styles/mastodon-light/diff.scss8
-rw-r--r--app/lib/activitypub/adapter.rb2
-rw-r--r--app/serializers/activitypub/actor_serializer.rb8
-rw-r--r--app/serializers/activitypub/collection_serializer.rb2
-rw-r--r--app/serializers/activitypub/hashtag_serializer.rb23
-rw-r--r--app/serializers/rest/account_featured_tag_serializer.rb19
-rw-r--r--config/routes.rb2
-rw-r--r--lib/mastodon/media_cli.rb3
-rw-r--r--spec/lib/feed_manager_spec.rb4
-rw-r--r--spec/lib/spam_check_spec.rb8
-rw-r--r--spec/services/unallow_domain_service_spec.rb6
-rw-r--r--spec/workers/scheduler/feed_cleanup_scheduler_spec.rb4
18 files changed, 128 insertions, 138 deletions
diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb
index e62fba748..00f3d3cba 100644
--- a/app/controllers/activitypub/collections_controller.rb
+++ b/app/controllers/activitypub/collections_controller.rb
@@ -12,7 +12,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
 
   def show
     expires_in 3.minutes, public: public_fetch_mode?
-    render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
+    render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
   end
 
   private
@@ -20,17 +20,9 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
   def set_items
     case params[:id]
     when 'featured'
-      @items = begin
-        # Because in public fetch mode we cache the response, there would be no
-        # benefit from performing the check below, since a blocked account or domain
-        # would likely be served the cache from the reverse proxy anyway
-
-        if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
-          []
-        else
-          cache_collection(@account.pinned_statuses.not_local_only, Status)
-        end
-      end
+      @items = for_signed_account { cache_collection(@account.pinned_statuses.not_local_only, Status) }
+    when 'tags'
+      @items = for_signed_account { @account.featured_tags }
     when 'devices'
       @items = @account.devices
     else
@@ -40,7 +32,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
 
   def set_size
     case params[:id]
-    when 'featured', 'devices'
+    when 'featured', 'devices', 'tags'
       @size = @items.size
     else
       not_found
@@ -51,7 +43,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
     case params[:id]
     when 'featured'
       @type = :ordered
-    when 'devices'
+    when 'devices', 'tags'
       @type = :unordered
     else
       not_found
@@ -66,4 +58,16 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
       items: @items
     )
   end
+
+  def for_signed_account
+    # Because in public fetch mode we cache the response, there would be no
+    # benefit from performing the check below, since a blocked account or domain
+    # would likely be served the cache from the reverse proxy anyway
+
+    if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
+      []
+    else
+      yield
+    end
+  end
 end
diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb
index c33c15255..e066860bf 100644
--- a/app/controllers/activitypub/outboxes_controller.rb
+++ b/app/controllers/activitypub/outboxes_controller.rb
@@ -20,9 +20,9 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
   def outbox_presenter
     if page_requested?
       ActivityPub::CollectionPresenter.new(
-        id: account_outbox_url(@account, page_params),
+        id: outbox_url(page_params),
         type: :ordered,
-        part_of: account_outbox_url(@account),
+        part_of: outbox_url,
         prev: prev_page,
         next: next_page,
         items: @statuses
@@ -32,12 +32,20 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
         id: account_outbox_url(@account),
         type: :ordered,
         size: @account.statuses_count,
-        first: account_outbox_url(@account, page: true),
-        last: account_outbox_url(@account, page: true, min_id: 0)
+        first: outbox_url(page: true),
+        last: outbox_url(page: true, min_id: 0)
       )
     end
   end
 
+  def outbox_url(**kwargs)
+    if params[:account_username].present?
+      account_outbox_url(@account, **kwargs)
+    else
+      instance_actor_outbox_url(**kwargs)
+    end
+  end
+
   def next_page
     account_outbox_url(@account, page: true, max_id: @statuses.last.id) if @statuses.size == LIMIT
   end
@@ -65,4 +73,8 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
   def page_params
     { page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact
   end
+
+  def set_account
+    @account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
+  end
 end
diff --git a/app/controllers/api/v1/accounts/featured_tags_controller.rb b/app/controllers/api/v1/accounts/featured_tags_controller.rb
new file mode 100644
index 000000000..d6277261d
--- /dev/null
+++ b/app/controllers/api/v1/accounts/featured_tags_controller.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class Api::V1::Accounts::FeaturedTagsController < Api::BaseController
+  before_action :set_account
+  before_action :set_featured_tags
+
+  respond_to :json
+
+  def index
+    render json: @featured_tags, each_serializer: REST::AccountFeaturedTagSerializer
+  end
+
+  private
+
+  def set_account
+    @account = Account.find(params[:account_id])
+  end
+
+  def set_featured_tags
+    @featured_tags = @account.featured_tags
+  end
+end
diff --git a/app/controllers/instance_actors_controller.rb b/app/controllers/instance_actors_controller.rb
index 6f02d6a35..4b074ca19 100644
--- a/app/controllers/instance_actors_controller.rb
+++ b/app/controllers/instance_actors_controller.rb
@@ -17,6 +17,6 @@ class InstanceActorsController < ApplicationController
   end
 
   def restrict_fields_to
-    %i(id type preferred_username inbox public_key endpoints url manually_approves_followers)
+    %i(id type preferred_username inbox outbox public_key endpoints url manually_approves_followers)
   end
 end
diff --git a/app/javascript/mastodon/actions/accounts.js b/app/javascript/mastodon/actions/accounts.js
index cb2c682a4..d28f7dad8 100644
--- a/app/javascript/mastodon/actions/accounts.js
+++ b/app/javascript/mastodon/actions/accounts.js
@@ -1,6 +1,5 @@
 import api, { getLinks } from '../api';
-import openDB from '../storage/db';
-import { importAccount, importFetchedAccount, importFetchedAccounts } from './importer';
+import { importFetchedAccount, importFetchedAccounts } from './importer';
 
 export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST';
 export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS';
@@ -74,45 +73,13 @@ export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST';
 export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS';
 export const FOLLOW_REQUEST_REJECT_FAIL    = 'FOLLOW_REQUEST_REJECT_FAIL';
 
-function getFromDB(dispatch, getState, index, id) {
-  return new Promise((resolve, reject) => {
-    const request = index.get(id);
-
-    request.onerror = reject;
-
-    request.onsuccess = () => {
-      if (!request.result) {
-        reject();
-        return;
-      }
-
-      dispatch(importAccount(request.result));
-      resolve(request.result.moved && getFromDB(dispatch, getState, index, request.result.moved));
-    };
-  });
-}
-
 export function fetchAccount(id) {
   return (dispatch, getState) => {
     dispatch(fetchRelationships([id]));
-
-    if (getState().getIn(['accounts', id], null) !== null) {
-      return;
-    }
-
     dispatch(fetchAccountRequest(id));
 
-    openDB().then(db => getFromDB(
-      dispatch,
-      getState,
-      db.transaction('accounts', 'read').objectStore('accounts').index('id'),
-      id,
-    ).then(() => db.close(), error => {
-      db.close();
-      throw error;
-    })).catch(() => api(getState).get(`/api/v1/accounts/${id}`).then(response => {
+    api(getState).get(`/api/v1/accounts/${id}`).then(response => {
       dispatch(importFetchedAccount(response.data));
-    })).then(() => {
       dispatch(fetchAccountSuccess());
     }).catch(error => {
       dispatch(fetchAccountFail(id, error));
diff --git a/app/javascript/mastodon/actions/statuses.js b/app/javascript/mastodon/actions/statuses.js
index e565e0b0a..3fc7c0702 100644
--- a/app/javascript/mastodon/actions/statuses.js
+++ b/app/javascript/mastodon/actions/statuses.js
@@ -1,9 +1,7 @@
 import api from '../api';
-import openDB from '../storage/db';
-import { evictStatus } from '../storage/modifier';
 
 import { deleteFromTimelines } from './timelines';
-import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus, importFetchedAccount } from './importer';
+import { importFetchedStatus, importFetchedStatuses, importFetchedAccount } from './importer';
 import { ensureComposeIsVisible } from './compose';
 
 export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
@@ -40,48 +38,6 @@ export function fetchStatusRequest(id, skipLoading) {
   };
 };
 
-function getFromDB(dispatch, getState, accountIndex, index, id) {
-  return new Promise((resolve, reject) => {
-    const request = index.get(id);
-
-    request.onerror = reject;
-
-    request.onsuccess = () => {
-      const promises = [];
-
-      if (!request.result) {
-        reject();
-        return;
-      }
-
-      dispatch(importStatus(request.result));
-
-      if (getState().getIn(['accounts', request.result.account], null) === null) {
-        promises.push(new Promise((accountResolve, accountReject) => {
-          const accountRequest = accountIndex.get(request.result.account);
-
-          accountRequest.onerror = accountReject;
-          accountRequest.onsuccess = () => {
-            if (!request.result) {
-              accountReject();
-              return;
-            }
-
-            dispatch(importAccount(accountRequest.result));
-            accountResolve();
-          };
-        }));
-      }
-
-      if (request.result.reblog && getState().getIn(['statuses', request.result.reblog], null) === null) {
-        promises.push(getFromDB(dispatch, getState, accountIndex, index, request.result.reblog));
-      }
-
-      resolve(Promise.all(promises));
-    };
-  });
-}
-
 export function fetchStatus(id) {
   return (dispatch, getState) => {
     const skipLoading = getState().getIn(['statuses', id], null) !== null;
@@ -94,23 +50,10 @@ export function fetchStatus(id) {
 
     dispatch(fetchStatusRequest(id, skipLoading));
 
-    openDB().then(db => {
-      const transaction = db.transaction(['accounts', 'statuses'], 'read');
-      const accountIndex = transaction.objectStore('accounts').index('id');
-      const index = transaction.objectStore('statuses').index('id');
-
-      return getFromDB(dispatch, getState, accountIndex, index, id).then(() => {
-        db.close();
-      }, error => {
-        db.close();
-        throw error;
-      });
-    }).then(() => {
-      dispatch(fetchStatusSuccess(skipLoading));
-    }, () => api(getState).get(`/api/v1/statuses/${id}`).then(response => {
+    api(getState).get(`/api/v1/statuses/${id}`).then(response => {
       dispatch(importFetchedStatus(response.data));
       dispatch(fetchStatusSuccess(skipLoading));
-    })).catch(error => {
+    }).catch(error => {
       dispatch(fetchStatusFail(id, error, skipLoading));
     });
   };
@@ -152,7 +95,6 @@ export function deleteStatus(id, routerHistory, withRedraft = false) {
     dispatch(deleteStatusRequest(id));
 
     api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
-      evictStatus(id);
       dispatch(deleteStatusSuccess(id));
       dispatch(deleteFromTimelines(id));
       dispatch(importFetchedAccount(response.data.account));
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index 8c8d69fc4..6b81e7623 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -256,14 +256,6 @@ html {
   background: $ui-base-color;
 }
 
-.status.status-direct {
-  background: lighten($ui-base-color, 4%);
-}
-
-.focusable:focus .status.status-direct {
-  background: lighten($ui-base-color, 8%);
-}
-
 .detailed-status,
 .detailed-status__action-bar {
   background: $white;
diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb
index 712c48823..4e406b41d 100644
--- a/app/lib/activitypub/adapter.rb
+++ b/app/lib/activitypub/adapter.rb
@@ -14,7 +14,7 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
     moved_to: { 'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' } },
     also_known_as: { 'alsoKnownAs' => { '@id' => 'as:alsoKnownAs', '@type' => '@id' } },
     emoji: { 'toot' => 'http://joinmastodon.org/ns#', 'Emoji' => 'toot:Emoji' },
-    featured: { 'toot' => 'http://joinmastodon.org/ns#', 'featured' => { '@id' => 'toot:featured', '@type' => '@id' } },
+    featured: { 'toot' => 'http://joinmastodon.org/ns#', 'featured' => { '@id' => 'toot:featured', '@type' => '@id' }, 'featuredTags' => { '@id' => 'toot:featuredTags', '@type' => '@id' } },
     property_value: { 'schema' => 'http://schema.org#', 'PropertyValue' => 'schema:PropertyValue', 'value' => 'schema:value' },
     atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' },
     conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' },
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 627d4446b..5d2741b17 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -10,7 +10,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
                      :discoverable, :olm
 
   attributes :id, :type, :following, :followers,
-             :inbox, :outbox, :featured,
+             :inbox, :outbox, :featured, :featured_tags,
              :preferred_username, :name, :summary,
              :url, :manually_approves_followers,
              :discoverable
@@ -74,13 +74,17 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
   end
 
   def outbox
-    account_outbox_url(object)
+    object.instance_actor? ? instance_actor_outbox_url : account_outbox_url(object)
   end
 
   def featured
     account_collection_url(object, :featured)
   end
 
+  def featured_tags
+    account_collection_url(object, :tags)
+  end
+
   def endpoints
     object
   end
diff --git a/app/serializers/activitypub/collection_serializer.rb b/app/serializers/activitypub/collection_serializer.rb
index ea7af5433..34026a6b5 100644
--- a/app/serializers/activitypub/collection_serializer.rb
+++ b/app/serializers/activitypub/collection_serializer.rb
@@ -16,6 +16,8 @@ class ActivityPub::CollectionSerializer < ActivityPub::Serializer
       ActivityPub::NoteSerializer
     when 'Device'
       ActivityPub::DeviceSerializer
+    when 'FeaturedTag'
+      ActivityPub::HashtagSerializer
     when 'ActivityPub::CollectionPresenter'
       ActivityPub::CollectionSerializer
     when 'String'
diff --git a/app/serializers/activitypub/hashtag_serializer.rb b/app/serializers/activitypub/hashtag_serializer.rb
new file mode 100644
index 000000000..1a56e4dfe
--- /dev/null
+++ b/app/serializers/activitypub/hashtag_serializer.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class ActivityPub::HashtagSerializer < ActivityPub::Serializer
+  include RoutingHelper
+
+  attributes :type, :href, :name
+
+  def type
+    'Hashtag'
+  end
+
+  def name
+    "##{object.name}"
+  end
+
+  def href
+    if object.class.name == 'FeaturedTag'
+      short_account_tag_url(object.account, object.tag)
+    else
+      tag_url(object)
+    end
+  end
+end
diff --git a/app/serializers/rest/account_featured_tag_serializer.rb b/app/serializers/rest/account_featured_tag_serializer.rb
new file mode 100644
index 000000000..d8d5fd68c
--- /dev/null
+++ b/app/serializers/rest/account_featured_tag_serializer.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class REST::AccountFeaturedTagSerializer < ActiveModel::Serializer
+  include RoutingHelper
+
+  attributes :id, :name, :url
+
+  def id
+    object.tag.id.to_s
+  end
+
+  def name
+    "##{object.name}"
+  end
+
+  def url
+    short_account_tag_url(object.account, object.tag)
+  end
+end
diff --git a/config/routes.rb b/config/routes.rb
index ce8057031..48d2718c8 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -37,6 +37,7 @@ Rails.application.routes.draw do
 
   resource :instance_actor, path: 'actor', only: [:show] do
     resource :inbox, only: [:create], module: :activitypub
+    resource :outbox, only: [:show], module: :activitypub
   end
 
   devise_scope :user do
@@ -438,6 +439,7 @@ Rails.application.routes.draw do
         resources :following, only: :index, controller: 'accounts/following_accounts'
         resources :lists, only: :index, controller: 'accounts/lists'
         resources :identity_proofs, only: :index, controller: 'accounts/identity_proofs'
+        resources :featured_tags, only: :index, controller: 'accounts/featured_tags'
 
         member do
           post :follow
diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb
index 31135f7fc..5f4a414b1 100644
--- a/lib/mastodon/media_cli.rb
+++ b/lib/mastodon/media_cli.rb
@@ -67,6 +67,7 @@ module Mastodon
       when :s3
         paperclip_instance = MediaAttachment.new.file
         s3_interface       = paperclip_instance.s3_interface
+        s3_permissions     = Paperclip::Attachment.default_options[:s3_permissions]
         bucket             = s3_interface.bucket(Paperclip::Attachment.default_options[:s3_credentials][:bucket])
         last_key           = options[:start_after]
 
@@ -87,7 +88,7 @@ module Mastodon
           record_map = preload_records_from_mixed_objects(objects)
 
           objects.each do |object|
-            object.acl.put(acl: 'public-read') if options[:fix_permissions] && !options[:dry_run]
+            object.acl.put(acl: s3_permissions) if options[:fix_permissions] && !options[:dry_run]
 
             path_segments = object.key.split('/')
             path_segments.delete('cache')
diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb
index 40e8214b6..bb5bdfdc5 100644
--- a/spec/lib/feed_manager_spec.rb
+++ b/spec/lib/feed_manager_spec.rb
@@ -448,7 +448,7 @@ RSpec.describe FeedManager do
       FeedManager.instance.push_to_home(receiver, another_status)
 
       # We should have a tracking set and an entry in reblogs.
-      expect(Redis.current.exists(reblog_set_key)).to be true
+      expect(Redis.current.exists?(reblog_set_key)).to be true
       expect(Redis.current.zrange(reblogs_key, 0, -1)).to eq [reblogged.id.to_s]
 
       # Push everything off the end of the feed.
@@ -461,7 +461,7 @@ RSpec.describe FeedManager do
       FeedManager.instance.trim('home', receiver.id)
 
       # We should not have any reblog tracking data.
-      expect(Redis.current.exists(reblog_set_key)).to be false
+      expect(Redis.current.exists?(reblog_set_key)).to be false
       expect(Redis.current.zrange(reblogs_key, 0, -1)).to be_empty
     end
   end
diff --git a/spec/lib/spam_check_spec.rb b/spec/lib/spam_check_spec.rb
index d4d66a499..159d83257 100644
--- a/spec/lib/spam_check_spec.rb
+++ b/spec/lib/spam_check_spec.rb
@@ -150,9 +150,9 @@ RSpec.describe SpamCheck do
     let(:redis_key) { spam_check.send(:redis_key) }
 
     it 'remembers' do
-      expect(Redis.current.exists(redis_key)).to be true
+      expect(Redis.current.exists?(redis_key)).to be true
       spam_check.remember!
-      expect(Redis.current.exists(redis_key)).to be true
+      expect(Redis.current.exists?(redis_key)).to be true
     end
   end
 
@@ -166,9 +166,9 @@ RSpec.describe SpamCheck do
     end
 
     it 'resets' do
-      expect(Redis.current.exists(redis_key)).to be true
+      expect(Redis.current.exists?(redis_key)).to be true
       spam_check.reset!
-      expect(Redis.current.exists(redis_key)).to be false
+      expect(Redis.current.exists?(redis_key)).to be false
     end
   end
 
diff --git a/spec/services/unallow_domain_service_spec.rb b/spec/services/unallow_domain_service_spec.rb
index 559e152fb..b93945b9a 100644
--- a/spec/services/unallow_domain_service_spec.rb
+++ b/spec/services/unallow_domain_service_spec.rb
@@ -55,9 +55,9 @@ RSpec.describe UnallowDomainService, type: :service do
       end
 
       it 'removes the remote accounts\'s statuses and media attachments' do
-        expect { bad_status1.reload }.to_not raise_exception ActiveRecord::RecordNotFound
-        expect { bad_status2.reload }.to_not raise_exception ActiveRecord::RecordNotFound
-        expect { bad_attachment.reload }.to_not raise_exception ActiveRecord::RecordNotFound
+        expect { bad_status1.reload }.to_not raise_error
+        expect { bad_status2.reload }.to_not raise_error
+        expect { bad_attachment.reload }.to_not raise_error
       end
     end
   end
diff --git a/spec/workers/scheduler/feed_cleanup_scheduler_spec.rb b/spec/workers/scheduler/feed_cleanup_scheduler_spec.rb
index 7fae680ba..914eed829 100644
--- a/spec/workers/scheduler/feed_cleanup_scheduler_spec.rb
+++ b/spec/workers/scheduler/feed_cleanup_scheduler_spec.rb
@@ -16,8 +16,8 @@ describe Scheduler::FeedCleanupScheduler do
 
     expect(Redis.current.zcard(feed_key_for(inactive_user))).to eq 0
     expect(Redis.current.zcard(feed_key_for(active_user))).to eq 1
-    expect(Redis.current.exists(feed_key_for(inactive_user, 'reblogs'))).to be false
-    expect(Redis.current.exists(feed_key_for(inactive_user, 'reblogs:2'))).to be false
+    expect(Redis.current.exists?(feed_key_for(inactive_user, 'reblogs'))).to be false
+    expect(Redis.current.exists?(feed_key_for(inactive_user, 'reblogs:2'))).to be false
   end
 
   def feed_key_for(user, subtype = nil)