Compare commits
10 commits
220a2e8fbf
...
e79bf1b1e7
Author | SHA1 | Date | |
---|---|---|---|
|
e79bf1b1e7 | ||
|
9429e30d75 | ||
|
c451bbe249 | ||
|
b87bfb8c96 | ||
|
548bb30b2a | ||
|
9b06c0f24a | ||
|
63c9102f8a | ||
|
dec2796a4a | ||
|
4812832620 | ||
|
23d88aa943 |
28 changed files with 844 additions and 680 deletions
|
@ -15,6 +15,6 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
|||
RUN gem install foreman
|
||||
|
||||
# [Optional] Uncomment this line to install global node packages.
|
||||
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g yarn" 2>&1
|
||||
RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && corepack enable" 2>&1
|
||||
|
||||
COPY welcome-message.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt
|
||||
|
|
|
@ -11,6 +11,7 @@ bundle install
|
|||
git checkout -- Gemfile.lock
|
||||
|
||||
# Fetch Javascript dependencies
|
||||
corepack prepare
|
||||
yarn install --immutable
|
||||
|
||||
# [re]create, migrate, and seed the test database
|
||||
|
|
|
@ -31,4 +31,3 @@ linters:
|
|||
- 'app/views/admin/accounts/_buttons.html.haml'
|
||||
- 'app/views/admin/accounts/_local_account.html.haml'
|
||||
- 'app/views/admin/roles/_form.html.haml'
|
||||
- 'app/views/layouts/application.html.haml'
|
||||
|
|
|
@ -104,12 +104,6 @@ RSpec/LetSetup:
|
|||
- 'spec/services/unsuspend_account_service_spec.rb'
|
||||
- 'spec/workers/scheduler/user_cleanup_scheduler_spec.rb'
|
||||
|
||||
RSpec/MessageChain:
|
||||
Exclude:
|
||||
- 'spec/models/concerns/remotable_spec.rb'
|
||||
- 'spec/models/session_activation_spec.rb'
|
||||
- 'spec/models/setting_spec.rb'
|
||||
|
||||
RSpec/MultipleExpectations:
|
||||
Max: 8
|
||||
|
||||
|
|
|
@ -5,10 +5,11 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
|
|||
before_action :require_user!
|
||||
|
||||
def index
|
||||
accounts = Account.without_suspended.where(id: account_ids).select('id')
|
||||
scope = Account.where(id: account_ids).select('id')
|
||||
scope.merge!(Account.without_suspended) unless truthy_param?(:with_suspended)
|
||||
# .where doesn't guarantee that our results are in the same order
|
||||
# we requested them, so return the "right" order to the requestor.
|
||||
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact
|
||||
@accounts = scope.index_by(&:id).values_at(*account_ids).compact
|
||||
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
|
||||
end
|
||||
|
||||
|
|
|
@ -91,6 +91,14 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
def html_title
|
||||
safe_join(
|
||||
[content_for(:page_title).to_s.chomp, title]
|
||||
.select(&:present?),
|
||||
' - '
|
||||
)
|
||||
end
|
||||
|
||||
def title
|
||||
Rails.env.production? ? site_title : "#{site_title} (Dev)"
|
||||
end
|
||||
|
|
|
@ -460,7 +460,7 @@ export function fetchRelationships(accountIds) {
|
|||
|
||||
dispatch(fetchRelationshipsRequest(newAccountIds));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
|
||||
api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
|
||||
dispatch(fetchRelationshipsSuccess({ relationships: response.data }));
|
||||
}).catch(error => {
|
||||
dispatch(fetchRelationshipsFail(error));
|
||||
|
|
|
@ -42,4 +42,5 @@ export interface ApiAccountJSON {
|
|||
suspended?: boolean;
|
||||
limited?: boolean;
|
||||
memorial?: boolean;
|
||||
hide_collections: boolean;
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ class Account extends ImmutablePureComponent {
|
|||
buttons = <Button title={intl.formatMessage(messages.mute)} onClick={this.handleMute} />;
|
||||
} else if (defaultAction === 'block') {
|
||||
buttons = <Button text={intl.formatMessage(messages.block)} onClick={this.handleBlock} />;
|
||||
} else if (!account.get('moved') || following) {
|
||||
} else if (!account.get('suspended') && !account.get('moved') || following) {
|
||||
buttons = <Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} />;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -289,7 +289,7 @@ class Header extends ImmutablePureComponent {
|
|||
lockedIcon = <Icon id='lock' icon={LockIcon} title={intl.formatMessage(messages.account_locked)} />;
|
||||
}
|
||||
|
||||
if (signedIn && account.get('id') !== me) {
|
||||
if (signedIn && account.get('id') !== me && !account.get('suspended')) {
|
||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
||||
menu.push({ text: intl.formatMessage(messages.direct, { name: account.get('username') }), action: this.props.onDirect });
|
||||
menu.push(null);
|
||||
|
@ -299,7 +299,7 @@ class Header extends ImmutablePureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.openOriginalPage), href: account.get('url') });
|
||||
}
|
||||
|
||||
if ('share' in navigator) {
|
||||
if ('share' in navigator && !account.get('suspended')) {
|
||||
menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare });
|
||||
menu.push(null);
|
||||
}
|
||||
|
@ -347,7 +347,9 @@ class Header extends ImmutablePureComponent {
|
|||
menu.push({ text: intl.formatMessage(messages.block, { name: account.get('username') }), action: this.props.onBlock, dangerous: true });
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
|
||||
if (!account.get('suspended')) {
|
||||
menu.push({ text: intl.formatMessage(messages.report, { name: account.get('username') }), action: this.props.onReport, dangerous: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (signedIn && isRemote) {
|
||||
|
@ -395,7 +397,7 @@ class Header extends ImmutablePureComponent {
|
|||
|
||||
<div className='account__header__image'>
|
||||
<div className='account__header__info'>
|
||||
{!suspended && info}
|
||||
{info}
|
||||
</div>
|
||||
|
||||
{!(suspended || hidden) && <img src={autoPlayGif ? account.get('header') : account.get('header_static')} alt='' className='parallax' />}
|
||||
|
@ -407,18 +409,16 @@ class Header extends ImmutablePureComponent {
|
|||
<Avatar account={suspended || hidden ? undefined : account} size={90} />
|
||||
</a>
|
||||
|
||||
{!suspended && (
|
||||
<div className='account__header__tabs__buttons'>
|
||||
{!hidden && (
|
||||
<>
|
||||
{actionBtn}
|
||||
{bellBtn}
|
||||
</>
|
||||
)}
|
||||
<div className='account__header__tabs__buttons'>
|
||||
{!hidden && (
|
||||
<>
|
||||
{actionBtn}
|
||||
{bellBtn}
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' />
|
||||
</div>
|
||||
)}
|
||||
<DropdownMenuContainer disabled={menu.length === 0} items={menu} icon='ellipsis-v' iconComponent={MoreHorizIcon} size={24} direction='right' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='account__header__tabs__name'>
|
||||
|
|
|
@ -45,6 +45,7 @@ const mapStateToProps = (state, { params: { acct, id } }) => {
|
|||
hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'followers', accountId, 'isLoading'], true),
|
||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||
hideCollections: state.getIn(['accounts', accountId, 'hide_collections'], false),
|
||||
hidden: getAccountHidden(state, accountId),
|
||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||
};
|
||||
|
@ -111,7 +112,7 @@ class Followers extends ImmutablePureComponent {
|
|||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl } = this.props;
|
||||
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl, hideCollections } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
|
@ -137,6 +138,8 @@ class Followers extends ImmutablePureComponent {
|
|||
emptyMessage = <LimitedAccountHint accountId={accountId} />;
|
||||
} else if (blockedBy) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
|
||||
} else if (hideCollections && accountIds.isEmpty()) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_hides_collections' defaultMessage='This user has chosen to not make this information available' />;
|
||||
} else if (remote && accountIds.isEmpty()) {
|
||||
emptyMessage = <RemoteHint url={remoteUrl} />;
|
||||
} else {
|
||||
|
|
|
@ -45,6 +45,7 @@ const mapStateToProps = (state, { params: { acct, id } }) => {
|
|||
hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']),
|
||||
isLoading: state.getIn(['user_lists', 'following', accountId, 'isLoading'], true),
|
||||
suspended: state.getIn(['accounts', accountId, 'suspended'], false),
|
||||
hideCollections: state.getIn(['accounts', accountId, 'hide_collections'], false),
|
||||
hidden: getAccountHidden(state, accountId),
|
||||
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
|
||||
};
|
||||
|
@ -111,7 +112,7 @@ class Following extends ImmutablePureComponent {
|
|||
}, 300, { leading: true });
|
||||
|
||||
render () {
|
||||
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl } = this.props;
|
||||
const { accountId, accountIds, hasMore, blockedBy, isAccount, multiColumn, isLoading, suspended, hidden, remote, remoteUrl, hideCollections } = this.props;
|
||||
|
||||
if (!isAccount) {
|
||||
return (
|
||||
|
@ -137,6 +138,8 @@ class Following extends ImmutablePureComponent {
|
|||
emptyMessage = <LimitedAccountHint accountId={accountId} />;
|
||||
} else if (blockedBy) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />;
|
||||
} else if (hideCollections && accountIds.isEmpty()) {
|
||||
emptyMessage = <FormattedMessage id='empty_column.account_hides_collections' defaultMessage='This user has chosen to not make this information available' />;
|
||||
} else if (remote && accountIds.isEmpty()) {
|
||||
emptyMessage = <RemoteHint url={remoteUrl} />;
|
||||
} else {
|
||||
|
|
|
@ -222,6 +222,7 @@
|
|||
"emoji_button.search_results": "Search results",
|
||||
"emoji_button.symbols": "Symbols",
|
||||
"emoji_button.travel": "Travel & Places",
|
||||
"empty_column.account_hides_collections": "This user has chosen to not make this information available",
|
||||
"empty_column.account_suspended": "Account suspended",
|
||||
"empty_column.account_timeline": "No posts here!",
|
||||
"empty_column.account_unavailable": "Profile unavailable",
|
||||
|
|
|
@ -93,6 +93,7 @@ export const accountDefaultValues: AccountShape = {
|
|||
memorial: false,
|
||||
limited: false,
|
||||
moved: null,
|
||||
hide_collections: false,
|
||||
};
|
||||
|
||||
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);
|
||||
|
|
|
@ -8,7 +8,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
|
|||
|
||||
attributes :id, :username, :acct, :display_name, :locked, :bot, :discoverable, :group, :created_at,
|
||||
:note, :url, :uri, :avatar, :avatar_static, :header, :header_static,
|
||||
:followers_count, :following_count, :statuses_count, :last_status_at
|
||||
:followers_count, :following_count, :statuses_count, :last_status_at, :hide_collections
|
||||
|
||||
has_one :moved_to_account, key: :moved, serializer: REST::AccountSerializer, if: :moved_and_not_nested?
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
%meta{ name: 'theme-color', content: '#191b22' }/
|
||||
%meta{ name: 'apple-mobile-web-app-capable', content: 'yes' }/
|
||||
|
||||
%title= content_for?(:page_title) ? safe_join([yield(:page_title).chomp.html_safe, title], ' - ') : title
|
||||
%title= html_title
|
||||
|
||||
= stylesheet_pack_tag 'common', media: 'all', crossorigin: 'anonymous'
|
||||
= stylesheet_pack_tag current_theme, media: 'all', crossorigin: 'anonymous'
|
||||
|
|
|
@ -43,6 +43,7 @@ end
|
|||
Sidekiq.logger.level = ::Logger.const_get(ENV.fetch('RAILS_LOG_LEVEL', 'info').upcase.to_s)
|
||||
|
||||
SidekiqUniqueJobs.configure do |config|
|
||||
config.enabled = !Rails.env.test?
|
||||
config.reaper = :ruby
|
||||
config.reaper_count = 1000
|
||||
config.reaper_interval = 600
|
||||
|
|
|
@ -301,6 +301,10 @@ namespace :api, format: false do
|
|||
resources :statuses, only: [:show, :destroy]
|
||||
end
|
||||
|
||||
namespace :accounts do
|
||||
resources :relationships, only: :index
|
||||
end
|
||||
|
||||
namespace :admin do
|
||||
resources :accounts, only: [:index]
|
||||
end
|
||||
|
|
|
@ -208,7 +208,7 @@
|
|||
"husky": "^8.0.3",
|
||||
"jest": "^29.5.0",
|
||||
"jest-environment-jsdom": "^29.5.0",
|
||||
"lint-staged": "^13.2.2",
|
||||
"lint-staged": "^15.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"stylelint": "^15.10.1",
|
||||
|
|
|
@ -7,66 +7,44 @@ RSpec.describe AccountsController do
|
|||
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
||||
shared_examples 'unapproved account check' do
|
||||
describe 'unapproved account check' do
|
||||
before { account.user.update(approved: false) }
|
||||
|
||||
it 'returns http not found' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
|
||||
expect(response).to have_http_status(404)
|
||||
%w(html json rss).each do |format|
|
||||
get :show, params: { username: account.username, format: format }
|
||||
expect(response).to have_http_status(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'permanently suspended account check' do
|
||||
describe 'permanently suspended account check' do
|
||||
before do
|
||||
account.suspend!
|
||||
account.deletion_request.destroy
|
||||
end
|
||||
|
||||
it 'returns http gone' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
|
||||
expect(response).to have_http_status(410)
|
||||
%w(html json rss).each do |format|
|
||||
get :show, params: { username: account.username, format: format }
|
||||
expect(response).to have_http_status(410)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'temporarily suspended account check' do |code: 403|
|
||||
describe 'temporarily suspended account check' do
|
||||
before { account.suspend! }
|
||||
|
||||
it 'returns appropriate http response code' do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
{ html: 403, json: 200, rss: 403 }.each do |format, code|
|
||||
get :show, params: { username: account.username, format: format }
|
||||
|
||||
expect(response).to have_http_status(code)
|
||||
expect(response).to have_http_status(code)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
context 'with basic account status checks' do
|
||||
context 'with HTML' do
|
||||
let(:format) { 'html' }
|
||||
|
||||
it_behaves_like 'unapproved account check'
|
||||
it_behaves_like 'permanently suspended account check'
|
||||
it_behaves_like 'temporarily suspended account check'
|
||||
end
|
||||
|
||||
context 'with JSON' do
|
||||
let(:format) { 'json' }
|
||||
|
||||
it_behaves_like 'unapproved account check'
|
||||
it_behaves_like 'permanently suspended account check'
|
||||
it_behaves_like 'temporarily suspended account check', code: 200
|
||||
end
|
||||
|
||||
context 'with RSS' do
|
||||
let(:format) { 'rss' }
|
||||
|
||||
it_behaves_like 'unapproved account check'
|
||||
it_behaves_like 'permanently suspended account check'
|
||||
it_behaves_like 'temporarily suspended account check'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with existing statuses' do
|
||||
let!(:status) { Fabricate(:status, account: account) }
|
||||
let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) }
|
||||
|
@ -227,22 +205,15 @@ RSpec.describe AccountsController do
|
|||
context 'with RSS' do
|
||||
let(:format) { 'rss' }
|
||||
|
||||
shared_examples 'common RSS response' do
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||
end
|
||||
|
||||
context 'with a normal account in an RSS request' do
|
||||
before do
|
||||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'common RSS response'
|
||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||
|
||||
it 'responds with correct statuses', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.body).to include_status_tag(status_media)
|
||||
expect(response.body).to include_status_tag(status_self_reply)
|
||||
expect(response.body).to include_status_tag(status)
|
||||
|
@ -259,9 +230,10 @@ RSpec.describe AccountsController do
|
|||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'common RSS response'
|
||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||
|
||||
it 'responds with correct statuses with replies', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.body).to include_status_tag(status_media)
|
||||
expect(response.body).to include_status_tag(status_reply)
|
||||
expect(response.body).to include_status_tag(status_self_reply)
|
||||
|
@ -278,9 +250,10 @@ RSpec.describe AccountsController do
|
|||
get :show, params: { username: account.username, format: format }
|
||||
end
|
||||
|
||||
it_behaves_like 'common RSS response'
|
||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||
|
||||
it 'responds with correct statuses with media', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.body).to include_status_tag(status_media)
|
||||
expect(response.body).to_not include_status_tag(status_direct)
|
||||
expect(response.body).to_not include_status_tag(status_private)
|
||||
|
@ -302,9 +275,10 @@ RSpec.describe AccountsController do
|
|||
get :show, params: { username: account.username, format: format, tag: tag.to_param }
|
||||
end
|
||||
|
||||
it_behaves_like 'common RSS response'
|
||||
it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie'
|
||||
|
||||
it 'responds with correct statuses with a tag', :aggregate_failures do
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.body).to include_status_tag(status_tag)
|
||||
expect(response.body).to_not include_status_tag(status_direct)
|
||||
expect(response.body).to_not include_status_tag(status_media)
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe Api::V1::Accounts::RelationshipsController do
|
||||
render_views
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:follows') }
|
||||
|
||||
before do
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
describe 'GET #index' do
|
||||
let(:simon) { Fabricate(:account) }
|
||||
let(:lewis) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
user.account.follow!(simon)
|
||||
lewis.follow!(user.account)
|
||||
end
|
||||
|
||||
context 'when provided only one ID' do
|
||||
before do
|
||||
get :index, params: { id: simon.id }
|
||||
end
|
||||
|
||||
it 'returns JSON with correct data', :aggregate_failures do
|
||||
json = body_as_json
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json).to be_a Enumerable
|
||||
expect(json.first[:following]).to be true
|
||||
expect(json.first[:followed_by]).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provided multiple IDs' do
|
||||
before do
|
||||
get :index, params: { id: [simon.id, lewis.id] }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
context 'when there is returned JSON data' do
|
||||
let(:json) { body_as_json }
|
||||
|
||||
it 'returns an enumerable json with correct elements', :aggregate_failures do
|
||||
expect(json).to be_a Enumerable
|
||||
|
||||
expect_simon_item_one
|
||||
expect_lewis_item_two
|
||||
end
|
||||
|
||||
def expect_simon_item_one
|
||||
expect(json.first[:id]).to eq simon.id.to_s
|
||||
expect(json.first[:following]).to be true
|
||||
expect(json.first[:showing_reblogs]).to be true
|
||||
expect(json.first[:followed_by]).to be false
|
||||
expect(json.first[:muting]).to be false
|
||||
expect(json.first[:requested]).to be false
|
||||
expect(json.first[:domain_blocking]).to be false
|
||||
end
|
||||
|
||||
def expect_lewis_item_two
|
||||
expect(json.second[:id]).to eq lewis.id.to_s
|
||||
expect(json.second[:following]).to be false
|
||||
expect(json.second[:showing_reblogs]).to be false
|
||||
expect(json.second[:followed_by]).to be true
|
||||
expect(json.second[:muting]).to be false
|
||||
expect(json.second[:requested]).to be false
|
||||
expect(json.second[:domain_blocking]).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns JSON with correct data on cached requests too' do
|
||||
get :index, params: { id: [simon.id] }
|
||||
|
||||
json = body_as_json
|
||||
|
||||
expect(json).to be_a Enumerable
|
||||
expect(json.first[:following]).to be true
|
||||
expect(json.first[:showing_reblogs]).to be true
|
||||
end
|
||||
|
||||
it 'returns JSON with correct data after change too' do
|
||||
user.account.unfollow!(simon)
|
||||
|
||||
get :index, params: { id: [simon.id] }
|
||||
|
||||
json = body_as_json
|
||||
|
||||
expect(json).to be_a Enumerable
|
||||
expect(json.first[:following]).to be false
|
||||
expect(json.first[:showing_reblogs]).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,5 +2,5 @@
|
|||
|
||||
Fabricator(:session_activation) do
|
||||
user { Fabricate.build(:user) }
|
||||
session_id 'MyString'
|
||||
session_id { sequence(:session_id) { |i| "session_id_#{i}" } }
|
||||
end
|
||||
|
|
|
@ -296,5 +296,51 @@ describe ApplicationHelper do
|
|||
expect(helper.title).to eq 'site title'
|
||||
expect(Rails.env).to have_received(:production?)
|
||||
end
|
||||
|
||||
it 'returns site title with note on non-production environment' do
|
||||
Setting.site_title = 'site title'
|
||||
allow(Rails.env).to receive(:production?).and_return(false)
|
||||
expect(helper.title).to eq 'site title (Dev)'
|
||||
expect(Rails.env).to have_received(:production?)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'html_title' do
|
||||
before do
|
||||
allow(Rails.env).to receive(:production?).and_return(true)
|
||||
end
|
||||
|
||||
around do |example|
|
||||
site_title = Setting.site_title
|
||||
example.run
|
||||
Setting.site_title = site_title
|
||||
end
|
||||
|
||||
context 'with a page_title content_for value' do
|
||||
it 'uses the value in the html title' do
|
||||
Setting.site_title = 'Site Title'
|
||||
helper.content_for(:page_title, 'Test Value')
|
||||
|
||||
expect(helper.html_title).to eq 'Test Value - Site Title'
|
||||
expect(helper.html_title).to be_html_safe
|
||||
end
|
||||
|
||||
it 'removes extra new lines' do
|
||||
Setting.site_title = 'Site Title'
|
||||
helper.content_for(:page_title, "Test Value\n")
|
||||
|
||||
expect(helper.html_title).to eq 'Test Value - Site Title'
|
||||
expect(helper.html_title).to be_html_safe
|
||||
end
|
||||
end
|
||||
|
||||
context 'without any page_title content_for value' do
|
||||
it 'returns the site title' do
|
||||
Setting.site_title = 'Site Title'
|
||||
|
||||
expect(helper.html_title).to eq 'Site Title'
|
||||
expect(helper.html_title).to be_html_safe
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -69,7 +69,9 @@ RSpec.describe Remotable do
|
|||
|
||||
context 'with an invalid URL' do
|
||||
before do
|
||||
allow(Addressable::URI).to receive_message_chain(:parse, :normalize).with(url).with(no_args).and_raise(Addressable::URI::InvalidURIError)
|
||||
parsed = instance_double(Addressable::URI)
|
||||
allow(parsed).to receive(:normalize).with(no_args).and_raise(Addressable::URI::InvalidURIError)
|
||||
allow(Addressable::URI).to receive(:parse).with(url).and_return(parsed)
|
||||
end
|
||||
|
||||
it 'makes no request' do
|
||||
|
|
|
@ -98,34 +98,44 @@ RSpec.describe SessionActivation do
|
|||
end
|
||||
|
||||
context 'when id exists' do
|
||||
let(:id) { '1' }
|
||||
let!(:session_activation) { Fabricate(:session_activation) }
|
||||
|
||||
it 'calls where.destroy_all' do
|
||||
expect(described_class).to receive_message_chain(:where, :destroy_all)
|
||||
.with(session_id: id).with(no_args)
|
||||
it 'destroys the record' do
|
||||
described_class.deactivate(session_activation.session_id)
|
||||
|
||||
described_class.deactivate(id)
|
||||
expect { session_activation.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.purge_old' do
|
||||
it 'calls order.offset.destroy_all' do
|
||||
expect(described_class).to receive_message_chain(:order, :offset, :destroy_all)
|
||||
.with('created_at desc').with(Rails.configuration.x.max_session_activations).with(no_args)
|
||||
around do |example|
|
||||
before = Rails.configuration.x.max_session_activations
|
||||
Rails.configuration.x.max_session_activations = 1
|
||||
example.run
|
||||
Rails.configuration.x.max_session_activations = before
|
||||
end
|
||||
|
||||
let!(:oldest_session_activation) { Fabricate(:session_activation, created_at: 10.days.ago) }
|
||||
let!(:newest_session_activation) { Fabricate(:session_activation, created_at: 5.days.ago) }
|
||||
|
||||
it 'preserves the newest X records based on config' do
|
||||
described_class.purge_old
|
||||
|
||||
expect { oldest_session_activation.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
expect { newest_session_activation.reload }.to_not raise_error
|
||||
end
|
||||
end
|
||||
|
||||
describe '.exclusive' do
|
||||
let(:id) { '1' }
|
||||
let!(:unwanted_session_activation) { Fabricate(:session_activation) }
|
||||
let!(:wanted_session_activation) { Fabricate(:session_activation) }
|
||||
|
||||
it 'calls where.destroy_all' do
|
||||
expect(described_class).to receive_message_chain(:where, :not, :destroy_all)
|
||||
.with(session_id: id).with(no_args)
|
||||
it 'preserves supplied record and destroys all others' do
|
||||
described_class.exclusive(wanted_session_activation.session_id)
|
||||
|
||||
described_class.exclusive(id)
|
||||
expect { unwanted_session_activation.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||
expect { wanted_session_activation.reload }.to_not raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -77,10 +77,13 @@ RSpec.describe Setting do
|
|||
let(:default_value) { { default_value: 'default_value' } }
|
||||
|
||||
it 'calls default_value.with_indifferent_access.merge!' do
|
||||
expect(default_value).to receive_message_chain(:with_indifferent_access, :merge!)
|
||||
.with(db_val.value)
|
||||
indifferent_hash = instance_double(Hash, merge!: nil)
|
||||
allow(default_value).to receive(:with_indifferent_access).and_return(indifferent_hash)
|
||||
|
||||
described_class[key]
|
||||
|
||||
expect(default_value).to have_received(:with_indifferent_access)
|
||||
expect(indifferent_hash).to have_received(:merge!).with(db_val.value)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
133
spec/requests/api/v1/accounts/relationships_spec.rb
Normal file
133
spec/requests/api/v1/accounts/relationships_spec.rb
Normal file
|
@ -0,0 +1,133 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe 'GET /api/v1/accounts/relationships' do
|
||||
subject do
|
||||
get '/api/v1/accounts/relationships', headers: headers, params: params
|
||||
end
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:scopes) { 'read:follows' }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
|
||||
let(:headers) { { 'Authorization' => "Bearer #{token.token}" } }
|
||||
|
||||
let(:simon) { Fabricate(:account) }
|
||||
let(:lewis) { Fabricate(:account) }
|
||||
let(:bob) { Fabricate(:account, suspended: true) }
|
||||
|
||||
before do
|
||||
user.account.follow!(simon)
|
||||
lewis.follow!(user.account)
|
||||
end
|
||||
|
||||
context 'when provided only one ID' do
|
||||
let(:params) { { id: simon.id } }
|
||||
|
||||
it 'returns JSON with correct data', :aggregate_failures do
|
||||
subject
|
||||
|
||||
json = body_as_json
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json).to be_a Enumerable
|
||||
expect(json.first[:following]).to be true
|
||||
expect(json.first[:followed_by]).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provided multiple IDs' do
|
||||
let(:params) { { id: [simon.id, lewis.id, bob.id] } }
|
||||
|
||||
context 'when there is returned JSON data' do
|
||||
let(:json) { body_as_json }
|
||||
|
||||
context 'with default parameters' do
|
||||
it 'returns an enumerable json with correct elements, excluding suspended accounts', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json).to be_a Enumerable
|
||||
expect(json.size).to eq 2
|
||||
|
||||
expect_simon_item_one
|
||||
expect_lewis_item_two
|
||||
end
|
||||
end
|
||||
|
||||
context 'with `with_suspended` parameter' do
|
||||
let(:params) { { id: [simon.id, lewis.id, bob.id], with_suspended: true } }
|
||||
|
||||
it 'returns an enumerable json with correct elements, including suspended accounts', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(json).to be_a Enumerable
|
||||
expect(json.size).to eq 3
|
||||
|
||||
expect_simon_item_one
|
||||
expect_lewis_item_two
|
||||
expect_bob_item_three
|
||||
end
|
||||
end
|
||||
|
||||
def expect_simon_item_one
|
||||
expect(json.first[:id]).to eq simon.id.to_s
|
||||
expect(json.first[:following]).to be true
|
||||
expect(json.first[:showing_reblogs]).to be true
|
||||
expect(json.first[:followed_by]).to be false
|
||||
expect(json.first[:muting]).to be false
|
||||
expect(json.first[:requested]).to be false
|
||||
expect(json.first[:domain_blocking]).to be false
|
||||
end
|
||||
|
||||
def expect_lewis_item_two
|
||||
expect(json.second[:id]).to eq lewis.id.to_s
|
||||
expect(json.second[:following]).to be false
|
||||
expect(json.second[:showing_reblogs]).to be false
|
||||
expect(json.second[:followed_by]).to be true
|
||||
expect(json.second[:muting]).to be false
|
||||
expect(json.second[:requested]).to be false
|
||||
expect(json.second[:domain_blocking]).to be false
|
||||
end
|
||||
|
||||
def expect_bob_item_three
|
||||
expect(json.third[:id]).to eq bob.id.to_s
|
||||
expect(json.third[:following]).to be false
|
||||
expect(json.third[:showing_reblogs]).to be false
|
||||
expect(json.third[:followed_by]).to be false
|
||||
expect(json.third[:muting]).to be false
|
||||
expect(json.third[:requested]).to be false
|
||||
expect(json.third[:domain_blocking]).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns JSON with correct data on cached requests too' do
|
||||
subject
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
json = body_as_json
|
||||
|
||||
expect(json).to be_a Enumerable
|
||||
expect(json.first[:following]).to be true
|
||||
expect(json.first[:showing_reblogs]).to be true
|
||||
end
|
||||
|
||||
it 'returns JSON with correct data after change too' do
|
||||
subject
|
||||
user.account.unfollow!(simon)
|
||||
|
||||
get '/api/v1/accounts/relationships', headers: headers, params: { id: [simon.id] }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
json = body_as_json
|
||||
|
||||
expect(json).to be_a Enumerable
|
||||
expect(json.first[:following]).to be false
|
||||
expect(json.first[:showing_reblogs]).to be false
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue