Merge remote-tracking branch 'parent/main' into upstream-20241216

This commit is contained in:
KMY 2024-12-16 10:14:31 +09:00
commit 3784ad273c
555 changed files with 7564 additions and 3363 deletions

View file

@ -32,15 +32,16 @@ RSpec.describe Admin::ExportDomainAllowsController do
it 'allows imported domains' do
post :import, params: { admin_import: { data: fixture_file_upload('domain_allows.csv') } }
expect(response).to redirect_to(admin_instances_path)
expect(response)
.to redirect_to(admin_instances_path)
# Header should not be imported
expect(DomainAllow.where(domain: '#domain').present?).to be(false)
# Domains should now be added
get :export, params: { format: :csv }
expect(response).to have_http_status(200)
expect(response.body).to eq(domain_allows_csv_file)
# Header row should not be imported, but domains should
expect(DomainAllow)
.to_not exist(domain: '#domain')
expect(DomainAllow)
.to exist(domain: 'good.domain')
expect(DomainAllow)
.to exist(domain: 'better.domain')
end
it 'displays error on no file selected' do

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::TermsOfService::DistributionsController do
render_views
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
let(:terms_of_service) { Fabricate(:terms_of_service, notification_sent_at: nil) }
before do
sign_in user, scope: :user
end
describe 'POST #create' do
it 'returns http success' do
post :create, params: { terms_of_service_id: terms_of_service.id }
expect(response).to redirect_to(admin_terms_of_service_index_path)
end
end
end

View file

@ -0,0 +1,66 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::TermsOfService::DraftsController do
render_views
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do
sign_in user, scope: :user
end
describe 'GET #show' do
it 'returns http success' do
get :show
expect(response).to have_http_status(:success)
end
end
describe 'PUT #update' do
subject { put :update, params: params }
let!(:terms) { Fabricate :terms_of_service, published_at: nil }
context 'with publishing params' do
let(:params) { { terms_of_service: { text: 'new' }, action_type: 'publish' } }
it 'publishes the record' do
expect { subject }
.to change(Admin::ActionLog, :count).by(1)
expect(response)
.to redirect_to(admin_terms_of_service_index_path)
expect(terms.reload.published_at)
.to_not be_nil
end
end
context 'with non publishing params' do
let(:params) { { terms_of_service: { text: 'new' }, action_type: 'save_draft' } }
it 'updates but does not publish the record' do
expect { subject }
.to_not change(Admin::ActionLog, :count)
expect(response)
.to redirect_to(admin_terms_of_service_draft_path)
expect(terms.reload.published_at)
.to be_nil
end
end
context 'with invalid params' do
let(:params) { { terms_of_service: { text: '' }, action_type: 'save_draft' } }
it 'does not update the record' do
subject
expect(response)
.to have_http_status(:success)
end
end
end
end

View file

@ -0,0 +1,65 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::TermsOfService::GeneratesController do
render_views
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do
sign_in user, scope: :user
end
describe 'GET #show' do
it 'returns http success' do
get :show
expect(response).to have_http_status(:success)
end
end
describe 'POST #create' do
subject { post :create, params: params }
context 'with valid params' do
let(:params) do
{
terms_of_service_generator: {
admin_email: 'test@host.example',
arbitration_address: '123 Main Street',
arbitration_website: 'https://host.example',
dmca_address: '123 DMCA Ave',
dmca_email: 'dmca@host.example',
domain: 'host.example',
jurisdiction: 'Europe',
},
}
end
it 'saves new record' do
expect { subject }
.to change(TermsOfService, :count).by(1)
expect(response)
.to redirect_to(admin_terms_of_service_draft_path)
end
end
context 'with invalid params' do
let(:params) do
{
terms_of_service_generator: {
admin_email: 'what the',
},
}
end
it 'does not save new record' do
expect { subject }
.to_not change(TermsOfService, :count)
expect(response)
.to have_http_status(200)
end
end
end
end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::TermsOfService::HistoriesController do
render_views
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do
sign_in user, scope: :user
end
describe 'GET #show' do
it 'returns http success' do
get :show
expect(response).to have_http_status(:success)
end
end
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::TermsOfService::PreviewsController do
render_views
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
let(:terms_of_service) { Fabricate(:terms_of_service, notification_sent_at: nil) }
before do
sign_in user, scope: :user
end
describe 'GET #show' do
it 'returns http success' do
get :show, params: { terms_of_service_id: terms_of_service.id }
expect(response).to have_http_status(:success)
end
end
end

View file

@ -0,0 +1,22 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::TermsOfService::TestsController do
render_views
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
let(:terms_of_service) { Fabricate(:terms_of_service, notification_sent_at: nil) }
before do
sign_in user, scope: :user
end
describe 'POST #create' do
it 'returns http success' do
post :create, params: { terms_of_service_id: terms_of_service.id }
expect(response).to redirect_to(admin_terms_of_service_preview_path(terms_of_service))
end
end
end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::TermsOfServiceController do
render_views
let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
before do
sign_in user, scope: :user
end
describe 'GET #index' do
it 'returns http success' do
get :index
expect(response).to have_http_status(:success)
end
end
end

View file

@ -1,29 +0,0 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Settings::VerificationsController do
render_views
let!(:user) { Fabricate(:user) }
before do
sign_in user, scope: :user
end
describe 'GET #show' do
before do
get :show
end
it 'returns http success with private cache control headers', :aggregate_failures do
expect(response)
.to have_http_status(200)
.and have_attributes(
headers: include(
'Cache-Control' => 'private, no-store'
)
)
end
end
end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
Fabricator(:status_edit) do
status { Fabricate.build(:status) }
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
Fabricator(:terms_of_service) do
text { Faker::Lorem.paragraph }
changelog { Faker::Lorem.paragraph }
published_at { Time.zone.now }
notification_sent_at { Time.zone.now }
end

BIN
spec/fixtures/files/600x400-animated.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
spec/fixtures/files/600x400-animated.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
spec/fixtures/files/600x400.gif vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

View file

@ -39,15 +39,15 @@ RSpec.describe Sanitize::Config do
end
it 'keeps a with href' do
expect(Sanitize.fragment('<a href="http://example.com">Test</a>', subject)).to eq '<a href="http://example.com" rel="nofollow noopener noreferrer" target="_blank">Test</a>'
expect(Sanitize.fragment('<a href="http://example.com">Test</a>', subject)).to eq '<a href="http://example.com" rel="nofollow noopener" target="_blank">Test</a>'
end
it 'keeps a with translate="no"' do
expect(Sanitize.fragment('<a href="http://example.com" translate="no">Test</a>', subject)).to eq '<a href="http://example.com" translate="no" rel="nofollow noopener noreferrer" target="_blank">Test</a>'
expect(Sanitize.fragment('<a href="http://example.com" translate="no">Test</a>', subject)).to eq '<a href="http://example.com" translate="no" rel="nofollow noopener" target="_blank">Test</a>'
end
it 'removes "translate" attribute with invalid value' do
expect(Sanitize.fragment('<a href="http://example.com" translate="foo">Test</a>', subject)).to eq '<a href="http://example.com" rel="nofollow noopener noreferrer" target="_blank">Test</a>'
expect(Sanitize.fragment('<a href="http://example.com" translate="foo">Test</a>', subject)).to eq '<a href="http://example.com" rel="nofollow noopener" target="_blank">Test</a>'
end
it 'removes a with unparsable href' do
@ -55,7 +55,7 @@ RSpec.describe Sanitize::Config do
end
it 'keeps a with supported scheme and no host' do
expect(Sanitize.fragment('<a href="dweb:/a/foo">Test</a>', subject)).to eq '<a href="dweb:/a/foo" rel="nofollow noopener noreferrer" target="_blank">Test</a>'
expect(Sanitize.fragment('<a href="dweb:/a/foo">Test</a>', subject)).to eq '<a href="dweb:/a/foo" rel="nofollow noopener" target="_blank">Test</a>'
end
it 'sanitizes math to LaTeX' do

View file

@ -50,7 +50,7 @@ RSpec.describe TextFormatter do
end
end
context 'when given a stand-alone google URL' do
context 'when given a stand-alone Google URL' do
let(:text) { 'http://google.com' }
it 'matches the full URL' do
@ -280,6 +280,26 @@ RSpec.describe TextFormatter do
end
end
context 'when given a lengthy URL' do
let(:text) { 'lorem https://prepitaph.org/wip/web-dovespair/ ipsum' }
it 'truncates the URL' do
expect(subject).to include '<span class="invisible">https://</span>'
expect(subject).to include '<span class="ellipsis">prepitaph.org/wip/web-dovespai</span>'
expect(subject).to include '<span class="invisible">r/</span>'
end
end
context 'when given a sufficiently short URL' do
let(:text) { 'lorem https://prepitaph.org/wip/web-devspair/ ipsum' }
it 'does not truncate the URL' do
expect(subject).to include '<span class="invisible">https://</span>'
expect(subject).to include '<span class="">prepitaph.org/wip/web-devspair/</span>'
expect(subject).to include '<span class="invisible"></span>'
end
end
context 'when given text containing a hashtag' do
let(:text) { '#hashtag' }

View file

@ -98,4 +98,9 @@ class UserMailerPreview < ActionMailer::Preview
def failed_2fa
UserMailer.failed_2fa(User.first, '127.0.0.1', 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:75.0) Gecko/20100101 Firefox/75.0', Time.now.utc)
end
# Preview this email at http://localhost:3000/rails/mailers/user_mailer/terms_of_service_changed
def terms_of_service_changed
UserMailer.terms_of_service_changed(User.first, TermsOfService.live.first)
end
end

View file

@ -272,4 +272,16 @@ RSpec.describe UserMailer do
.and(have_body_text(I18n.t('user_mailer.backup_ready.explanation')))
end
end
describe '#terms_of_service_changed' do
let(:terms) { Fabricate :terms_of_service }
let(:mail) { described_class.terms_of_service_changed(receiver, terms) }
it 'renders terms_of_service_changed mail' do
expect(mail)
.to be_present
.and(have_subject(I18n.t('user_mailer.terms_of_service_changed.subject')))
.and(have_body_text(I18n.t('user_mailer.terms_of_service_changed.changelog')))
end
end
end

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe AccountPin do
describe 'Associations' do
it { is_expected.to belong_to(:account).required }
it { is_expected.to belong_to(:target_account).required }
end
describe 'Validations' do
describe 'the follow relationship' do
subject { Fabricate.build :account_pin, account: account }
let(:account) { Fabricate :account }
let(:target_account) { Fabricate :account }
context 'when account is following target account' do
before { account.follow!(target_account) }
it { is_expected.to allow_value(target_account).for(:target_account).against(:base) }
end
context 'when account is not following target account' do
it { is_expected.to_not allow_value(target_account).for(:target_account).against(:base).with_message(not_following_message) }
def not_following_message
I18n.t('accounts.pin_errors.following')
end
end
end
end
end

View file

@ -7,19 +7,8 @@ RSpec.describe CustomFilter do
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_presence_of(:context) }
it 'requires non-empty of context' do
record = described_class.new(context: [])
record.valid?
expect(record).to model_have_error_on_field(:context)
end
it 'requires valid context value' do
record = described_class.new(context: ['invalid'])
record.valid?
expect(record).to model_have_error_on_field(:context)
end
it { is_expected.to_not allow_values([], %w(invalid)).for(:context) }
it { is_expected.to allow_values(%w(home)).for(:context) }
end
describe 'Normalizations' do

View file

@ -61,10 +61,7 @@ RSpec.describe Form::Import do
let(:import_type) { 'following' }
let(:import_file) { 'boop.ogg' }
it 'has errors' do
# NOTE: not testing more specific error because we don't know the string to match
expect(subject).to model_have_error_on_field(:data)
end
it { is_expected.to_not allow_value(data).for(:data) }
end
context 'when importing more follows than allowed' do

View file

@ -90,7 +90,7 @@ RSpec.describe MediaAttachment, :attachment_processing do
media.destroy
end
it 'saves media attachment with correct file and size metadata' do
it 'saves metadata and generates styles' do
expect(media)
.to be_persisted
.and be_processing_complete
@ -98,18 +98,28 @@ RSpec.describe MediaAttachment, :attachment_processing do
file: be_present,
type: eq('image'),
file_content_type: eq(content_type),
file_file_name: end_with(extension)
file_file_name: end_with(extension),
blurhash: have_attributes(size: eq(36))
)
# Rack::Mime (used by PublicFileServerMiddleware) recognizes file extension
expect(Rack::Mime.mime_type(extension, nil)).to eq content_type
# Strip original file name
expect(media.file_file_name)
.to_not start_with '600x400'
# Generate styles
expect(FastImage.size(media.file.path(:original)))
.to eq [600, 400]
expect(FastImage.size(media.file.path(:small)))
.to eq [588, 392]
# Use extension recognized by Rack::Mime (used by PublicFileServerMiddleware)
expect(media.file.path(:original))
.to end_with(extension)
expect(media.file.path(:small))
.to end_with(extension)
# Set meta for original and thumbnail
expect(media.file.meta.deep_symbolize_keys)
expect(media_metadata)
.to include(
original: include(
width: eq(600),
@ -122,6 +132,60 @@ RSpec.describe MediaAttachment, :attachment_processing do
aspect: eq(1.5)
)
)
# Rack::Mime (used by PublicFileServerMiddleware) recognizes file extension
expect(Rack::Mime.mime_type(extension, nil)).to eq content_type
end
end
shared_examples 'animated 600x400 image' do
after do
media.destroy
end
it 'saves metadata and generates styles' do
expect(media)
.to be_persisted
.and be_processing_complete
.and have_attributes(
file: be_present,
type: eq('gifv'),
file_content_type: eq('video/mp4'),
file_file_name: end_with('.mp4'),
blurhash: have_attributes(size: eq(36))
)
# Strip original file name
expect(media.file_file_name)
.to_not start_with '600x400'
# Transcode to MP4
expect(media.file.path(:original))
.to end_with('.mp4')
# Generate static thumbnail
expect(FastImage.size(media.file.path(:small)))
.to eq [600, 400]
expect(FastImage.animated?(media.file.path(:small)))
.to be false
expect(media.file.path(:small))
.to end_with('.png')
# Set meta for styles
expect(media_metadata)
.to include(
original: include(
width: eq(600),
height: eq(400),
duration: eq(3),
frame_rate: '1/1'
),
small: include(
width: eq(600),
height: eq(400),
aspect: eq(1.5)
)
)
end
end
@ -137,10 +201,10 @@ RSpec.describe MediaAttachment, :attachment_processing do
it_behaves_like 'static 600x400 image', 'image/png', '.png'
end
describe 'monochrome jpg' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('monochrome.png')) }
describe 'gif' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400.gif')) }
it_behaves_like 'static 600x400 image', 'image/png', '.png'
it_behaves_like 'static 600x400 image', 'image/gif', '.gif'
end
describe 'webp' do
@ -161,6 +225,12 @@ RSpec.describe MediaAttachment, :attachment_processing do
it_behaves_like 'static 600x400 image', 'image/jpeg', '.jpeg'
end
describe 'monochrome jpg' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('monochrome.png')) }
it_behaves_like 'static 600x400 image', 'image/png', '.png'
end
describe 'base64-encoded image' do
let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('600x400.jpeg').read)}" }
let(:media) { Fabricate(:media_attachment, file: base64_attachment) }
@ -169,51 +239,15 @@ RSpec.describe MediaAttachment, :attachment_processing do
end
describe 'animated gif' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('avatar.gif')) }
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400-animated.gif')) }
it 'sets correct file metadata' do
expect(media)
.to have_attributes(
type: eq('gifv'),
file_content_type: eq('video/mp4')
)
expect(media_metadata)
.to include(
original: include(
width: eq(128),
height: eq(128)
)
)
end
it_behaves_like 'animated 600x400 image'
end
describe 'static gif' do
fixtures = [
{ filename: 'attachment.gif', width: 600, height: 400, aspect: 1.5 },
{ filename: 'mini-static.gif', width: 32, height: 32, aspect: 1.0 },
]
describe 'animated png' do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture('600x400-animated.png')) }
fixtures.each do |fixture|
context fixture[:filename] do
let(:media) { Fabricate(:media_attachment, file: attachment_fixture(fixture[:filename])) }
it 'sets correct file metadata' do
expect(media)
.to have_attributes(
type: eq('image'),
file_content_type: eq('image/gif')
)
expect(media_metadata)
.to include(
original: include(
width: eq(fixture[:width]),
height: eq(fixture[:height]),
aspect: eq(fixture[:aspect])
)
)
end
end
end
it_behaves_like 'animated 600x400 image'
end
describe 'ogg with cover art' do

View file

@ -3,8 +3,14 @@
require 'rails_helper'
RSpec.describe Mention do
describe 'validations' do
describe 'Associations' do
it { is_expected.to belong_to(:account).required }
it { is_expected.to belong_to(:status).required }
end
describe 'Validations' do
subject { Fabricate.build :mention }
it { is_expected.to validate_uniqueness_of(:account_id).scoped_to(:status_id) }
end
end

View file

@ -105,25 +105,32 @@ RSpec.describe Report do
describe 'history' do
subject(:action_logs) { report.history }
let(:report) { Fabricate(:report, target_account_id: target_account.id, status_ids: [status.id], created_at: 3.days.ago, updated_at: 1.day.ago) }
let(:report) { Fabricate(:report, target_account_id: target_account.id, status_ids: [status.id]) }
let(:target_account) { Fabricate(:account) }
let(:status) { Fabricate(:status) }
let(:account_warning) { Fabricate(:account_warning, report_id: report.id) }
before do
Fabricate(:action_log, target_type: 'Report', account_id: target_account.id, target_id: report.id, created_at: 2.days.ago)
Fabricate(:action_log, target_type: 'Account', account_id: target_account.id, target_id: report.target_account_id, created_at: 2.days.ago)
Fabricate(:action_log, target_type: 'Status', account_id: target_account.id, target_id: status.id, created_at: 2.days.ago)
Fabricate(:action_log, target_type: 'AccountWarning', account_id: target_account.id, target_id: account_warning.id, created_at: 2.days.ago)
end
let!(:matched_type_account_warning) { Fabricate(:action_log, target_type: 'AccountWarning', target_id: account_warning.id) }
let!(:matched_type_account) { Fabricate(:action_log, target_type: 'Account', target_id: report.target_account_id) }
let!(:matched_type_report) { Fabricate(:action_log, target_type: 'Report', target_id: report.id) }
let!(:matched_type_status) { Fabricate(:action_log, target_type: 'Status', target_id: status.id) }
let!(:unmatched_type_account_warning) { Fabricate(:action_log, target_type: 'AccountWarning') }
let!(:unmatched_type_account) { Fabricate(:action_log, target_type: 'Account') }
let!(:unmatched_type_report) { Fabricate(:action_log, target_type: 'Report') }
let!(:unmatched_type_status) { Fabricate(:action_log, target_type: 'Status') }
it 'returns expected logs' do
expect(action_logs)
.to have_attributes(count: 4)
.and include(have_attributes(target_type: 'Account'))
.and include(have_attributes(target_type: 'AccountWarning'))
.and include(have_attributes(target_type: 'Report'))
.and include(have_attributes(target_type: 'Status'))
.and include(matched_type_account_warning)
.and include(matched_type_account)
.and include(matched_type_report)
.and include(matched_type_status)
.and not_include(unmatched_type_account_warning)
.and not_include(unmatched_type_account)
.and not_include(unmatched_type_report)
.and not_include(unmatched_type_status)
end
end
@ -149,13 +156,13 @@ RSpec.describe Report do
end
end
describe 'validations' do
describe 'Validations' do
let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
it 'is invalid if comment is longer than character limit and reporter is local' do
report = Fabricate.build(:report, comment: comment_over_limit)
expect(report.valid?).to be false
expect(report).to model_have_error_on_field(:comment)
report = Fabricate.build(:report)
expect(report).to_not allow_value(comment_over_limit).for(:comment)
end
it 'is valid if comment is longer than character limit and reporter is not local' do
@ -164,16 +171,16 @@ RSpec.describe Report do
end
it 'is invalid if it references invalid rules' do
report = Fabricate.build(:report, category: :violation, rule_ids: [-1])
expect(report.valid?).to be false
expect(report).to model_have_error_on_field(:rule_ids)
report = Fabricate.build(:report, category: :violation)
expect(report).to_not allow_value([-1]).for(:rule_ids)
end
it 'is invalid if it references rules but category is not "violation"' do
rule = Fabricate(:rule)
report = Fabricate.build(:report, category: :spam, rule_ids: rule.id)
expect(report.valid?).to be false
expect(report).to model_have_error_on_field(:rule_ids)
report = Fabricate.build(:report, category: :spam)
expect(report).to_not allow_value(rule.id).for(:rule_ids)
end
def comment_over_limit

View file

@ -5,7 +5,39 @@ require 'rails_helper'
RSpec.describe Tag do
include_examples 'Reviewable'
describe 'validations' do
describe 'Validations' do
describe 'name' do
context 'with a new record' do
subject { Fabricate.build :tag, name: 'original' }
it { is_expected.to allow_value('changed').for(:name) }
end
context 'with an existing record' do
subject { Fabricate :tag, name: 'original' }
it { is_expected.to_not allow_value('changed').for(:name).with_message(previous_name_error_message) }
end
end
describe 'display_name' do
context 'with a new record' do
subject { Fabricate.build :tag, name: 'original', display_name: 'OriginalDisplayName' }
it { is_expected.to allow_value('ChangedDisplayName').for(:display_name) }
end
context 'with an existing record' do
subject { Fabricate :tag, name: 'original', display_name: 'OriginalDisplayName' }
it { is_expected.to_not allow_value('ChangedDisplayName').for(:display_name).with_message(previous_name_error_message) }
end
end
def previous_name_error_message
I18n.t('tags.does_not_match_previous_name')
end
it 'invalid with #' do
expect(described_class.new(name: '#hello_world')).to_not be_valid
end

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe TermsOfService do
describe '#scope_for_notification' do
subject { terms_of_service.scope_for_notification }
let(:published_at) { Time.now.utc }
let(:terms_of_service) { Fabricate(:terms_of_service, published_at: published_at) }
let(:user_before) { Fabricate(:user, created_at: published_at - 2.days) }
let(:user_before_unconfirmed) { Fabricate(:user, created_at: published_at - 2.days, confirmed_at: nil) }
let(:user_before_suspended) { Fabricate(:user, created_at: published_at - 2.days) }
let(:user_after) { Fabricate(:user, created_at: published_at + 1.hour) }
before do
user_before_suspended.account.suspend!
user_before_unconfirmed
user_before
user_after
end
it 'includes only users created before the terms of service were published' do
expect(subject.pluck(:id)).to match_array(user_before.id)
end
end
end

View file

@ -0,0 +1,73 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe TermsOfServicePolicy do
subject { described_class }
let(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')).account }
let(:john) { Fabricate(:account) }
permissions :index?, :create? do
it { is_expected.to permit(admin, TermsOfService) }
it { is_expected.to_not permit(john, TermsOfService) }
end
permissions :update?, :destroy? do
let(:terms) { Fabricate(:terms_of_service, published_at: published) }
context 'with an unpublished terms' do
let(:published) { nil }
it { is_expected.to permit(admin, terms) }
it { is_expected.to_not permit(john, terms) }
end
context 'with a published terms' do
let(:published) { 5.days.ago }
it { is_expected.to_not permit(admin, terms) }
it { is_expected.to_not permit(john, terms) }
end
end
permissions :distribute? do
let(:terms) { Fabricate(:terms_of_service, published_at: published, notification_sent_at: notification) }
context 'with notification already sent' do
let(:notification) { 3.days.ago }
context 'with published true' do
let(:published) { 5.days.ago }
it { is_expected.to_not permit(admin, terms) }
it { is_expected.to_not permit(john, terms) }
end
context 'with published false' do
let(:published) { nil }
it { is_expected.to_not permit(admin, terms) }
it { is_expected.to_not permit(john, terms) }
end
end
context 'with notification not yet sent' do
let(:notification) { nil }
context 'with published true' do
let(:published) { 5.days.ago }
it { is_expected.to permit(admin, terms) }
it { is_expected.to_not permit(john, terms) }
end
context 'with published false' do
let(:published) { nil }
it { is_expected.to_not permit(admin, terms) }
it { is_expected.to_not permit(john, terms) }
end
end
end
end

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Terms of Service' do
describe 'GET /api/v1/instance/terms_of_service' do
before do
Fabricate(:terms_of_service)
end
it 'returns http success' do
get api_v1_instance_terms_of_service_path
expect(response)
.to have_http_status(200)
expect(response.content_type)
.to start_with('application/json')
expect(response.parsed_body)
.to be_present
.and include(:content)
end
end
end

View file

@ -137,7 +137,7 @@ RSpec.describe 'Media' do
end
context 'with image/gif', :attachment_processing do
let(:params) { { file: fixture_file_upload('attachment.gif', 'image/gif') } }
let(:params) { { file: fixture_file_upload('600x400.gif', 'image/gif') } }
it_behaves_like 'a successful media upload', 'image'
end

View file

@ -5,7 +5,7 @@ require 'rails_helper'
RSpec.describe REST::AccountRelationshipSeveranceEventSerializer do
subject { serialized_record_json(record, described_class) }
let(:record) { Fabricate.build :account_relationship_severance_event, id: 123 }
let(:record) { Fabricate.build :account_relationship_severance_event, id: 123, created_at: DateTime.new(2024, 11, 28, 16, 20, 0) }
describe 'serialization' do
it 'returns expected values' do
@ -15,4 +15,10 @@ RSpec.describe REST::AccountRelationshipSeveranceEventSerializer do
)
end
end
context 'when created_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,19 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::AccountSerializer::FieldSerializer do
subject { serialized_record_json(field, described_class) }
let(:default_datetime) { DateTime.new(2024, 11, 28, 16, 20, 0) }
let(:user) { Fabricate(:user) }
let(:account) { user.account }
context 'when verified_at is populated' do
let(:field) { Account::Field.new(account, 'name' => 'Foo', 'value' => 'Bar', 'verified_at' => default_datetime) }
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['verified_at']) }.to_not raise_error
end
end
end

