Merge remote-tracking branch 'parent/main' into upstream-20250403
This commit is contained in:
commit
32f5604499
265 changed files with 6227 additions and 3383 deletions
|
@ -1,52 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Settings::PicturesController do
|
||||
render_views
|
||||
|
||||
let!(:user) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
sign_in user, scope: :user
|
||||
end
|
||||
|
||||
describe 'DELETE #destroy' do
|
||||
context 'with invalid picture id' do
|
||||
it 'returns http bad request' do
|
||||
delete :destroy, params: { id: 'invalid' }
|
||||
expect(response).to have_http_status(400)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid picture id' do
|
||||
context 'when account updates correctly' do
|
||||
let(:service) { instance_double(UpdateAccountService, call: true) }
|
||||
|
||||
before do
|
||||
allow(UpdateAccountService).to receive(:new).and_return(service)
|
||||
end
|
||||
|
||||
it 'updates the account' do
|
||||
delete :destroy, params: { id: 'avatar' }
|
||||
expect(response).to redirect_to(settings_profile_path)
|
||||
expect(response).to have_http_status(303)
|
||||
expect(service).to have_received(:call).with(user.account, { 'avatar' => nil, 'avatar_remote_url' => '' })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account cannot update' do
|
||||
let(:service) { instance_double(UpdateAccountService, call: false) }
|
||||
|
||||
before do
|
||||
allow(UpdateAccountService).to receive(:new).and_return(service)
|
||||
end
|
||||
|
||||
it 'redirects to profile' do
|
||||
delete :destroy, params: { id: 'avatar' }
|
||||
expect(response).to redirect_to(settings_profile_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
7
spec/fabricators/fasp/debug_callback_fabricator.rb
Normal file
7
spec/fabricators/fasp/debug_callback_fabricator.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:fasp_debug_callback, from: 'Fasp::DebugCallback') do
|
||||
fasp_provider
|
||||
ip '127.0.0.234'
|
||||
request_body 'MyText'
|
||||
end
|
31
spec/fabricators/fasp/provider_fabricator.rb
Normal file
31
spec/fabricators/fasp/provider_fabricator.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Fabricator(:fasp_provider, from: 'Fasp::Provider') do
|
||||
name { Faker::App.name }
|
||||
base_url { Faker::Internet.unique.url }
|
||||
sign_in_url { Faker::Internet.url }
|
||||
remote_identifier 'MyString'
|
||||
provider_public_key_pem "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAh2ldXsaej2MXj0DHdCx7XibSo66uKlrLfJ5J6hte1Gk=\n-----END PUBLIC KEY-----\n"
|
||||
server_private_key_pem "-----BEGIN PRIVATE KEY-----\nMC4CAQAwBQYDK2VwBCIEICDjlajhVb8XfzyTchQWKraMKwtQW+r4opoAg7V3kw1Q\n-----END PRIVATE KEY-----\n"
|
||||
capabilities []
|
||||
end
|
||||
|
||||
Fabricator(:confirmed_fasp, from: :fasp_provider) do
|
||||
confirmed true
|
||||
capabilities [
|
||||
{ id: 'callback', version: '0.1' },
|
||||
{ id: 'data_sharing', version: '0.1' },
|
||||
]
|
||||
end
|
||||
|
||||
Fabricator(:debug_fasp, from: :fasp_provider) do
|
||||
confirmed true
|
||||
capabilities [
|
||||
{ id: 'callback', version: '0.1', enabled: true },
|
||||
]
|
||||
|
||||
after_build do |fasp|
|
||||
# Prevent fabrication from attempting an HTTP call to the provider
|
||||
def fasp.update_remote_capabilities = true
|
||||
end
|
||||
end
|
|
@ -28,6 +28,19 @@ RSpec.describe Admin::Trends::StatusesHelper do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with a remote status that has excessive attributes' do
|
||||
let(:attr_limit) { Nokogiri::Gumbo::DEFAULT_MAX_ATTRIBUTES * 2 }
|
||||
let(:html) { "<html><body #{(1..attr_limit).map { |x| "attr-#{x}" }.join(' ')}><p>text</p></body></html>" }
|
||||
|
||||
let(:status) { Fabricate.build(:status, uri: 'https://host.example', text: html) }
|
||||
|
||||
it 'renders a correct preview text' do
|
||||
result = helper.one_line_preview(status)
|
||||
|
||||
expect(result).to eq ''
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a status that has empty text' do
|
||||
let(:status) { Fabricate.build(:status, text: '') }
|
||||
|
||||
|
|
|
@ -13,13 +13,28 @@ RSpec.describe AccountReachFinder do
|
|||
let(:ap_mentioned_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-3', domain: 'example.com') }
|
||||
let(:ap_mentioned_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.org/inbox-4', domain: 'example.org') }
|
||||
|
||||
let(:ap_followed_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-5', domain: 'example.com') }
|
||||
let(:ap_followed_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-6', domain: 'example.org') }
|
||||
|
||||
let(:ap_requested_example_com) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-7', domain: 'example.com') }
|
||||
let(:ap_requested_example_org) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/inbox-8', domain: 'example.org') }
|
||||
|
||||
let(:unrelated_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/unrelated-inbox', domain: 'example.com') }
|
||||
let(:old_followed_account) { Fabricate(:account, protocol: :activitypub, inbox_url: 'https://example.com/old-followed-inbox', domain: 'example.com') }
|
||||
|
||||
before do
|
||||
travel_to(2.months.ago) { account.follow!(old_followed_account) }
|
||||
|
||||
ap_follower_example_com.follow!(account)
|
||||
ap_follower_example_org.follow!(account)
|
||||
ap_follower_with_shared.follow!(account)
|
||||
|
||||
account.follow!(ap_followed_example_com)
|
||||
account.follow!(ap_followed_example_org)
|
||||
|
||||
account.request_follow!(ap_requested_example_com)
|
||||
account.request_follow!(ap_requested_example_org)
|
||||
|
||||
Fabricate(:status, account: account).tap do |status|
|
||||
status.mentions << Mention.new(account: ap_follower_example_com)
|
||||
status.mentions << Mention.new(account: ap_mentioned_with_shared)
|
||||
|
@ -44,7 +59,10 @@ RSpec.describe AccountReachFinder do
|
|||
expect(subject)
|
||||
.to include(*follower_inbox_urls)
|
||||
.and include(*mentioned_account_inbox_urls)
|
||||
.and include(*recently_followed_inbox_urls)
|
||||
.and include(*recently_requested_inbox_urls)
|
||||
.and not_include(unrelated_account.preferred_inbox_url)
|
||||
.and not_include(old_followed_account.preferred_inbox_url)
|
||||
end
|
||||
|
||||
def follower_inbox_urls
|
||||
|
@ -56,5 +74,15 @@ RSpec.describe AccountReachFinder do
|
|||
[ap_mentioned_with_shared, ap_mentioned_example_com, ap_mentioned_example_org]
|
||||
.map(&:preferred_inbox_url)
|
||||
end
|
||||
|
||||
def recently_followed_inbox_urls
|
||||
[ap_followed_example_com, ap_followed_example_org]
|
||||
.map(&:preferred_inbox_url)
|
||||
end
|
||||
|
||||
def recently_requested_inbox_urls
|
||||
[ap_requested_example_com, ap_requested_example_org]
|
||||
.map(&:preferred_inbox_url)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -287,6 +287,23 @@ RSpec.describe ActivityPub::TagManager do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#uris_to_local_accounts' do
|
||||
it 'returns the expected local accounts' do
|
||||
account = Fabricate(:account)
|
||||
expect(subject.uris_to_local_accounts([subject.uri_for(account), instance_actor_url])).to contain_exactly(account, Account.representative)
|
||||
end
|
||||
|
||||
it 'does not return remote accounts' do
|
||||
account = Fabricate(:account, uri: 'https://example.com/123', domain: 'example.com')
|
||||
expect(subject.uris_to_local_accounts([subject.uri_for(account)])).to be_empty
|
||||
end
|
||||
|
||||
it 'does not return an account for a local post' do
|
||||
status = Fabricate(:status)
|
||||
expect(subject.uris_to_local_accounts([subject.uri_for(status)])).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
describe '#uri_to_resource' do
|
||||
it 'returns the local account' do
|
||||
account = Fabricate(:account)
|
||||
|
|
57
spec/lib/fasp/request_spec.rb
Normal file
57
spec/lib/fasp/request_spec.rb
Normal file
|
@ -0,0 +1,57 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
require 'securerandom'
|
||||
|
||||
RSpec.describe Fasp::Request do
|
||||
include ProviderRequestHelper
|
||||
|
||||
subject { described_class.new(provider) }
|
||||
|
||||
let(:provider) do
|
||||
Fabricate(:fasp_provider, base_url: 'https://reqprov.example.com/fasp')
|
||||
end
|
||||
|
||||
shared_examples 'a provider request' do |method|
|
||||
context 'when the response is signed by the provider' do
|
||||
before do
|
||||
stub_provider_request(provider, method:, path: '/test_path')
|
||||
end
|
||||
|
||||
it "performs a signed #{method.to_s.upcase} request relative to the base_path of the fasp" do
|
||||
subject.send(method, '/test_path')
|
||||
|
||||
expect(WebMock).to have_requested(method, 'https://reqprov.example.com/fasp/test_path')
|
||||
.with(headers: {
|
||||
'Signature' => /.+/,
|
||||
'Signature-Input' => /.+/,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the response is not signed' do
|
||||
before do
|
||||
stub_request(method, 'https://reqprov.example.com/fasp/test_path')
|
||||
.to_return(status: 200)
|
||||
end
|
||||
|
||||
it 'raises an error' do
|
||||
expect do
|
||||
subject.send(method, '/test_path')
|
||||
end.to raise_error(Mastodon::SignatureVerificationError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get' do
|
||||
include_examples 'a provider request', :get
|
||||
end
|
||||
|
||||
describe '#post' do
|
||||
include_examples 'a provider request', :post
|
||||
end
|
||||
|
||||
describe '#delete' do
|
||||
include_examples 'a provider request', :delete
|
||||
end
|
||||
end
|
209
spec/models/fasp/provider_spec.rb
Normal file
209
spec/models/fasp/provider_spec.rb
Normal file
|
@ -0,0 +1,209 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Fasp::Provider do
|
||||
include ProviderRequestHelper
|
||||
|
||||
describe '#capabilities' do
|
||||
subject { described_class.new(confirmed: true, capabilities:) }
|
||||
|
||||
let(:capabilities) do
|
||||
[
|
||||
{ 'id' => 'one', 'enabled' => false },
|
||||
{ 'id' => 'two' },
|
||||
]
|
||||
end
|
||||
|
||||
it 'returns an array of `Fasp::Capability` objects' do
|
||||
expect(subject.capabilities).to all(be_a(Fasp::Capability))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#capabilities_attributes=' do
|
||||
subject { described_class.new(confirmed: true) }
|
||||
|
||||
let(:capabilities_params) do
|
||||
{
|
||||
'0' => { 'id' => 'one', 'enabled' => '1' },
|
||||
'1' => { 'id' => 'two', 'enabled' => '0' },
|
||||
'2' => { 'id' => 'three' },
|
||||
}
|
||||
end
|
||||
|
||||
it 'sets capabilities from nested form style hash' do
|
||||
subject.capabilities_attributes = capabilities_params
|
||||
|
||||
expect(subject).to be_capability('one')
|
||||
expect(subject).to be_capability('two')
|
||||
expect(subject).to be_capability('three')
|
||||
expect(subject).to be_capability_enabled('one')
|
||||
expect(subject).to_not be_capability_enabled('two')
|
||||
expect(subject).to_not be_capability_enabled('three')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#capability?' do
|
||||
subject { described_class.new(confirmed:, capabilities:) }
|
||||
|
||||
let(:capabilities) do
|
||||
[
|
||||
{ 'id' => 'one', 'enabled' => false },
|
||||
{ 'id' => 'two', 'enabled' => true },
|
||||
]
|
||||
end
|
||||
|
||||
context 'when the provider is not confirmed' do
|
||||
let(:confirmed) { false }
|
||||
|
||||
it 'always returns false' do
|
||||
expect(subject.capability?('one')).to be false
|
||||
expect(subject.capability?('two')).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the provider is confirmed' do
|
||||
let(:confirmed) { true }
|
||||
|
||||
it 'returns true for available and false for missing capabilities' do
|
||||
expect(subject.capability?('one')).to be true
|
||||
expect(subject.capability?('two')).to be true
|
||||
expect(subject.capability?('three')).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#capability_enabled?' do
|
||||
subject { described_class.new(confirmed:, capabilities:) }
|
||||
|
||||
let(:capabilities) do
|
||||
[
|
||||
{ 'id' => 'one', 'enabled' => false },
|
||||
{ 'id' => 'two', 'enabled' => true },
|
||||
]
|
||||
end
|
||||
|
||||
context 'when the provider is not confirmed' do
|
||||
let(:confirmed) { false }
|
||||
|
||||
it 'always returns false' do
|
||||
expect(subject).to_not be_capability_enabled('one')
|
||||
expect(subject).to_not be_capability_enabled('two')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the provider is confirmed' do
|
||||
let(:confirmed) { true }
|
||||
|
||||
it 'returns true for enabled and false for disabled or missing capabilities' do
|
||||
expect(subject).to_not be_capability_enabled('one')
|
||||
expect(subject).to be_capability_enabled('two')
|
||||
expect(subject).to_not be_capability_enabled('three')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#server_private_key' do
|
||||
subject { Fabricate(:fasp_provider) }
|
||||
|
||||
it 'returns an OpenSSL::PKey::PKey' do
|
||||
expect(subject.server_private_key).to be_a OpenSSL::PKey::PKey
|
||||
end
|
||||
end
|
||||
|
||||
describe '#server_public_key_base64' do
|
||||
subject { Fabricate(:fasp_provider) }
|
||||
|
||||
it 'returns the server public key base64 encoded' do
|
||||
expect(subject.server_public_key_base64).to eq 'T2RHkakkqAOWEMRYv9OY7LGsuIcAdmBlxuXOKax6sjw='
|
||||
end
|
||||
end
|
||||
|
||||
describe '#provider_public_key_base64=' do
|
||||
subject { Fabricate(:fasp_provider) }
|
||||
|
||||
it 'allows setting the provider public key from a base64 encoded raw key' do
|
||||
subject.provider_public_key_base64 = '9qgjOfWRhozWc9dwx5JmbshizZ7TyPBhYk9+b5tE3e4='
|
||||
|
||||
expect(subject.provider_public_key_pem).to eq "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA9qgjOfWRhozWc9dwx5JmbshizZ7TyPBhYk9+b5tE3e4=\n-----END PUBLIC KEY-----\n"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#provider_public_key' do
|
||||
subject { Fabricate(:fasp_provider) }
|
||||
|
||||
it 'returns an OpenSSL::PKey::PKey' do
|
||||
expect(subject.provider_public_key).to be_a OpenSSL::PKey::PKey
|
||||
end
|
||||
end
|
||||
|
||||
describe '#provider_public_key_raw' do
|
||||
subject { Fabricate(:fasp_provider) }
|
||||
|
||||
it 'returns a string comprised of raw bytes' do
|
||||
expect(subject.provider_public_key_raw).to be_a String
|
||||
expect(subject.provider_public_key_raw.encoding).to eq Encoding::BINARY
|
||||
end
|
||||
end
|
||||
|
||||
describe '#provider_public_key_fingerprint' do
|
||||
subject { Fabricate(:fasp_provider) }
|
||||
|
||||
it 'returns a base64 encoded sha256 hash of the public key' do
|
||||
expect(subject.provider_public_key_fingerprint).to eq '/AmW9EMlVq4o+Qcu9lNfTE8Ss/v9+evMPtyj2R437qE='
|
||||
end
|
||||
end
|
||||
|
||||
describe '#url' do
|
||||
subject { Fabricate(:fasp_provider, base_url: 'https://myprovider.example.com/fasp_base/') }
|
||||
|
||||
it 'returns a full URL for a given path' do
|
||||
url = subject.url('/test_path')
|
||||
expect(url).to eq 'https://myprovider.example.com/fasp_base/test_path'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_info!' do
|
||||
subject { Fabricate(:fasp_provider, base_url: 'https://myprov.example.com/fasp/') }
|
||||
|
||||
before do
|
||||
stub_provider_request(subject,
|
||||
path: '/provider_info',
|
||||
response_body: {
|
||||
capabilities: [
|
||||
{ id: 'debug', version: '0.1' },
|
||||
],
|
||||
contactEmail: 'newcontact@example.com',
|
||||
fediverseAccount: '@newfedi@social.example.com',
|
||||
privacyPolicy: 'https::///example.com/privacy',
|
||||
signInUrl: 'https://myprov.example.com/sign_in',
|
||||
})
|
||||
end
|
||||
|
||||
context 'when setting confirm to `true`' do
|
||||
it 'updates the provider and marks it as `confirmed`' do
|
||||
subject.update_info!(confirm: true)
|
||||
|
||||
expect(subject.contact_email).to eq 'newcontact@example.com'
|
||||
expect(subject.fediverse_account).to eq '@newfedi@social.example.com'
|
||||
expect(subject.privacy_policy).to eq 'https::///example.com/privacy'
|
||||
expect(subject.sign_in_url).to eq 'https://myprov.example.com/sign_in'
|
||||
expect(subject).to be_confirmed
|
||||
expect(subject).to be_persisted
|
||||
end
|
||||
end
|
||||
|
||||
context 'when setting confirm to `false`' do
|
||||
it 'updates the provider but does not mark it as `confirmed`' do
|
||||
subject.update_info!
|
||||
|
||||
expect(subject.contact_email).to eq 'newcontact@example.com'
|
||||
expect(subject.fediverse_account).to eq '@newfedi@social.example.com'
|
||||
expect(subject.privacy_policy).to eq 'https::///example.com/privacy'
|
||||
expect(subject.sign_in_url).to eq 'https://myprov.example.com/sign_in'
|
||||
expect(subject).to_not be_confirmed
|
||||
expect(subject).to be_persisted
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
34
spec/policies/admin/fasp/provider_policy_spec.rb
Normal file
34
spec/policies/admin/fasp/provider_policy_spec.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Admin::Fasp::ProviderPolicy, type: :policy do
|
||||
subject { described_class }
|
||||
|
||||
let(:admin) { Fabricate(:admin_user).account }
|
||||
let(:user) { Fabricate(:account) }
|
||||
|
||||
shared_examples 'admin only' do |target|
|
||||
let(:provider) { target.is_a?(Symbol) ? Fabricate(target) : target }
|
||||
|
||||
context 'with an admin' do
|
||||
it 'permits' do
|
||||
expect(subject).to permit(admin, provider)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a non-admin' do
|
||||
it 'denies' do
|
||||
expect(subject).to_not permit(user, provider)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
permissions :index?, :create? do
|
||||
include_examples 'admin only', Fasp::Provider
|
||||
end
|
||||
|
||||
permissions :show?, :create?, :update?, :destroy? do
|
||||
include_examples 'admin only', :fasp_provider
|
||||
end
|
||||
end
|
28
spec/requests/api/fasp/debug/v0/callback/responses_spec.rb
Normal file
28
spec/requests/api/fasp/debug/v0/callback/responses_spec.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::Fasp::Debug::V0::Callback::Responses', feature: :fasp do
|
||||
include ProviderRequestHelper
|
||||
|
||||
describe 'POST /api/fasp/debug/v0/callback/responses' do
|
||||
let(:provider) { Fabricate(:debug_fasp) }
|
||||
|
||||
it 'create a record of the callback' do
|
||||
payload = { test: 'call' }
|
||||
headers = request_authentication_headers(provider,
|
||||
url: api_fasp_debug_v0_callback_responses_url,
|
||||
method: :post,
|
||||
body: payload)
|
||||
|
||||
expect do
|
||||
post api_fasp_debug_v0_callback_responses_path, headers:, params: payload, as: :json
|
||||
end.to change(Fasp::DebugCallback, :count).by(1)
|
||||
expect(response).to have_http_status(201)
|
||||
|
||||
debug_callback = Fasp::DebugCallback.last
|
||||
expect(debug_callback.fasp_provider).to eq provider
|
||||
expect(debug_callback.request_body).to eq '{"test":"call"}'
|
||||
end
|
||||
end
|
||||
end
|
42
spec/requests/api/fasp/registrations_spec.rb
Normal file
42
spec/requests/api/fasp/registrations_spec.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::Fasp::Registrations', feature: :fasp do
|
||||
describe 'POST /api/fasp/registration' do
|
||||
subject do
|
||||
post api_fasp_registration_path, params:
|
||||
end
|
||||
|
||||
context 'when given valid data' do
|
||||
let(:params) do
|
||||
{
|
||||
name: 'Test Provider',
|
||||
baseUrl: 'https://newprovider.example.com/fasp',
|
||||
serverId: '123',
|
||||
publicKey: '9qgjOfWRhozWc9dwx5JmbshizZ7TyPBhYk9+b5tE3e4=',
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates a new provider' do
|
||||
expect { subject }.to change(Fasp::Provider, :count).by(1)
|
||||
|
||||
expect(response).to have_http_status 200
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given invalid data' do
|
||||
let(:params) do
|
||||
{
|
||||
name: 'incomplete',
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not create a provider and returns an error code' do
|
||||
expect { subject }.to_not change(Fasp::Provider, :count)
|
||||
|
||||
expect(response).to have_http_status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
55
spec/requests/settings/pictures_spec.rb
Normal file
55
spec/requests/settings/pictures_spec.rb
Normal file
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Settings Pictures' do
|
||||
let!(:user) { Fabricate(:user) }
|
||||
|
||||
before { sign_in user }
|
||||
|
||||
describe 'DELETE /settings/profile/pictures/:id' do
|
||||
context 'with invalid picture id' do
|
||||
it 'returns http bad request' do
|
||||
delete settings_profile_picture_path(id: 'invalid')
|
||||
|
||||
expect(response)
|
||||
.to have_http_status(400)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with valid picture id' do
|
||||
before { stub_service }
|
||||
|
||||
context 'when account updates correctly' do
|
||||
let(:service) { instance_double(UpdateAccountService, call: true) }
|
||||
|
||||
it 'updates the account' do
|
||||
delete settings_profile_picture_path(id: 'avatar')
|
||||
|
||||
expect(response)
|
||||
.to redirect_to(settings_profile_path)
|
||||
.and have_http_status(303)
|
||||
expect(service)
|
||||
.to have_received(:call).with(user.account, { 'avatar' => nil, 'avatar_remote_url' => '' })
|
||||
end
|
||||
end
|
||||
|
||||
context 'when account cannot update' do
|
||||
let(:service) { instance_double(UpdateAccountService, call: false) }
|
||||
|
||||
it 'redirects to profile' do
|
||||
delete settings_profile_picture_path(id: 'avatar')
|
||||
|
||||
expect(response)
|
||||
.to redirect_to(settings_profile_path)
|
||||
end
|
||||
end
|
||||
|
||||
def stub_service
|
||||
allow(UpdateAccountService)
|
||||
.to receive(:new)
|
||||
.and_return(service)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
72
spec/support/fasp/provider_request_helper.rb
Normal file
72
spec/support/fasp/provider_request_helper.rb
Normal file
|
@ -0,0 +1,72 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ProviderRequestHelper
|
||||
private
|
||||
|
||||
def stub_provider_request(provider, path: '/', method: :get, response_status: 200, response_body: '')
|
||||
response_body = encode_body(response_body)
|
||||
response_headers = {
|
||||
'content-type' => 'application/json',
|
||||
}.merge(response_authentication_headers(provider, response_status, response_body))
|
||||
|
||||
stub_request(method, provider.url(path))
|
||||
.to_return do |_request|
|
||||
{
|
||||
status: response_status,
|
||||
body: response_body,
|
||||
headers: response_headers,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def request_authentication_headers(provider, url: root_url, method: :get, body: '')
|
||||
body = encode_body(body)
|
||||
headers = {}
|
||||
headers['content-digest'] = content_digest(body)
|
||||
request = Linzer.new_request(method, url, {}, headers)
|
||||
key = private_key_for(provider)
|
||||
signature = sign(request, key, %w(@method @target-uri content-digest))
|
||||
headers.merge(signature.to_h)
|
||||
end
|
||||
|
||||
def response_authentication_headers(provider, status, body)
|
||||
headers = {}
|
||||
headers['content-digest'] = content_digest(body)
|
||||
response = Linzer.new_response(body, status, headers)
|
||||
key = private_key_for(provider)
|
||||
signature = sign(response, key, %w(@status content-digest))
|
||||
headers.merge(signature.to_h)
|
||||
end
|
||||
|
||||
def private_key_for(provider)
|
||||
@cached_provider_keys ||= {}
|
||||
@cached_provider_keys[provider] ||=
|
||||
begin
|
||||
key = OpenSSL::PKey.generate_key('ed25519')
|
||||
provider.update!(provider_public_key_pem: key.public_to_pem)
|
||||
key
|
||||
end
|
||||
|
||||
{
|
||||
id: provider.id.to_s,
|
||||
private_key: @cached_provider_keys[provider].private_to_pem,
|
||||
}
|
||||
end
|
||||
|
||||
def sign(request_or_response, key, components)
|
||||
message = Linzer::Message.new(request_or_response)
|
||||
linzer_key = Linzer.new_ed25519_key(key[:private_key], key[:id])
|
||||
Linzer.sign(linzer_key, message, components)
|
||||
end
|
||||
|
||||
def encode_body(body)
|
||||
return body if body.nil? || body.is_a?(String)
|
||||
|
||||
encoder = ActionDispatch::RequestEncoder.encoder(:json)
|
||||
encoder.encode_params(body)
|
||||
end
|
||||
|
||||
def content_digest(content)
|
||||
"sha-256=:#{OpenSSL::Digest.base64digest('sha256', content)}:"
|
||||
end
|
||||
end
|
|
@ -24,11 +24,6 @@ RSpec.describe 'Account notes', :inline_jobs, :js, :streaming do
|
|||
# The easiest way is to send ctrl+enter ourselves
|
||||
find_field(class: 'account__header__account-note__content').send_keys [:control, :enter]
|
||||
|
||||
within('.account__header__account-note .inline-alert') do
|
||||
expect(page)
|
||||
.to have_content('SAVED')
|
||||
end
|
||||
|
||||
expect(page)
|
||||
.to have_css('.account__header__account-note__content', text: note_text)
|
||||
|
||||
|
|
29
spec/system/admin/fasp/debug/callbacks_spec.rb
Normal file
29
spec/system/admin/fasp/debug/callbacks_spec.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Debug FASP Callback Management', feature: :fasp do
|
||||
before { sign_in Fabricate(:admin_user) }
|
||||
|
||||
describe 'Viewing and deleting callbacks' do
|
||||
let(:provider) { Fabricate(:fasp_provider, name: 'debug prov') }
|
||||
|
||||
before do
|
||||
Fabricate(:fasp_debug_callback, fasp_provider: provider, request_body: 'called back')
|
||||
end
|
||||
|
||||
it 'displays callbacks and allows to delete them' do
|
||||
visit admin_fasp_debug_callbacks_path
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.debug.callbacks.title'))
|
||||
expect(page).to have_css('td', text: 'debug prov')
|
||||
expect(page).to have_css('code', text: 'called back')
|
||||
|
||||
expect do
|
||||
click_on I18n.t('admin.fasp.debug.callbacks.delete')
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.debug.callbacks.title'))
|
||||
end.to change(Fasp::DebugCallback, :count).by(-1)
|
||||
end
|
||||
end
|
||||
end
|
33
spec/system/admin/fasp/debug_calls_spec.rb
Normal file
33
spec/system/admin/fasp/debug_calls_spec.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'FASP Debug Calls', feature: :fasp do
|
||||
include ProviderRequestHelper
|
||||
|
||||
before { sign_in Fabricate(:admin_user) }
|
||||
|
||||
describe 'Triggering a FASP debug call' do
|
||||
let!(:provider) { Fabricate(:debug_fasp) }
|
||||
let!(:debug_call) do
|
||||
stub_provider_request(provider,
|
||||
method: :post,
|
||||
path: '/debug/v0/callback/logs',
|
||||
response_status: 201)
|
||||
end
|
||||
|
||||
it 'makes a debug call to the provider' do
|
||||
visit admin_fasp_providers_path
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
|
||||
expect(page).to have_css('td', text: provider.name)
|
||||
|
||||
within 'table#providers' do
|
||||
click_on I18n.t('admin.fasp.providers.callback')
|
||||
end
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
|
||||
expect(debug_call).to have_been_requested
|
||||
end
|
||||
end
|
||||
end
|
81
spec/system/admin/fasp/providers_spec.rb
Normal file
81
spec/system/admin/fasp/providers_spec.rb
Normal file
|
@ -0,0 +1,81 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'FASP Management', feature: :fasp do
|
||||
include ProviderRequestHelper
|
||||
|
||||
before { sign_in Fabricate(:admin_user) }
|
||||
|
||||
describe 'Managing capabilities' do
|
||||
let!(:provider) { Fabricate(:confirmed_fasp) }
|
||||
let!(:enable_call) do
|
||||
stub_provider_request(provider,
|
||||
method: :post,
|
||||
path: '/capabilities/callback/0/activation')
|
||||
end
|
||||
let!(:disable_call) do
|
||||
stub_provider_request(provider,
|
||||
method: :delete,
|
||||
path: '/capabilities/callback/0/activation')
|
||||
end
|
||||
|
||||
before do
|
||||
# We currently err on the side of caution and prefer to send
|
||||
# a "disable capability" call too often over risking to miss
|
||||
# one. So the following call _can_ happen here, and if it does
|
||||
# that is fine, but it has no bearing on the behavior that is
|
||||
# being tested.
|
||||
stub_provider_request(provider,
|
||||
method: :delete,
|
||||
path: '/capabilities/data_sharing/0/activation')
|
||||
end
|
||||
|
||||
it 'allows enabling and disabling of capabilities' do
|
||||
visit admin_fasp_providers_path
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
|
||||
expect(page).to have_css('td', text: provider.name)
|
||||
|
||||
click_on I18n.t('admin.fasp.providers.edit')
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.edit'))
|
||||
|
||||
check 'callback'
|
||||
|
||||
click_on I18n.t('admin.fasp.providers.save')
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
|
||||
expect(provider.reload).to be_capability_enabled('callback')
|
||||
expect(enable_call).to have_been_requested
|
||||
|
||||
click_on I18n.t('admin.fasp.providers.edit')
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.edit'))
|
||||
|
||||
uncheck 'callback'
|
||||
|
||||
click_on I18n.t('admin.fasp.providers.save')
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
|
||||
expect(provider.reload).to_not be_capability_enabled('callback')
|
||||
expect(disable_call).to have_been_requested
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Removing a provider' do
|
||||
let!(:provider) { Fabricate(:fasp_provider) }
|
||||
|
||||
it 'allows to completely remove a provider' do
|
||||
visit admin_fasp_providers_path
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
|
||||
expect(page).to have_css('td', text: provider.name)
|
||||
|
||||
click_on I18n.t('admin.fasp.providers.delete')
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.title'))
|
||||
expect(page).to have_no_css('td', text: provider.name)
|
||||
end
|
||||
end
|
||||
end
|
39
spec/system/admin/fasp/registrations_spec.rb
Normal file
39
spec/system/admin/fasp/registrations_spec.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'FASP registration', feature: :fasp do
|
||||
include ProviderRequestHelper
|
||||
|
||||
before { sign_in Fabricate(:admin_user) }
|
||||
|
||||
describe 'Confirming an unconfirmed FASP' do
|
||||
let(:provider) { Fabricate(:fasp_provider, confirmed: false) }
|
||||
|
||||
before do
|
||||
stub_provider_request(provider,
|
||||
path: '/provider_info',
|
||||
response_body: {
|
||||
capabilities: [
|
||||
{ id: 'debug', version: '0.1' },
|
||||
],
|
||||
contactEmail: 'newcontact@example.com',
|
||||
fediverseAccount: '@newfedi@social.example.com',
|
||||
privacyPolicy: 'https::///example.com/privacy',
|
||||
signInUrl: 'https://myprov.example.com/sign_in',
|
||||
})
|
||||
end
|
||||
|
||||
it 'displays key fingerprint and updates the provider on confirmation' do
|
||||
visit new_admin_fasp_provider_registration_path(provider)
|
||||
|
||||
expect(page).to have_css('code', text: provider.provider_public_key_fingerprint)
|
||||
|
||||
click_on I18n.t('admin.fasp.providers.registrations.confirm')
|
||||
|
||||
expect(page).to have_css('h2', text: I18n.t('admin.fasp.providers.edit'))
|
||||
|
||||
expect(provider.reload).to be_confirmed
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue