Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- `app/controllers/application_controller.rb`:
  Conflict due to theming system.
- `app/controllers/oauth/authorizations_controller.rb`:
  Conflict due to theming system.
This commit is contained in:
Thibaut Girka
2020-01-04 22:54:06 +01:00
72 changed files with 708 additions and 376 deletions

View File

@@ -85,10 +85,7 @@ describe Api::ProofsController do
end
it 'has the correct avatar url' do
first_part = 'https://cb6e6126.ngrok.io/system/accounts/avatars/'
last_part = 'original/avatar.gif'
expect(body_as_json[:avatar]).to match /#{Regexp.quote(first_part)}(?:\d{3,5}\/){3}#{Regexp.quote(last_part)}/
expect(body_as_json[:avatar]).to match "https://cb6e6126.ngrok.io#{alice.avatar.url}"
end
end
end

View File

@@ -3,19 +3,38 @@ require 'rails_helper'
describe Api::V1::Accounts::FollowerAccountsController do
render_views
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:account) { Fabricate(:account) }
let(:alice) { Fabricate(:account) }
let(:bob) { Fabricate(:account) }
before do
Fabricate(:follow, target_account: user.account)
alice.follow!(account)
bob.follow!(account)
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
it 'returns http success' do
get :index, params: { account_id: user.account.id, limit: 1 }
get :index, params: { account_id: account.id, limit: 2 }
expect(response).to have_http_status(200)
end
it 'returns accounts following the given account' do
get :index, params: { account_id: account.id, limit: 2 }
expect(body_as_json.size).to eq 2
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
end
it 'does not return blocked users' do
user.account.block!(bob)
get :index, params: { account_id: account.id, limit: 2 }
expect(body_as_json.size).to eq 1
expect(body_as_json[0][:id]).to eq alice.id.to_s
end
end
end

View File

@@ -3,19 +3,38 @@ require 'rails_helper'
describe Api::V1::Accounts::FollowingAccountsController do
render_views
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
let(:account) { Fabricate(:account) }
let(:alice) { Fabricate(:account) }
let(:bob) { Fabricate(:account) }
before do
Fabricate(:follow, account: user.account)
account.follow!(alice)
account.follow!(bob)
allow(controller).to receive(:doorkeeper_token) { token }
end
describe 'GET #index' do
it 'returns http success' do
get :index, params: { account_id: user.account.id, limit: 1 }
get :index, params: { account_id: account.id, limit: 2 }
expect(response).to have_http_status(200)
end
it 'returns accounts followed by the given account' do
get :index, params: { account_id: account.id, limit: 2 }
expect(body_as_json.size).to eq 2
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
end
it 'does not return blocked users' do
user.account.block!(bob)
get :index, params: { account_id: account.id, limit: 2 }
expect(body_as_json.size).to eq 1
expect(body_as_json[0][:id]).to eq alice.id.to_s
end
end
end

View File

@@ -6,6 +6,8 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') }
let(:alice) { Fabricate(:account) }
let(:bob) { Fabricate(:account) }
context 'with an oauth token' do
before do
@@ -16,14 +18,28 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control
let(:status) { Fabricate(:status, account: user.account) }
before do
Fabricate(:favourite, status: status)
Favourite.create!(account: alice, status: status)
Favourite.create!(account: bob, status: status)
end
it 'returns http success' do
get :index, params: { status_id: status.id, limit: 1 }
get :index, params: { status_id: status.id, limit: 2 }
expect(response).to have_http_status(200)
expect(response.headers['Link'].links.size).to eq(2)
end
it 'returns accounts who favorited the status' do
get :index, params: { status_id: status.id, limit: 2 }
expect(body_as_json.size).to eq 2
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
end
it 'does not return blocked users' do
user.account.block!(bob)
get :index, params: { status_id: status.id, limit: 2 }
expect(body_as_json.size).to eq 1
expect(body_as_json[0][:id]).to eq alice.id.to_s
end
end
end

View File

@@ -6,6 +6,8 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') }
let(:alice) { Fabricate(:account) }
let(:bob) { Fabricate(:account) }
context 'with an oauth token' do
before do
@@ -16,14 +18,28 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll
let(:status) { Fabricate(:status, account: user.account) }
before do
Fabricate(:status, reblog_of_id: status.id)
Fabricate(:status, account: alice, reblog_of_id: status.id)
Fabricate(:status, account: bob, reblog_of_id: status.id)
end
it 'returns http success' do
get :index, params: { status_id: status.id, limit: 1 }
get :index, params: { status_id: status.id, limit: 2 }
expect(response).to have_http_status(200)
expect(response.headers['Link'].links.size).to eq(2)
end
it 'returns accounts who reblogged the status' do
get :index, params: { status_id: status.id, limit: 2 }
expect(body_as_json.size).to eq 2
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
end
it 'does not return blocked users' do
user.account.block!(bob)
get :index, params: { status_id: status.id, limit: 2 }
expect(body_as_json.size).to eq 1
expect(body_as_json[0][:id]).to eq alice.id.to_s
end
end
end

View File

@@ -1,30 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
describe ApplicationController, type: :controller do
controller do
include ObfuscateFilename
obfuscate_filename :file
def file
render plain: params[:file]&.original_filename
end
end
before do
routes.draw { get 'file' => 'anonymous#file' }
end
it 'obfusticates filename if the given parameter is specified' do
file = fixture_file_upload('files/imports.txt', 'text/plain')
post 'file', params: { file: file }
expect(response.body).to end_with '.txt'
expect(response.body).not_to include 'imports'
end
it 'does nothing if the given parameter is not specified' do
post 'file'
end
end

View File

@@ -22,6 +22,18 @@ describe FollowerAccountsController do
expect(assigned[0]).to eq follow1
expect(assigned[1]).to eq follow0
end
it 'does not assign blocked users' do
user = Fabricate(:user)
user.account.block!(follower0)
sign_in(user)
expect(response).to have_http_status(200)
assigned = assigns(:follows).to_a
expect(assigned.size).to eq 1
expect(assigned[0]).to eq follow1
end
end
context 'when format is json' do

View File

@@ -22,6 +22,18 @@ describe FollowingAccountsController do
expect(assigned[0]).to eq follow1
expect(assigned[1]).to eq follow0
end
it 'does not assign blocked users' do
user = Fabricate(:user)
user.account.block!(followee0)
sign_in(user)
expect(response).to have_http_status(200)
assigned = assigns(:follows).to_a
expect(assigned.size).to eq 1
expect(assigned[0]).to eq follow1
end
end
context 'when format is json' do

View File

@@ -1,47 +1,51 @@
require "rails_helper"
# frozen_string_literal: true
require 'rails_helper'
feature 'Log in' do
include ProfileStories
feature "Log in" do
given(:email) { "test@example.com" }
given(:password) { "password" }
given(:confirmed_at) { Time.zone.now }
background do
Fabricate(:user, email: email, password: password, confirmed_at: confirmed_at)
as_a_registered_user
visit new_user_session_path
end
subject { page }
scenario "A valid email and password user is able to log in" do
fill_in "user_email", with: email
fill_in "user_password", with: password
scenario 'A valid email and password user is able to log in' do
fill_in 'user_email', with: email
fill_in 'user_password', with: password
click_on I18n.t('auth.login')
is_expected.to have_css("div.app-holder")
is_expected.to have_css('div.app-holder')
end
scenario "A invalid email and password user is not able to log in" do
fill_in "user_email", with: "invalid_email"
fill_in "user_password", with: "invalid_password"
scenario 'A invalid email and password user is not able to log in' do
fill_in 'user_email', with: 'invalid_email'
fill_in 'user_password', with: 'invalid_password'
click_on I18n.t('auth.login')
is_expected.to have_css(".flash-message", text: failure_message("invalid"))
is_expected.to have_css('.flash-message', text: failure_message('invalid'))
end
context do
given(:confirmed_at) { nil }
scenario "A unconfirmed user is able to log in" do
fill_in "user_email", with: email
fill_in "user_password", with: password
scenario 'A unconfirmed user is able to log in' do
fill_in 'user_email', with: email
fill_in 'user_password', with: password
click_on I18n.t('auth.login')
is_expected.to have_css("div.admin-wrapper")
is_expected.to have_css('div.admin-wrapper')
end
end
def failure_message(message)
keys = User.authentication_keys.map { |key| User.human_attribute_name(key) }
I18n.t("devise.failure.#{message}", authentication_keys: keys.join("support.array.words_connector"))
I18n.t("devise.failure.#{message}", authentication_keys: keys.join('support.array.words_connector'))
end
end