View file

@ -5,6 +5,7 @@ require 'rails_helper'
RSpec.describe REST::AccountSerializer do
subject { serialized_record_json(account, described_class) }
let(:default_datetime) { DateTime.new(2024, 11, 28, 16, 20, 0) }
let(:role) { Fabricate(:user_role, name: 'Role', highlighted: true) }
let(:user) { Fabricate(:user, role: role) }
let(:account) { user.account }
@ -44,4 +45,24 @@ RSpec.describe REST::AccountSerializer do
expect(subject['memorial']).to be true
end
end
context 'when created_at is populated' do
before do
account.account_stat.update!(created_at: default_datetime)
end
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
context 'when last_status_at is populated' do
before do
account.account_stat.update!(last_status_at: default_datetime)
end
it 'is serialized as yyyy-mm-dd' do
expect(subject['last_status_at']).to eq('2024-11-28')
end
end
end

View file

@ -14,6 +14,7 @@ RSpec.describe REST::AccountWarningSerializer do
'id' => be_a(String).and(eq('123')),
'status_ids' => be_a(Array).and(eq(['456', '789']))
)
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
end

View file

@ -5,6 +5,14 @@ require 'rails_helper'
RSpec.describe REST::Admin::AccountSerializer do
subject { serialized_record_json(record, described_class) }
context 'when created_at is populated' do
let(:record) { Fabricate :account, user: Fabricate(:user) }
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
describe 'created_by_application_id' do
context 'when account is application-created' do
let(:record) { Fabricate :account, user: Fabricate(:user, created_by_application: application) }

View file

@ -14,6 +14,8 @@ RSpec.describe REST::Admin::CohortSerializer do
'data' => be_a(Array),
'period' => /2024-01-01/
)
expect { DateTime.rfc3339(subject['period']) }.to_not raise_error
subject['data'].each { |datum| expect { DateTime.rfc3339(datum['date']) }.to_not raise_error }
end
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::Admin::DomainAllowSerializer do
subject { serialized_record_json(record, described_class) }
let(:record) { Fabricate(:domain_allow) }
context 'when created_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::Admin::DomainBlockSerializer do
subject { serialized_record_json(record, described_class) }
let(:record) { Fabricate(:domain_block) }
context 'when created_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::Admin::EmailDomainBlockSerializer do
subject { serialized_record_json(record, described_class) }
let(:record) { Fabricate(:email_domain_block) }
context 'when created_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::Admin::IpBlockSerializer do
subject { serialized_record_json(record, described_class) }
let(:record) { Fabricate(:ip_block) }
context 'when created_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
context 'when expires_at is populated' do
let(:record) { Fabricate(:ip_block, expires_at: DateTime.new(2024, 11, 28, 16, 20, 0)) }
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['expires_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::Admin::IpSerializer do
subject { serialized_record_json(record, described_class) }
let(:record) { UserIp.new(used_at: 3.days.ago) }
context 'when used_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['used_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::Admin::MeasureSerializer do
subject { serialized_record_json(record, described_class) }
let(:start_at) { 2.days.ago }
let(:end_at) { Time.now.utc }
let(:params) { ActionController::Parameters.new({ instance_accounts: [123] }) }
let(:record) { Admin::Metrics::Measure::ActiveUsersMeasure.new(start_at, end_at, params) }
context 'when start_at is populated' do
it 'parses as RFC 3339 datetime' do
subject['data'].each { |datum| expect { DateTime.rfc3339(datum['date']) }.to_not raise_error }
end
end
end

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::Admin::ReportSerializer do
subject { serialized_record_json(report, described_class) }
let(:report) { Fabricate(:report) }
context 'with created_at' do
it 'is serialized as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
context 'with action_taken_at' do
let(:acting_account) { Fabricate(:account) }
before do
report.resolve!(acting_account)
end
it 'is serialized as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['action_taken_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::AnnouncementSerializer do
subject do
serialized_record_json(
announcement,
described_class,
options: {
scope: current_user,
scope_name: :current_user,
}
)
end
let(:current_user) { Fabricate(:user) }
let(:announcement) { Fabricate(:announcement, starts_at: 10.days.ago, published_at: 10.days.ago, ends_at: 5.days.from_now) }
context 'when date fields are populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['starts_at']) }.to_not raise_error
expect { DateTime.rfc3339(subject['ends_at']) }.to_not raise_error
expect { DateTime.rfc3339(subject['published_at']) }.to_not raise_error
expect { DateTime.rfc3339(subject['updated_at']) }.to_not raise_error
end
end
end

View file

@ -5,9 +5,11 @@ require 'rails_helper'
RSpec.describe REST::ExtendedDescriptionSerializer do
subject { serialized_record_json(record, described_class) }
let(:default_datetime) { DateTime.new(2024, 11, 28, 16, 20, 0) }
describe 'serialization' do
context 'with text present' do
let(:record) { ExtendedDescription.new text: 'Hello world', updated_at: Date.new(2024, 1, 1) }
let(:record) { ExtendedDescription.new text: 'Hello world', updated_at: default_datetime }
it 'returns expected values' do
expect(subject)
@ -15,19 +17,19 @@ RSpec.describe REST::ExtendedDescriptionSerializer do
'content' => eq(<<~HTML),
<p>Hello world</p>
HTML
'updated_at' => eq('2024-01-01')
'updated_at' => eq('2024-11-28T16:20:00+00:00')
)
end
end
context 'with text missing' do
let(:record) { ExtendedDescription.new text: nil, updated_at: Date.new(2024, 1, 1) }
let(:record) { ExtendedDescription.new text: nil, updated_at: default_datetime }
it 'returns expected values' do
expect(subject)
.to include(
'content' => eq(''),
'updated_at' => eq('2024-01-01')
'updated_at' => eq('2024-11-28T16:20:00+00:00')
)
end
end

