Merge remote-tracking branch 'parent/main' into kb_migration

This commit is contained in:
KMY 2023-09-03 10:55:46 +09:00
commit 32cfd20257
54 changed files with 1045 additions and 138 deletions

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
Fabricator(:software_update) do
version '99.99.99'
urgent false
type 'patch'
end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'finding software updates through the admin interface' do
before do
Fabricate(:software_update, version: '99.99.99', type: 'major', urgent: true, release_notes: 'https://github.com/mastodon/mastodon/releases/v99')
sign_in Fabricate(:user, role: UserRole.find_by(name: 'Owner')), scope: :user
end
it 'shows a link to the software updates page, which links to release notes' do
visit settings_profile_path
click_on I18n.t('admin.critical_update_pending')
expect(page).to have_title(I18n.t('admin.software_updates.title'))
expect(page).to have_content('99.99.99')
click_on I18n.t('admin.software_updates.release_notes')
expect(page).to have_current_path('https://github.com/mastodon/mastodon/releases/v99', url: true)
end
end

View file

@ -0,0 +1,133 @@
# frozen_string_literal: true
require 'rails_helper'
describe Admin::SystemCheck::SoftwareVersionCheck do
include RoutingHelper
subject(:check) { described_class.new(user) }
let(:user) { Fabricate(:user) }
describe 'skip?' do
context 'when user cannot view devops' do
before { allow(user).to receive(:can?).with(:view_devops).and_return(false) }
it 'returns true' do
expect(check.skip?).to be true
end
end
context 'when user can view devops' do
before { allow(user).to receive(:can?).with(:view_devops).and_return(true) }
it 'returns false' do
expect(check.skip?).to be false
end
context 'when checks are disabled' do
around do |example|
ClimateControl.modify UPDATE_CHECK_URL: '' do
example.run
end
end
it 'returns true' do
expect(check.skip?).to be true
end
end
end
end
describe 'pass?' do
context 'when there is no known update' do
it 'returns true' do
expect(check.pass?).to be true
end
end
context 'when there is a non-urgent major release' do
before do
Fabricate(:software_update, version: '99.99.99', type: 'major', urgent: false)
end
it 'returns true' do
expect(check.pass?).to be true
end
end
context 'when there is an urgent major release' do
before do
Fabricate(:software_update, version: '99.99.99', type: 'major', urgent: true)
end
it 'returns false' do
expect(check.pass?).to be false
end
end
context 'when there is an urgent minor release' do
before do
Fabricate(:software_update, version: '99.99.99', type: 'minor', urgent: true)
end
it 'returns false' do
expect(check.pass?).to be false
end
end
context 'when there is an urgent patch release' do
before do
Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: true)
end
it 'returns false' do
expect(check.pass?).to be false
end
end
context 'when there is a non-urgent patch release' do
before do
Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: false)
end
it 'returns false' do
expect(check.pass?).to be false
end
end
end
describe 'message' do
context 'when there is a non-urgent patch release pending' do
before do
Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: false)
end
it 'sends class name symbol to message instance' do
allow(Admin::SystemCheck::Message).to receive(:new)
.with(:software_version_patch_check, anything, anything)
check.message
expect(Admin::SystemCheck::Message).to have_received(:new)
.with(:software_version_patch_check, nil, admin_software_updates_path)
end
end
context 'when there is an urgent patch release pending' do
before do
Fabricate(:software_update, version: '99.99.99', type: 'patch', urgent: true)
end
it 'sends class name symbol to message instance' do
allow(Admin::SystemCheck::Message).to receive(:new)
.with(:software_version_critical_check, anything, anything, anything)
check.message
expect(Admin::SystemCheck::Message).to have_received(:new)
.with(:software_version_critical_check, nil, admin_software_updates_path, true)
end
end
end
end

View file