View File

@@ -0,0 +1,53 @@
# frozen_string_literal: true
require 'rails_helper'
feature 'Profile' do
include ProfileStories
given(:local_domain) { ENV['LOCAL_DOMAIN'] }
background do
as_a_logged_in_user
with_alice_as_local_user
end
subject { page }
scenario 'I can view Annes public account' do
visit account_path('alice')
is_expected.to have_title("alice (@alice@#{local_domain})")
within('.public-account-header h1') do
is_expected.to have_content("alice @alice@#{local_domain}")
end
bio_elem = first('.public-account-bio')
expect(bio_elem).to have_content(alice_bio)
# The bio has hashtags made clickable
expect(bio_elem).to have_link('cryptology')
expect(bio_elem).to have_link('science')
# Nicknames are make clickable
expect(bio_elem).to have_link('@alice')
expect(bio_elem).to have_link('@bob')
# Nicknames not on server are not clickable
expect(bio_elem).not_to have_link('@pepe')
end
scenario 'I can change my account' do
visit settings_profile_path
fill_in 'Display name', with: 'Bob'
fill_in 'Bio', with: 'Bob is silent'
click_on 'Save changes'
is_expected.to have_content 'Changes successfully saved!'
# View my own public profile and see the changes
click_link "Bob @bob@#{local_domain}"
within('.public-account-header h1') do
is_expected.to have_content("Bob @bob@#{local_domain}")
end
expect(first('.public-account-bio')).to have_content('Bob is silent')
end
end

View File

@@ -0,0 +1,21 @@
require 'rails_helper'
RSpec.describe HandleBadEncodingMiddleware do
let(:app) { double() }
let(:middleware) { HandleBadEncodingMiddleware.new(app) }
it "request with query string is unchanged" do
expect(app).to receive(:call).with("PATH" => "/some/path", "QUERY_STRING" => "name=fred")
middleware.call("PATH" => "/some/path", "QUERY_STRING" => "name=fred")
end
it "request with no query string is unchanged" do
expect(app).to receive(:call).with("PATH" => "/some/path")
middleware.call("PATH" => "/some/path")
end
it "request with invalid encoding in query string drops query string" do
expect(app).to receive(:call).with("QUERY_STRING" => "", "PATH" => "/some/path")
middleware.call("QUERY_STRING" => "q=%2Fsearch%2Fall%Forder%3Ddescending%26page%3D5%26sort%3Dcreated_at", "PATH" => "/some/path")
end
end

View File

@@ -823,4 +823,5 @@ RSpec.describe Account, type: :model do
end
include_examples 'AccountAvatar', :account
include_examples 'AccountHeader', :account
end

View File

@@ -133,6 +133,24 @@ RSpec.describe MediaAttachment, type: :model do
expect(media.file.meta["small"]["height"]).to eq 327
expect(media.file.meta["small"]["aspect"]).to eq 490.0 / 327
end
it 'gives the file a random name' do
expect(media.file_file_name).to_not eq 'attachment.jpg'
end
end
describe 'base64-encoded jpeg' do
let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" }
let(:media) { MediaAttachment.create(account: Fabricate(:account), file: base64_attachment) }
it 'saves media attachment' do
expect(media.persisted?).to be true
expect(media.file).to_not be_nil
end
it 'gives the file a file name' do
expect(media.file_file_name).to_not be_blank
end
end
describe 'descriptions for remote attachments' do

View File