View file

@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::FeaturedTagSerializer do
subject do
serialized_record_json(
featured_tag,
described_class
)
end
let(:featured_tag) { Fabricate :featured_tag }
context 'when last_status_at is populated' do
before do
featured_tag.increment(DateTime.new(2024, 11, 28, 16, 20, 0))
end
it 'is serialized as yyyy-mm-dd' do
expect(subject['last_status_at']).to eq('2024-11-28')
end
end
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::FilterSerializer do
subject do
serialized_record_json(
filter,
described_class
)
end
let(:filter) { Fabricate :custom_filter, expires_at: DateTime.new(2024, 11, 28, 16, 20, 0) }
context 'when expires_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['expires_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::MarkerSerializer do
subject do
serialized_record_json(
marker,
described_class
)
end
let(:marker) { Fabricate :marker }
context 'when updated_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['updated_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::MutedAccountSerializer do
subject do
serialized_record_json(
mutee,
described_class,
options: {
scope: current_user,
scope_name: :current_user,
}
)
end
let(:current_user) { Fabricate(:user) }
let(:account) { current_user.account }
let(:mutee) { Fabricate(:account, username: 'mutee') }
context 'when mute_expires_at is populated and non-nil' do
before do
account.follow!(mutee)
account.mute!(mutee, duration: 1.day)
end
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['mute_expires_at']) }.to_not raise_error
end
end
context 'when mute has no duration' do
before do
account.follow!(mutee)
account.mute!(mutee)
end
it 'has a nil mute_expires_at' do
expect(subject['mute_expires_at']).to be_nil
end
end
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::NotificationGroupSerializer do
subject do
serialized_record_json(
notification_group,
described_class
)
end
let(:notification_group) { NotificationGroup.new pagination_data: { latest_notification_at: 3.days.ago }, notification: Fabricate(:notification), sample_accounts: [] }
context 'when latest_page_notification_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['latest_page_notification_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::NotificationRequestSerializer do
subject do
serialized_record_json(
notification_request,
described_class,
options: {
scope: current_user,
scope_name: :current_user,
}
)
end
let(:current_user) { Fabricate(:user) }
let(:notification_request) { Fabricate :notification_request }
context 'when created_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
context 'when updated_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['updated_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::NotificationSerializer do
subject do
serialized_record_json(
notification,
described_class
)
end
let(:notification) { Fabricate :notification }
context 'when created_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::PollSerializer do
subject do
serialized_record_json(
poll,
described_class,
options: {
scope: current_user,
scope_name: :current_user,
}
)
end
let(:current_user) { Fabricate(:user) }
let(:poll) { Fabricate :poll }
context 'when expires_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['expires_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,33 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::ReportSerializer do
subject do
serialized_record_json(
report,
described_class
)
end
let(:status) { Fabricate(:status) }
let(:report) { Fabricate(:report, status_ids: [status.id]) }
context 'with created_at' do
it 'is serialized as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
context 'with action_taken_at' do
let(:acting_account) { Fabricate(:account) }
before do
report.resolve!(acting_account)
end
it 'is serialized as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['action_taken_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::ScheduledStatusSerializer do
subject do
serialized_record_json(
scheduled_status,
described_class
)
end
let(:account) { Fabricate(:account) }
let(:scheduled_status) { Fabricate.build(:scheduled_status, scheduled_at: 4.minutes.from_now, account: account) }
context 'with scheduled_at' do
it 'is serialized as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['scheduled_at']) }.to_not raise_error
end
end
end