@ -85,4 +85,46 @@ RSpec.describe AdminMailer do
expect(mail.body.encoded).to match 'The following items need a review before they can be displayed publicly'
end
end
describe '.new_software_updates' do
let(:recipient) { Fabricate(:account, username: 'Bob') }
let(:mail) { described_class.with(recipient: recipient).new_software_updates }
before do
recipient.user.update(locale: :en)
end
it 'renders the headers' do
expect(mail.subject).to eq('New Mastodon versions are available for cb6e6126.ngrok.io!')
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
end
it 'renders the body' do
expect(mail.body.encoded).to match 'New Mastodon versions have been released, you may want to update!'
end
end
describe '.new_critical_software_updates' do
let(:recipient) { Fabricate(:account, username: 'Bob') }
let(:mail) { described_class.with(recipient: recipient).new_critical_software_updates }
before do
recipient.user.update(locale: :en)
end
it 'renders the headers', :aggregate_failures do
expect(mail.subject).to eq('Critical Mastodon updates are available for cb6e6126.ngrok.io!')
expect(mail.to).to eq [recipient.user_email]
expect(mail.from).to eq ['notifications@localhost']
expect(mail['Importance'].value).to eq 'high'
expect(mail['Priority'].value).to eq 'urgent'
expect(mail['X-Priority'].value).to eq '1'
end
it 'renders the body' do
expect(mail.body.encoded).to match 'New critical versions of Mastodon have been released, you may want to update as soon as possible!'
end
end
end

View file

@ -0,0 +1,87 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe SoftwareUpdate do
describe '.pending_to_a' do
before do
allow(Mastodon::Version).to receive(:gem_version).and_return(Gem::Version.new(mastodon_version))
Fabricate(:software_update, version: '3.4.42', type: 'patch', urgent: true)
Fabricate(:software_update, version: '3.5.0', type: 'minor', urgent: false)
Fabricate(:software_update, version: '4.2.0', type: 'major', urgent: false)
end
context 'when the Mastodon version is an outdated release' do
let(:mastodon_version) { '3.4.0' }
it 'returns the expected versions' do
expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('3.4.42', '3.5.0', '4.2.0')
end
end
context 'when the Mastodon version is more recent than anything last returned by the server' do
let(:mastodon_version) { '5.0.0' }
it 'returns the expected versions' do
expect(described_class.pending_to_a.pluck(:version)).to eq []
end
end
context 'when the Mastodon version is an outdated nightly' do
let(:mastodon_version) { '4.3.0-nightly.2023-09-10' }
before do
Fabricate(:software_update, version: '4.3.0-nightly.2023-09-12', type: 'major', urgent: true)
end
it 'returns the expected versions' do
expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-nightly.2023-09-12')
end
end
context 'when the Mastodon version is a very outdated nightly' do
let(:mastodon_version) { '4.2.0-nightly.2023-07-10' }
it 'returns the expected versions' do
expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.2.0')
end
end
context 'when the Mastodon version is an outdated dev version' do
let(:mastodon_version) { '4.3.0-0.dev.0' }
before do
Fabricate(:software_update, version: '4.3.0-0.dev.2', type: 'major', urgent: true)
end
it 'returns the expected versions' do
expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-0.dev.2')
end
end
context 'when the Mastodon version is an outdated beta version' do
let(:mastodon_version) { '4.3.0-beta1' }
before do
Fabricate(:software_update, version: '4.3.0-beta2', type: 'major', urgent: true)
end
it 'returns the expected versions' do
expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-beta2')
end
end
context 'when the Mastodon version is an outdated beta version and there is a rc' do
let(:mastodon_version) { '4.3.0-beta1' }
before do
Fabricate(:software_update, version: '4.3.0-rc1', type: 'major', urgent: true)
end
it 'returns the expected versions' do
expect(described_class.pending_to_a.pluck(:version)).to contain_exactly('4.3.0-rc1')
end
end
end
end

View file

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'rails_helper'
require 'pundit/rspec'
RSpec.describe SoftwareUpdatePolicy do
subject { described_class }
let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')).account }
let(:john) { Fabricate(:account) }
permissions :index? do
context 'when owner' do
it 'permits' do
expect(subject).to permit(admin, SoftwareUpdate)
end
end
context 'when not owner' do
it 'denies' do
expect(subject).to_not permit(john, SoftwareUpdate)
end
end
end
end

View file