@@ -5,11 +5,11 @@ RSpec.describe ProcessMentionsService, type: :service do
let(:visibility) { :public }
let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: visibility) }
subject { ProcessMentionsService.new }
context 'OStatus with public toot' do
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
subject { ProcessMentionsService.new }
before do
stub_request(:post, remote_user.salmon_url)
subject.call(status)
@@ -24,8 +24,6 @@ RSpec.describe ProcessMentionsService, type: :service do
let(:visibility) { :private }
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
subject { ProcessMentionsService.new }
before do
stub_request(:post, remote_user.salmon_url)
subject.call(status)
@@ -41,29 +39,45 @@ RSpec.describe ProcessMentionsService, type: :service do
end
context 'ActivityPub' do
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
context do
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
subject { ProcessMentionsService.new }
before do
stub_request(:post, remote_user.inbox_url)
subject.call(status)
end
before do
stub_request(:post, remote_user.inbox_url)
subject.call(status)
it 'creates a mention' do
expect(remote_user.mentions.where(status: status).count).to eq 1
end
it 'sends activity to the inbox' do
expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
end
end
it 'creates a mention' do
expect(remote_user.mentions.where(status: status).count).to eq 1
end
context 'with an IDN domain' do
let(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') }
let(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") }
it 'sends activity to the inbox' do
expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
before do
stub_request(:post, remote_user.inbox_url)
subject.call(status)
end
it 'creates a mention' do
expect(remote_user.mentions.where(status: status).count).to eq 1
end
it 'sends activity to the inbox' do
expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
end
end
end
context 'Temporarily-unreachable ActivityPub user' do
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) }
subject { ProcessMentionsService.new }
before do
stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404)
stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500)

View File

@@ -16,4 +16,24 @@ shared_examples 'AccountAvatar' do |fabricator|
end
end
end
describe 'base64-encoded files' do
let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" }
let(:account) { Fabricate(fabricator, avatar: base64_attachment) }
it 'saves avatar' do
expect(account.persisted?).to be true
expect(account.avatar).to_not be_nil
end
it 'gives the avatar a file name' do
expect(account.avatar_file_name).to_not be_blank
end
it 'saves a new avatar under a different file name' do
previous_file_name = account.avatar_file_name
account.update(avatar: base64_attachment)
expect(account.avatar_file_name).to_not eq previous_file_name
end
end
end

View File

@@ -0,0 +1,23 @@
# frozen_string_literal: true
shared_examples 'AccountHeader' do |fabricator|
describe 'base64-encoded files' do
let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" }
let(:account) { Fabricate(fabricator, header: base64_attachment) }
it 'saves header' do
expect(account.persisted?).to be true
expect(account.header).to_not be_nil
end
it 'gives the header a file name' do
expect(account.header_file_name).to_not be_blank
end
it 'saves a new header under a different file name' do
previous_file_name = account.header_file_name
account.update(header: base64_attachment)
expect(account.header_file_name).to_not eq previous_file_name
end
end
end

View File

@@ -0,0 +1,45 @@
# frozen_string_literal: true
module ProfileStories
attr_reader :bob, :alice, :alice_bio
def as_a_registered_user
@bob = Fabricate(
:user,
email: email, password: password, confirmed_at: confirmed_at,
account: Fabricate(:account, username: 'bob')
)
end
def as_a_logged_in_user
as_a_registered_user
visit new_user_session_path
fill_in 'user_email', with: email
fill_in 'user_password', with: password
click_on I18n.t('auth.login')
end
def with_alice_as_local_user
@alice_bio = '@alice and @bob are fictional characters commonly used as'\
'placeholder names in #cryptology, as well as #science and'\
'engineering 📖 literature. Not affilated with @pepe.'
@alice = Fabricate(
:user,
email: 'alice@example.com', password: password, confirmed_at: confirmed_at,
account: Fabricate(:account, username: 'alice', note: @alice_bio)
)
end
def confirmed_at
@confirmed_at ||= Time.zone.now
end
def email
@email ||= 'test@example.com'
end
def password
@password ||= 'password'
end
end

View File

@@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'rails_helper'
describe RefollowWorker do
subject { described_class.new }
let(:account) { Fabricate(:account, domain: 'example.org', protocol: :activitypub) }
let(:alice) { Fabricate(:account, domain: nil, username: 'alice') }
let(:bob) { Fabricate(:account, domain: nil, username: 'bob') }
describe 'perform' do
let(:service) { double }
before do
allow(FollowService).to receive(:new).and_return(service)
allow(service).to receive(:call)
alice.follow!(account, reblogs: true)
bob.follow!(account, reblogs: false)
end
it 'calls FollowService for local followers' do
result = subject.perform(account.id)
expect(result).to be_nil
expect(service).to have_received(:call).with(alice, account, reblogs: true)
expect(service).to have_received(:call).with(bob, account, reblogs: false)
end
end
end