View file

@ -0,0 +1,20 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe REST::StatusEditSerializer do
subject do
serialized_record_json(
status_edit,
described_class
)
end
let(:status_edit) { Fabricate(:status_edit) }
context 'when created_at is populated' do
it 'parses as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
end

View file

@ -51,5 +51,19 @@ RSpec.describe REST::StatusSerializer do
expect(subject['favourites_count']).to eq(40)
end
end
context 'with created_at' do
it 'is serialized as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['created_at']) }.to_not raise_error
end
end
context 'when edited_at is populated' do
let(:status) { Fabricate.build :status, edited_at: 3.days.ago }
it 'is serialized as RFC 3339 datetime' do
expect { DateTime.rfc3339(subject['edited_at']) }.to_not raise_error
end
end
end
end

View file

@ -38,7 +38,7 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
let(:media_attachments) { [] }
before do
mentions.each { |a| Fabricate(:mention, status: status, account: a) }
mentions.each { |(account, silent)| Fabricate(:mention, status: status, account: account, silent: silent) }
tags.each { |t| status.tags << t }
media_attachments.each { |m| status.media_attachments << m }
stub_request(:get, bogus_mention).to_raise(HTTP::ConnectionError)
@ -324,7 +324,19 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
end
context 'when originally with mentions' do
let(:mentions) { [alice, bob] }
let(:mentions) { [[alice, false], [bob, false]] }
before do
subject.call(status, json, json)
end
it 'updates mentions' do
expect(status.active_mentions.reload.map(&:account_id)).to eq [alice.id]
end
end
context 'when originally with silent mentions' do
let(:mentions) { [[alice, true], [bob, true]] }
before do
subject.call(status, json, json)

View file

@ -109,7 +109,7 @@ RSpec.describe ReblogService do
Status
.where(id: reblog_of_id)
.where(text: 'discard-status-text')
.update_all(deleted_at: Time.now.utc)
.touch_all(:deleted_at)
end
end
end

View file

@ -199,6 +199,14 @@ RSpec.describe UpdateStatusService do
.to eq [bob.id]
expect(status.mentions.pluck(:account_id))
.to contain_exactly(alice.id, bob.id)
# Going back when a mention was switched to silence should still be possible
subject.call(status, status.account_id, text: 'Hello @alice')
expect(status.active_mentions.pluck(:account_id))
.to eq [alice.id]
expect(status.mentions.pluck(:account_id))
.to contain_exactly(alice.id, bob.id)
end
end

View file

@ -1,15 +0,0 @@
# frozen_string_literal: true
RSpec::Matchers.define :model_have_error_on_field do |expected|
match do |record|
record.valid? if record.errors.empty?
record.errors.key?(expected)
end
failure_message do |record|
keys = record.errors.attribute_names
"expect record.errors(#{keys}) to include #{expected}"
end
end

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Admin Terms of services' do
describe 'Viewing terms of services index page' do
let!(:terms) { Fabricate :terms_of_service, text: 'Test terms' }
before { sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
it 'allows tags listing and editing' do
visit admin_terms_of_service_index_path
expect(page)
.to have_title(I18n.t('admin.terms_of_service.title'))
expect(page)
.to have_content(terms.text)
end
end
end

View file

@ -34,7 +34,7 @@ RSpec.describe 'Log in' do
it 'A unconfirmed user is able to log in' do
fill_in_auth_details(email, password)
expect(subject).to have_css('div.admin-wrapper')
expect(subject).to have_css('.title', text: I18n.t('auth.setup.title'))
end
end

View file

@ -0,0 +1,34 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Settings verification page' do
let(:user) { Fabricate :user }
before { sign_in user }
describe 'Viewing the verification page' do
it 'shows the page and updates attribution' do
visit settings_verification_path
expect(page)
.to have_content(verification_summary)
.and have_private_cache_control
fill_in attribution_field, with: 'host.example'
expect { click_on submit_button }
.to(change { user.account.reload.attribution_domains_as_text })
expect(page)
.to have_content(success_message)
end
end
def verification_summary
I18n.t('verification.website_verification')
end
def attribution_field
I18n.t('simple_form.labels.account.attribution_domains_as_text')
end
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Terms of Service page' do
it 'visits the about page and renders the web app' do
visit terms_of_service_path
expect(page)
.to have_css('noscript', text: /Mastodon/)
.and have_css('body', class: 'app-body')
end
end

View file

@ -0,0 +1,32 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Admin::DistributeTermsOfServiceNotificationWorker do
let(:worker) { described_class.new }
describe '#perform' do
context 'with missing record' do
it 'runs without error' do
expect { worker.perform(nil) }.to_not raise_error
end
end
context 'with valid terms' do
let(:terms) { Fabricate(:terms_of_service) }
let!(:user) { Fabricate :user, confirmed_at: 3.days.ago }
it 'sends the terms update via email', :inline_jobs do
emails = capture_emails { worker.perform(terms.id) }
expect(emails.size)
.to eq(1)
expect(emails.first)
.to have_attributes(
to: [user.email],
subject: I18n.t('user_mailer.terms_of_service_changed.subject')
)
end
end
end
end