@ -0,0 +1,158 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe SoftwareUpdateCheckService, type: :service do
subject { described_class.new }
shared_examples 'when the feature is enabled' do
let(:full_update_check_url) { "#{update_check_url}?version=#{Mastodon::Version.to_s.split('+')[0]}" }
let(:devops_role) { Fabricate(:user_role, name: 'DevOps', permissions: UserRole::FLAGS[:view_devops]) }
let(:owner_user) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) }
let(:old_devops_user) { Fabricate(:user) }
let(:none_user) { Fabricate(:user, role: devops_role) }
let(:patch_user) { Fabricate(:user, role: devops_role) }
let(:critical_user) { Fabricate(:user, role: devops_role) }
around do |example|
queue_adapter = ActiveJob::Base.queue_adapter
ActiveJob::Base.queue_adapter = :test
example.run
ActiveJob::Base.queue_adapter = queue_adapter
end
before do
Fabricate(:software_update, version: '3.5.0', type: 'major', urgent: false)
Fabricate(:software_update, version: '42.13.12', type: 'major', urgent: false)
owner_user.settings.update('notification_emails.software_updates': 'all')
owner_user.save!
old_devops_user.settings.update('notification_emails.software_updates': 'all')
old_devops_user.save!
none_user.settings.update('notification_emails.software_updates': 'none')
none_user.save!
patch_user.settings.update('notification_emails.software_updates': 'patch')
patch_user.save!
critical_user.settings.update('notification_emails.software_updates': 'critical')
critical_user.save!
end
context 'when the update server errors out' do
before do
stub_request(:get, full_update_check_url).to_return(status: 404)
end
it 'deletes outdated update records but keeps valid update records' do
expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12']).to(['42.13.12'])
end
end
context 'when the server returns new versions' do
let(:server_json) do
{
updatesAvailable: [
{
version: '4.2.1',
urgent: false,
type: 'patch',
releaseNotes: 'https://github.com/mastodon/mastodon/releases/v4.2.1',
},
{
version: '4.3.0',
urgent: false,
type: 'minor',
releaseNotes: 'https://github.com/mastodon/mastodon/releases/v4.3.0',
},
{
version: '5.0.0',
urgent: false,
type: 'minor',
releaseNotes: 'https://github.com/mastodon/mastodon/releases/v5.0.0',
},
],
}
end
before do
stub_request(:get, full_update_check_url).to_return(body: Oj.dump(server_json))
end
it 'updates the list of known updates' do
expect { subject.call }.to change { SoftwareUpdate.pluck(:version).sort }.from(['3.5.0', '42.13.12']).to(['4.2.1', '4.3.0', '5.0.0'])
end
context 'when no update is urgent' do
it 'sends e-mail notifications according to settings', :aggregate_failures do
expect { subject.call }.to have_enqueued_mail(AdminMailer, :new_software_updates)
.with(hash_including(params: { recipient: owner_user.account })).once
.and(have_enqueued_mail(AdminMailer, :new_software_updates).with(hash_including(params: { recipient: patch_user.account })).once)
.and(have_enqueued_mail.at_most(2))
end
end
context 'when an update is urgent' do
let(:server_json) do
{
updatesAvailable: [
{
version: '5.0.0',
urgent: true,
type: 'minor',
releaseNotes: 'https://github.com/mastodon/mastodon/releases/v5.0.0',
},
],
}
end
it 'sends e-mail notifications according to settings', :aggregate_failures do
expect { subject.call }.to have_enqueued_mail(AdminMailer, :new_critical_software_updates)
.with(hash_including(params: { recipient: owner_user.account })).once
.and(have_enqueued_mail(AdminMailer, :new_critical_software_updates).with(hash_including(params: { recipient: patch_user.account })).once)
.and(have_enqueued_mail(AdminMailer, :new_critical_software_updates).with(hash_including(params: { recipient: critical_user.account })).once)
.and(have_enqueued_mail.at_most(3))
end
end
end
end
context 'when update checking is disabled' do
around do |example|
ClimateControl.modify UPDATE_CHECK_URL: '' do
example.run
end
end
before do
Fabricate(:software_update, version: '3.5.0', type: 'major', urgent: false)
end
it 'deletes outdated update records' do
expect { subject.call }.to change(SoftwareUpdate, :count).from(1).to(0)
end
end
context 'when using the default update checking API' do
let(:update_check_url) { 'https://api.joinmastodon.org/update-check' }
it_behaves_like 'when the feature is enabled'
end
context 'when using a custom update check URL' do
let(:update_check_url) { 'https://api.example.com/update_check' }
around do |example|
ClimateControl.modify UPDATE_CHECK_URL: 'https://api.example.com/update_check' do
example.run
end
end
it_behaves_like 'when the feature is enabled'
end
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'rails_helper'
describe Scheduler::SoftwareUpdateCheckScheduler do
subject { described_class.new }
describe 'perform' do
let(:service_double) { instance_double(SoftwareUpdateCheckService, call: nil) }
before do
allow(SoftwareUpdateCheckService).to receive(:new).and_return(service_double)
end
it 'calls SoftwareUpdateCheckService' do
subject.perform
expect(service_double).to have_received(:call)
end
end
end