diff --git a/.github/workflows/test-migrations-one-step.yml b/.github/workflows/test-migrations-one-step.yml
index d7e424a8c4..212b2cfe73 100644
--- a/.github/workflows/test-migrations-one-step.yml
+++ b/.github/workflows/test-migrations-one-step.yml
@@ -23,9 +23,17 @@ jobs:
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
+ strategy:
+ fail-fast: false
+
+ matrix:
+ postgres:
+ - 14-alpine
+ - 15-alpine
+
services:
postgres:
- image: postgres:14-alpine
+ image: postgres:${{ matrix.postgres}}
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
diff --git a/.github/workflows/test-migrations-two-step.yml b/.github/workflows/test-migrations-two-step.yml
index 25bf5ba87f..310153929d 100644
--- a/.github/workflows/test-migrations-two-step.yml
+++ b/.github/workflows/test-migrations-two-step.yml
@@ -23,9 +23,17 @@ jobs:
needs: pre_job
if: needs.pre_job.outputs.should_skip != 'true'
+ strategy:
+ fail-fast: false
+
+ matrix:
+ postgres:
+ - 14-alpine
+ - 15-alpine
+
services:
postgres:
- image: postgres:14-alpine
+ image: postgres:${{ matrix.postgres}}
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 0fb99c0402..69dd433e38 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -21,12 +21,6 @@ Layout/ArgumentAlignment:
- 'config/initializers/cors.rb'
- 'config/initializers/session_store.rb'
-# This cop supports safe autocorrection (--autocorrect).
-# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment.
-Layout/ExtraSpacing:
- Exclude:
- - 'config/initializers/omniauth.rb'
-
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
# SupportedHashRocketStyles: key, separator, table
@@ -39,12 +33,6 @@ Layout/HashAlignment:
- 'config/initializers/rack_attack.rb'
- 'config/routes.rb'
-# This cop supports safe autocorrection (--autocorrect).
-# Configuration parameters: Width, AllowedPatterns.
-Layout/IndentationWidth:
- Exclude:
- - 'config/initializers/ffmpeg.rb'
-
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment.
Layout/LeadingCommentSpace:
@@ -52,14 +40,6 @@ Layout/LeadingCommentSpace:
- 'config/application.rb'
- 'config/initializers/omniauth.rb'
-# This cop supports safe autocorrection (--autocorrect).
-# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces.
-# SupportedStyles: space, no_space
-# SupportedStylesForEmptyBraces: space, no_space
-Layout/SpaceBeforeBlockBraces:
- Exclude:
- - 'config/initializers/paperclip.rb'
-
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: require_no_space, require_space
@@ -68,19 +48,6 @@ Layout/SpaceInLambdaLiteral:
- 'config/environments/production.rb'
- 'config/initializers/content_security_policy.rb'
-# This cop supports safe autocorrection (--autocorrect).
-# Configuration parameters: EnforcedStyle.
-# SupportedStyles: space, no_space
-Layout/SpaceInsideStringInterpolation:
- Exclude:
- - 'config/initializers/webauthn.rb'
-
-# This cop supports safe autocorrection (--autocorrect).
-# Configuration parameters: AllowInHeredoc.
-Layout/TrailingWhitespace:
- Exclude:
- - 'config/initializers/paperclip.rb'
-
# Configuration parameters: AllowedMethods, AllowedPatterns.
Lint/AmbiguousBlockAssociation:
Exclude:
@@ -621,7 +588,6 @@ RSpec/NoExpectationExample:
RSpec/PendingWithoutReason:
Exclude:
- - 'spec/controllers/statuses_controller_spec.rb'
- 'spec/models/account_spec.rb'
# This cop supports unsafe autocorrection (--autocorrect-all).
@@ -637,10 +603,6 @@ RSpec/RepeatedExample:
Exclude:
- 'spec/policies/status_policy_spec.rb'
-RSpec/RepeatedExampleGroupBody:
- Exclude:
- - 'spec/controllers/statuses_controller_spec.rb'
-
RSpec/StubbedMock:
Exclude:
- 'spec/controllers/api/base_controller_spec.rb'
diff --git a/Dockerfile b/Dockerfile
index 91c26d2ac0..cb5096581c 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -55,7 +55,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"]
ENV DEBIAN_FRONTEND="noninteractive" \
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin"
-# Ignoreing these here since we don't want to pin any versions and the Debian image removes apt-get content after use
+# Ignoring these here since we don't want to pin any versions and the Debian image removes apt-get content after use
# hadolint ignore=DL3008,DL3009
RUN apt-get update && \
echo "Etc/UTC" > /etc/localtime && \
diff --git a/Gemfile b/Gemfile
index e393b2192d..67a0fb0780 100644
--- a/Gemfile
+++ b/Gemfile
@@ -99,54 +99,87 @@ gem 'json-ld'
gem 'json-ld-preloaded', '~> 3.2'
gem 'rdf-normalize', '~> 0.5'
-group :development, :test do
- gem 'fabrication', '~> 2.30'
- gem 'fuubar', '~> 2.5'
- gem 'i18n-tasks', '~> 1.0', require: false
- gem 'rspec-rails', '~> 6.0'
- gem 'rspec_chunked', '~> 0.6'
-
- gem 'rubocop-capybara', require: false
- gem 'rubocop-performance', require: false
- gem 'rubocop-rails', require: false
- gem 'rubocop-rspec', require: false
- gem 'rubocop', require: false
-end
-
-group :production, :test do
- gem 'private_address_check', '~> 0.5'
-end
+gem 'private_address_check', '~> 0.5'
group :test do
- gem 'capybara', '~> 3.39'
- gem 'climate_control'
- gem 'faker', '~> 3.2'
- gem 'json-schema', '~> 4.0'
- gem 'rack-test', '~> 2.1'
- gem 'rails-controller-testing', '~> 1.0'
- gem 'rspec_junit_formatter', '~> 0.6'
+ # RSpec runner for rails
+ gem 'rspec-rails', '~> 6.0'
+
+ # Used to split testing into chunks in CI
+ gem 'rspec_chunked', '~> 0.6'
+
+ # RSpec progress bar formatter
+ gem 'fuubar', '~> 2.5'
+
+ # Extra RSpec extenion methods and helpers for sidekiq
gem 'rspec-sidekiq', '~> 3.1'
+
+ # Browser integration testing
+ gem 'capybara', '~> 3.39'
+
+ # Used to mock environment variables
+ gem 'climate_control', '~> 0.2'
+
+ # Generating fake data for specs
+ gem 'faker', '~> 3.2'
+
+ # Generate test objects for specs
+ gem 'fabrication', '~> 2.30'
+
+ # Add back helpers functions removed in Rails 5.1
+ gem 'rails-controller-testing', '~> 1.0'
+
+ # Validate schemas in specs
+ gem 'json-schema', '~> 4.0'
+
+ # Test harness fo rack components
+ gem 'rack-test', '~> 2.1'
+
+ # Coverage formatter for RSpec test if DISABLE_SIMPLECOV is false
gem 'simplecov', '~> 0.22', require: false
+
+ # Stub web requests for specs
gem 'webmock', '~> 3.18'
end
group :development do
+ # Code linting CLI and plugins
+ gem 'rubocop', require: false
+ gem 'rubocop-capybara', require: false
+ gem 'rubocop-performance', require: false
+ gem 'rubocop-rails', require: false
+ gem 'rubocop-rspec', require: false
+
+ # Annotates modules with schema
gem 'annotate', '~> 3.2'
+
+ # Enhanced error message pages for development
gem 'better_errors', '~> 2.9'
gem 'binding_of_caller', '~> 1.0'
+
+ # Preview mail in the browser
gem 'letter_opener', '~> 1.8'
gem 'letter_opener_web', '~> 2.0'
- gem 'memory_profiler'
+
+ # Security analysis CLI tools
gem 'brakeman', '~> 5.4', require: false
gem 'bundler-audit', '~> 0.9', require: false
+
+ # Linter CLI for HAML files
gem 'haml_lint', require: false
+ # Deployment automation
gem 'capistrano', '~> 3.17'
gem 'capistrano-rails', '~> 1.6'
gem 'capistrano-rbenv', '~> 2.2'
gem 'capistrano-yarn', '~> 2.0'
- gem 'stackprof'
+ # Validate missing i18n keys
+ gem 'i18n-tasks', '~> 1.0', require: false
+
+ # Profiling tools
+ gem 'memory_profiler', require: false
+ gem 'stackprof', require: false
end
group :production do
diff --git a/Gemfile.lock b/Gemfile.lock
index b5d277097a..acea3bbbed 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -601,8 +601,6 @@ GEM
sidekiq (>= 2.4.0)
rspec-support (3.12.0)
rspec_chunked (0.6)
- rspec_junit_formatter (0.6.0)
- rspec-core (>= 2, < 4, != 2.12.0)
rubocop (1.50.2)
json (~> 2.3)
parallel (~> 1.10)
@@ -787,7 +785,7 @@ DEPENDENCIES
capybara (~> 3.39)
charlock_holmes (~> 0.7.7)
chewy (~> 7.3)
- climate_control
+ climate_control (~> 0.2)
cocoon (~> 1.2)
color_diff (~> 0.1)
concurrent-ruby
@@ -866,7 +864,6 @@ DEPENDENCIES
rspec-rails (~> 6.0)
rspec-sidekiq (~> 3.1)
rspec_chunked (~> 0.6)
- rspec_junit_formatter (~> 0.6)
rubocop
rubocop-capybara
rubocop-performance
diff --git a/app/controllers/api/v1/admin/domain_allows_controller.rb b/app/controllers/api/v1/admin/domain_allows_controller.rb
index 61e1d481c7..dd54d67106 100644
--- a/app/controllers/api/v1/admin/domain_allows_controller.rb
+++ b/app/controllers/api/v1/admin/domain_allows_controller.rb
@@ -29,7 +29,7 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
def create
authorize :domain_allow, :create?
- @domain_allow = DomainAllow.find_by(resource_params)
+ @domain_allow = DomainAllow.find_by(domain: resource_params[:domain])
if @domain_allow.nil?
@domain_allow = DomainAllow.create!(resource_params)
diff --git a/app/controllers/api/v1/statuses/reblogs_controller.rb b/app/controllers/api/v1/statuses/reblogs_controller.rb
index 1be15a5a43..e3769437b7 100644
--- a/app/controllers/api/v1/statuses/reblogs_controller.rb
+++ b/app/controllers/api/v1/statuses/reblogs_controller.rb
@@ -2,6 +2,8 @@
class Api::V1::Statuses::ReblogsController < Api::BaseController
include Authorization
+ include Redisable
+ include Lockable
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
before_action :require_user!
@@ -10,7 +12,9 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
override_rate_limit_headers :create, family: :statuses
def create
- @status = ReblogService.new.call(current_account, @reblog, reblog_params)
+ with_redis_lock("reblog:#{current_account.id}:#{@reblog.id}") do
+ @status = ReblogService.new.call(current_account, @reblog, reblog_params)
+ end
render json: @status, serializer: REST::StatusSerializer
end
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index a49d23a9f5..a9d92b6e2b 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -132,7 +132,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def set_sessions
- @sessions = current_user.session_activations
+ @sessions = current_user.session_activations.order(updated_at: :desc)
end
def set_strikes
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
index efae7e35f8..0a1df55066 100644
--- a/app/controllers/oauth/authorized_applications_controller.rb
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -10,6 +10,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :set_body_classes
before_action :set_cache_headers
+ before_action :set_last_used_at_by_app, only: :index, unless: -> { request.format == :json }
+
skip_before_action :require_functional!
include Localized
@@ -40,4 +42,14 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
+
+ def set_last_used_at_by_app
+ @last_used_at_by_app = Doorkeeper::AccessToken
+ .select('DISTINCT ON (application_id) application_id, last_used_at')
+ .where(resource_owner_id: current_resource_owner.id)
+ .where.not(last_used_at: nil)
+ .order(application_id: :desc, last_used_at: :desc)
+ .pluck(:application_id, :last_used_at)
+ .to_h
+ end
end
diff --git a/app/javascript/mastodon/utils/__tests__/html-test.js b/app/javascript/mastodon/utils/__tests__/html-test.js
index ef9296e6c3..d948cf4c5d 100644
--- a/app/javascript/mastodon/utils/__tests__/html-test.js
+++ b/app/javascript/mastodon/utils/__tests__/html-test.js
@@ -1,7 +1,7 @@
import * as html from '../html';
describe('html', () => {
- describe('unsecapeHTML', () => {
+ describe('unescapeHTML', () => {
it('returns unescaped HTML', () => {
const output = html.unescapeHTML('
lorem
ipsum
<br>');
expect(output).toEqual('lorem\n\nipsum\n
');
diff --git a/app/javascript/types/image.d.ts b/app/javascript/types/image.d.ts
index fae2ed7014..15f0007af5 100644
--- a/app/javascript/types/image.d.ts
+++ b/app/javascript/types/image.d.ts
@@ -14,11 +14,6 @@ declare module '*.jpg' {
export default path;
}
-declare module '*.jpg' {
- const path: string;
- export default path;
-}
-
declare module '*.png' {
const path: string;
export default path;
diff --git a/app/lib/activitypub/activity/flag.rb b/app/lib/activitypub/activity/flag.rb
index dc808ad364..dc1932f597 100644
--- a/app/lib/activitypub/activity/flag.rb
+++ b/app/lib/activitypub/activity/flag.rb
@@ -16,7 +16,7 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
@account,
target_account,
status_ids: target_statuses.nil? ? [] : target_statuses.map(&:id),
- comment: @json['content'] || '',
+ comment: report_comment,
uri: report_uri
)
end
@@ -35,4 +35,8 @@ class ActivityPub::Activity::Flag < ActivityPub::Activity
def report_uri
@json['id'] unless @json['id'].nil? || non_matching_uri_hosts?(@account.uri, @json['id'])
end
+
+ def report_comment
+ (@json['content'] || '')[0...5000]
+ end
end
diff --git a/app/lib/activitypub/tag_manager.rb b/app/lib/activitypub/tag_manager.rb
index a65a9565ab..8643286317 100644
--- a/app/lib/activitypub/tag_manager.rb
+++ b/app/lib/activitypub/tag_manager.rb
@@ -28,6 +28,8 @@ class ActivityPub::TagManager
return activity_account_status_url(target.account, target) if target.reblog?
short_account_status_url(target.account, target)
+ when :flag
+ target.uri
end
end
@@ -43,6 +45,8 @@ class ActivityPub::TagManager
account_status_url(target.account, target)
when :emoji
emoji_url(target)
+ when :flag
+ target.uri
end
end
diff --git a/app/lib/application_extension.rb b/app/lib/application_extension.rb
index d61ec0e6e7..4de69c1ead 100644
--- a/app/lib/application_extension.rb
+++ b/app/lib/application_extension.rb
@@ -9,10 +9,6 @@ module ApplicationExtension
validates :redirect_uri, length: { maximum: 2_000 }
end
- def most_recently_used_access_token
- @most_recently_used_access_token ||= access_tokens.where.not(last_used_at: nil).order(last_used_at: :desc).first
- end
-
def confirmation_redirect_uri
redirect_uri.lines.first.strip
end
diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb
index f8a0be636e..dfed69285f 100644
--- a/app/lib/link_details_extractor.rb
+++ b/app/lib/link_details_extractor.rb
@@ -140,7 +140,7 @@ class LinkDetailsExtractor
end
def html
- player_url.present? ? content_tag(:iframe, nil, src: player_url, width: width, height: height, allowtransparency: 'true', scrolling: 'no', frameborder: '0') : nil
+ player_url.present? ? content_tag(:iframe, nil, src: player_url, width: width, height: height, allowfullscreen: 'true', allowtransparency: 'true', scrolling: 'no', frameborder: '0') : nil
end
def width
diff --git a/app/models/report.rb b/app/models/report.rb
index c3a0c4c8b2..e738281adc 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -40,7 +40,10 @@ class Report < ApplicationRecord
scope :resolved, -> { where.not(action_taken_at: nil) }
scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].index_with({ user: [:invite_request, :invite] })) }
- validates :comment, length: { maximum: 1_000 }
+ # A report is considered local if the reporter is local
+ delegate :local?, to: :account
+
+ validates :comment, length: { maximum: 1_000 }, if: :local?
validates :rule_ids, absence: true, unless: :violation?
validate :validate_rule_ids
@@ -51,10 +54,6 @@ class Report < ApplicationRecord
violation: 2_000,
}
- def local?
- false # Force uri_for to use uri attribute
- end
-
before_validation :set_uri, only: :create
after_create_commit :trigger_webhooks
diff --git a/app/services/backup_service.rb b/app/services/backup_service.rb
index 89b8d06cd1..5a99eb50c1 100644
--- a/app/services/backup_service.rb
+++ b/app/services/backup_service.rb
@@ -101,8 +101,8 @@ class BackupService < BaseService
actor[:likes] = 'likes.json'
actor[:bookmarks] = 'bookmarks.json'
- download_to_zip(tar, account.avatar, "avatar#{File.extname(account.avatar.path)}") if account.avatar.exists?
- download_to_zip(tar, account.header, "header#{File.extname(account.header.path)}") if account.header.exists?
+ download_to_zip(zipfile, account.avatar, "avatar#{File.extname(account.avatar.path)}") if account.avatar.exists?
+ download_to_zip(zipfile, account.header, "header#{File.extname(account.header.path)}") if account.header.exists?
json = Oj.dump(actor)
diff --git a/app/validators/vote_validator.rb b/app/validators/vote_validator.rb
index 9bd17fbe80..fa2bd223dc 100644
--- a/app/validators/vote_validator.rb
+++ b/app/validators/vote_validator.rb
@@ -3,8 +3,8 @@
class VoteValidator < ActiveModel::Validator
def validate(vote)
vote.errors.add(:base, I18n.t('polls.errors.expired')) if vote.poll_expired?
-
vote.errors.add(:base, I18n.t('polls.errors.invalid_choice')) if invalid_choice?(vote)
+ vote.errors.add(:base, I18n.t('polls.errors.self_vote')) if self_vote?(vote)
vote.errors.add(:base, I18n.t('polls.errors.already_voted')) if additional_voting_not_allowed?(vote)
end
@@ -27,6 +27,10 @@ class VoteValidator < ActiveModel::Validator
vote.choice.negative? || vote.choice >= vote.poll.options.size
end
+ def self_vote?(vote)
+ vote.account_id == vote.poll.account_id
+ end
+
def already_voted_for_same_choice_on_multiple_poll?(vote)
if vote.persisted?
account_votes_on_same_poll(vote).where(choice: vote.choice).where.not(poll_votes: { id: vote }).exists?
diff --git a/app/views/oauth/authorized_applications/index.html.haml b/app/views/oauth/authorized_applications/index.html.haml
index 0280d8aef8..55d8524dbe 100644
--- a/app/views/oauth/authorized_applications/index.html.haml
+++ b/app/views/oauth/authorized_applications/index.html.haml
@@ -18,8 +18,8 @@
.announcements-list__item__action-bar
.announcements-list__item__meta
- - if application.most_recently_used_access_token
- = t('doorkeeper.authorized_applications.index.last_used_at', date: l(application.most_recently_used_access_token.last_used_at.to_date))
+ - if @last_used_at_by_app[application.id]
+ = t('doorkeeper.authorized_applications.index.last_used_at', date: l(@last_used_at_by_app[application.id].to_date))
- else
= t('doorkeeper.authorized_applications.index.never_used')
diff --git a/app/workers/post_process_media_worker.rb b/app/workers/post_process_media_worker.rb
index 996d5def91..2d11b00c20 100644
--- a/app/workers/post_process_media_worker.rb
+++ b/app/workers/post_process_media_worker.rb
@@ -24,7 +24,7 @@ class PostProcessMediaWorker
media_attachment.processing = :in_progress
media_attachment.save
- # Because paperclip-av-transcover overwrites this attribute
+ # Because paperclip-av-transcoder overwrites this attribute
# we will save it here and restore it after reprocess is done
previous_meta = media_attachment.file_meta
diff --git a/config/initializers/ffmpeg.rb b/config/initializers/ffmpeg.rb
index 4c0bf779d8..cd5914eb55 100644
--- a/config/initializers/ffmpeg.rb
+++ b/config/initializers/ffmpeg.rb
@@ -1,3 +1,3 @@
if ENV['FFMPEG_BINARY'].present?
- FFMPEG.ffmpeg_binary = ENV['FFMPEG_BINARY']
+ FFMPEG.ffmpeg_binary = ENV['FFMPEG_BINARY']
end
diff --git a/config/initializers/omniauth.rb b/config/initializers/omniauth.rb
index f016701467..c2cd444f08 100644
--- a/config/initializers/omniauth.rb
+++ b/config/initializers/omniauth.rb
@@ -73,7 +73,7 @@ Devise.setup do |config|
oidc_options[:display_name] = ENV['OIDC_DISPLAY_NAME'] #OPTIONAL
oidc_options[:issuer] = ENV['OIDC_ISSUER'] if ENV['OIDC_ISSUER'] #NEED
oidc_options[:discovery] = ENV['OIDC_DISCOVERY'] == 'true' if ENV['OIDC_DISCOVERY'] #OPTIONAL (default: false)
- oidc_options[:client_auth_method] = ENV['OIDC_CLIENT_AUTH_METHOD'] if ENV['OIDC_CLIENT_AUTH_METHOD'] #OPTIONAL (default: basic)
+ oidc_options[:client_auth_method] = ENV['OIDC_CLIENT_AUTH_METHOD'] if ENV['OIDC_CLIENT_AUTH_METHOD'] #OPTIONAL (default: basic)
scope_string = ENV['OIDC_SCOPE'] if ENV['OIDC_SCOPE'] #NEED
scopes = scope_string.split(',')
oidc_options[:scope] = scopes.map { |x| x.to_sym }
diff --git a/config/initializers/paperclip.rb b/config/initializers/paperclip.rb
index 9f0ffc6dc7..093d2ba9ae 100644
--- a/config/initializers/paperclip.rb
+++ b/config/initializers/paperclip.rb
@@ -61,13 +61,13 @@ if ENV['S3_ENABLED'] == 'true'
s3_options: {
signature_version: ENV.fetch('S3_SIGNATURE_VERSION') { 'v4' },
- http_open_timeout: ENV.fetch('S3_OPEN_TIMEOUT'){ '5' }.to_i,
- http_read_timeout: ENV.fetch('S3_READ_TIMEOUT'){ '5' }.to_i,
+ http_open_timeout: ENV.fetch('S3_OPEN_TIMEOUT') { '5' }.to_i,
+ http_read_timeout: ENV.fetch('S3_READ_TIMEOUT') { '5' }.to_i,
http_idle_timeout: 5,
retry_limit: 0,
}
)
-
+
Paperclip::Attachment.default_options[:s3_permissions] = ->(*) { nil } if ENV['S3_PERMISSION'] == ''
if ENV.has_key?('S3_ENDPOINT')
@@ -124,7 +124,7 @@ elsif ENV['SWIFT_ENABLED'] == 'true'
openstack_cache_ttl: ENV.fetch('SWIFT_CACHE_TTL') { 60 },
openstack_temp_url_key: ENV['SWIFT_TEMP_URL_KEY'],
},
-
+
fog_file: { 'Cache-Control' => 'public, max-age=315576000, immutable' },
fog_directory: ENV['SWIFT_CONTAINER'],
diff --git a/config/initializers/webauthn.rb b/config/initializers/webauthn.rb
index a0a5b81537..a4f027947c 100644
--- a/config/initializers/webauthn.rb
+++ b/config/initializers/webauthn.rb
@@ -1,7 +1,7 @@
WebAuthn.configure do |config|
# This value needs to match `window.location.origin` evaluated by
# the User Agent during registration and authentication ceremonies.
- config.origin = "#{Rails.configuration.x.use_https ? 'https' : 'http' }://#{Rails.configuration.x.web_domain}"
+ config.origin = "#{Rails.configuration.x.use_https ? 'https' : 'http'}://#{Rails.configuration.x.web_domain}"
# Relying Party name for display purposes
config.rp_name = "Mastodon"
diff --git a/config/locales/en.yml b/config/locales/en.yml
index aea9656602..76198763a4 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1446,6 +1446,7 @@ en:
expired: The poll has already ended
invalid_choice: The chosen vote option does not exist
over_character_limit: cannot be longer than %{max} characters each
+ self_vote: You cannot vote in your own polls
too_few_options: must have more than one item
too_many_options: can't contain more than %{max} items
preferences:
diff --git a/jest.config.js b/jest.config.js
index d6e7bc65f7..922b0f550c 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -10,7 +10,6 @@ const config = {
'/tmp/',
'/app/javascript/themes/',
],
- setupFiles: ['raf/polyfill'],
setupFilesAfterEnv: ['/app/javascript/mastodon/test_setup.js'],
collectCoverageFrom: [
'app/javascript/mastodon/**/*.{js,jsx,ts,tsx}',
diff --git a/lib/mastodon/media_cli.rb b/lib/mastodon/media_cli.rb
index 7dacd8d3d4..1bedcd9beb 100644
--- a/lib/mastodon/media_cli.rb
+++ b/lib/mastodon/media_cli.rb
@@ -24,7 +24,7 @@ module Mastodon
desc 'remove', 'Remove remote media files, headers or avatars'
long_desc <<-DESC
Removes locally cached copies of media attachments (and optionally profile
- headers and avatars) from other servers. By default, only media attachements
+ headers and avatars) from other servers. By default, only media attachments
are removed.
The --days option specifies how old media attachments have to be before
they are removed. In case of avatars and headers, it specifies how old
diff --git a/lib/tasks/tests.rake b/lib/tasks/tests.rake
index ceb421f4b2..60399c9de1 100644
--- a/lib/tasks/tests.rake
+++ b/lib/tasks/tests.rake
@@ -25,7 +25,7 @@ namespace :tests do
end
if Account.where(domain: Rails.configuration.x.local_domain).exists?
- puts 'Faux remote accounts not properly claned up'
+ puts 'Faux remote accounts not properly cleaned up'
exit(1)
end
diff --git a/package.json b/package.json
index ad98f977dd..11831f02db 100644
--- a/package.json
+++ b/package.json
@@ -76,7 +76,7 @@
"intl-messageformat": "^2.2.0",
"intl-relativeformat": "^6.4.3",
"js-yaml": "^4.1.0",
- "jsdom": "^21.1.2",
+ "jsdom": "^22.0.0",
"lodash": "^4.17.21",
"mark-loader": "^0.1.6",
"marky": "^1.2.5",
@@ -158,7 +158,6 @@
"@types/pg": "^8.6.6",
"@types/prop-types": "^15.7.5",
"@types/punycode": "^2.1.0",
- "@types/raf": "^3.4.0",
"@types/react": "^16.14.38",
"@types/react-dom": "^16.9.18",
"@types/react-helmet": "^6.1.6",
@@ -198,7 +197,6 @@
"jest-environment-jsdom": "^29.5.0",
"lint-staged": "^13.2.2",
"prettier": "^2.8.8",
- "raf": "^3.4.1",
"react-intl-translations-manager": "^5.0.3",
"react-test-renderer": "^16.14.0",
"stylelint": "^15.6.1",
diff --git a/spec/controllers/admin/confirmations_controller_spec.rb b/spec/controllers/admin/confirmations_controller_spec.rb
index ffab56d9aa..181616a66e 100644
--- a/spec/controllers/admin/confirmations_controller_spec.rb
+++ b/spec/controllers/admin/confirmations_controller_spec.rb
@@ -32,7 +32,7 @@ RSpec.describe Admin::ConfirmationsController do
end
end
- describe 'POST #resernd' do
+ describe 'POST #resend' do
subject { post :resend, params: { account_id: user.account.id } }
let!(:user) { Fabricate(:user, confirmed_at: confirmed_at) }
diff --git a/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb
index e5ee288827..fe39596dfd 100644
--- a/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb
@@ -5,23 +5,182 @@ require 'rails_helper'
describe Api::V1::Admin::CanonicalEmailBlocksController do
render_views
- let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') }
- let(:account) { Fabricate(:account) }
+ let(:role) { UserRole.find_by(name: 'Admin') }
+ let(:user) { Fabricate(:user, role: role) }
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+ let(:scopes) { 'admin:read:canonical_email_blocks admin:write:canonical_email_blocks' }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
+ shared_examples 'forbidden for wrong scope' do |wrong_scope|
+ let(:scopes) { wrong_scope }
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ shared_examples 'forbidden for wrong role' do |wrong_role|
+ let(:role) { UserRole.find_by(name: wrong_role) }
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
describe 'GET #index' do
+ context 'with wrong scope' do
+ before do
+ get :index
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'read:statuses'
+ end
+
+ context 'with wrong role' do
+ before do
+ get :index
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
it 'returns http success' do
- get :index, params: { account_id: account.id, limit: 2 }
+ get :index
expect(response).to have_http_status(200)
end
+
+ context 'when there is no canonical email block' do
+ it 'returns an empty list' do
+ get :index
+
+ body = body_as_json
+
+ expect(body).to be_empty
+ end
+ end
+
+ context 'when there are canonical email blocks' do
+ let!(:canonical_email_blocks) { Fabricate.times(5, :canonical_email_block) }
+ let(:expected_email_hashes) { canonical_email_blocks.pluck(:canonical_email_hash) }
+
+ it 'returns the correct canonical email hashes' do
+ get :index
+
+ json = body_as_json
+
+ expect(json.pluck(:canonical_email_hash)).to match_array(expected_email_hashes)
+ end
+
+ context 'with limit param' do
+ let(:params) { { limit: 2 } }
+
+ it 'returns only the requested number of canonical email blocks' do
+ get :index, params: params
+
+ json = body_as_json
+
+ expect(json.size).to eq(params[:limit])
+ end
+ end
+
+ context 'with since_id param' do
+ let(:params) { { since_id: canonical_email_blocks[1].id } }
+
+ it 'returns only the canonical email blocks after since_id' do
+ get :index, params: params
+
+ canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s)
+ json = body_as_json
+
+ expect(json.pluck(:id)).to match_array(canonical_email_blocks_ids[2..])
+ end
+ end
+
+ context 'with max_id param' do
+ let(:params) { { max_id: canonical_email_blocks[3].id } }
+
+ it 'returns only the canonical email blocks before max_id' do
+ get :index, params: params
+
+ canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s)
+ json = body_as_json
+
+ expect(json.pluck(:id)).to match_array(canonical_email_blocks_ids[..2])
+ end
+ end
+ end
+ end
+
+ describe 'GET #show' do
+ let!(:canonical_email_block) { Fabricate(:canonical_email_block) }
+ let(:params) { { id: canonical_email_block.id } }
+
+ context 'with wrong scope' do
+ before do
+ get :show, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'read:statuses'
+ end
+
+ context 'with wrong role' do
+ before do
+ get :show, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
+ context 'when canonical email block exists' do
+ it 'returns http success' do
+ get :show, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns canonical email block data correctly' do
+ get :show, params: params
+
+ json = body_as_json
+
+ expect(json[:id]).to eq(canonical_email_block.id.to_s)
+ expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
+ end
+ end
+
+ context 'when canonical block does not exist' do
+ it 'returns http not found' do
+ get :show, params: { id: 0 }
+
+ expect(response).to have_http_status(404)
+ end
+ end
end
describe 'POST #test' do
+ context 'with wrong scope' do
+ before do
+ post :test
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'read:statuses'
+ end
+
+ context 'with wrong role' do
+ before do
+ post :test, params: { email: 'whatever@email.com' }
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
context 'when required email is not provided' do
it 'returns http bad request' do
post :test
@@ -68,4 +227,132 @@ describe Api::V1::Admin::CanonicalEmailBlocksController do
end
end
end
+
+ describe 'POST #create' do
+ let(:params) { { email: 'example@email.com' } }
+ let(:canonical_email_block) { CanonicalEmailBlock.new(email: params[:email]) }
+
+ context 'with wrong scope' do
+ before do
+ post :create, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'read:statuses'
+ end
+
+ context 'with wrong role' do
+ before do
+ post :create, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
+ it 'returns http success' do
+ post :create, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns canonical_email_hash correctly' do
+ post :create, params: params
+
+ json = body_as_json
+
+ expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
+ end
+
+ context 'when required email param is not provided' do
+ it 'returns http unprocessable entity' do
+ post :create
+
+ expect(response).to have_http_status(422)
+ end
+ end
+
+ context 'when canonical_email_hash param is provided instead of email' do
+ let(:params) { { canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } }
+
+ it 'returns http success' do
+ post :create, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns correct canonical_email_hash' do
+ post :create, params: params
+
+ json = body_as_json
+
+ expect(json[:canonical_email_hash]).to eq(params[:canonical_email_hash])
+ end
+ end
+
+ context 'when both email and canonical_email_hash params are provided' do
+ let(:params) { { email: 'example@email.com', canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } }
+
+ it 'returns http success' do
+ post :create, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'ignores canonical_email_hash param' do
+ post :create, params: params
+
+ json = body_as_json
+
+ expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash)
+ end
+ end
+
+ context 'when canonical email was already blocked' do
+ before do
+ canonical_email_block.save
+ end
+
+ it 'returns http unprocessable entity' do
+ post :create, params: params
+
+ expect(response).to have_http_status(422)
+ end
+ end
+ end
+
+ describe 'DELETE #destroy' do
+ let!(:canonical_email_block) { Fabricate(:canonical_email_block) }
+ let(:params) { { id: canonical_email_block.id } }
+
+ context 'with wrong scope' do
+ before do
+ delete :destroy, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'read:statuses'
+ end
+
+ context 'with wrong role' do
+ before do
+ delete :destroy, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
+ it 'returns http success' do
+ delete :destroy, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ context 'when canonical email block is not found' do
+ it 'returns http not found' do
+ delete :destroy, params: { id: 0 }
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
end
diff --git a/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb b/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb
index 9db8a35b46..ca63ea5a7e 100644
--- a/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb
@@ -128,5 +128,13 @@ RSpec.describe Api::V1::Admin::DomainAllowsController do
expect(response).to have_http_status(422)
end
end
+
+ context 'when domain name is not specified' do
+ it 'returns http unprocessable entity' do
+ post :create
+
+ expect(response).to have_http_status(422)
+ end
+ end
end
end
diff --git a/spec/controllers/api/v1/admin/email_domain_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/email_domain_blocks_controller_spec.rb
index a92a298699..3643eb0f3d 100644
--- a/spec/controllers/api/v1/admin/email_domain_blocks_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/email_domain_blocks_controller_spec.rb
@@ -5,19 +5,280 @@ require 'rails_helper'
describe Api::V1::Admin::EmailDomainBlocksController do
render_views
- let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') }
+ let(:role) { UserRole.find_by(name: 'Admin') }
+ let(:user) { Fabricate(:user, role: role) }
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
let(:account) { Fabricate(:account) }
+ let(:scopes) { 'admin:read:email_domain_blocks admin:write:email_domain_blocks' }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
+ shared_examples 'forbidden for wrong scope' do |wrong_scope|
+ let(:scopes) { wrong_scope }
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ shared_examples 'forbidden for wrong role' do |wrong_role|
+ let(:role) { UserRole.find_by(name: wrong_role) }
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
describe 'GET #index' do
+ context 'with wrong scope' do
+ before do
+ get :index
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'read:statuses'
+ end
+
+ context 'with wrong role' do
+ before do
+ get :index
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
it 'returns http success' do
- get :index, params: { account_id: account.id, limit: 2 }
+ get :index
expect(response).to have_http_status(200)
end
+
+ context 'when there is no email domain block' do
+ it 'returns an empty list' do
+ get :index
+
+ json = body_as_json
+
+ expect(json).to be_empty
+ end
+ end
+
+ context 'when there are email domain blocks' do
+ let!(:email_domain_blocks) { Fabricate.times(5, :email_domain_block) }
+ let(:blocked_email_domains) { email_domain_blocks.pluck(:domain) }
+
+ it 'return the correct blocked email domains' do
+ get :index
+
+ json = body_as_json
+
+ expect(json.pluck(:domain)).to match_array(blocked_email_domains)
+ end
+
+ context 'with limit param' do
+ let(:params) { { limit: 2 } }
+
+ it 'returns only the requested number of email domain blocks' do
+ get :index, params: params
+
+ json = body_as_json
+
+ expect(json.size).to eq(params[:limit])
+ end
+ end
+
+ context 'with since_id param' do
+ let(:params) { { since_id: email_domain_blocks[1].id } }
+
+ it 'returns only the email domain blocks after since_id' do
+ get :index, params: params
+
+ email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s)
+ json = body_as_json
+
+ expect(json.pluck(:id)).to match_array(email_domain_blocks_ids[2..])
+ end
+ end
+
+ context 'with max_id param' do
+ let(:params) { { max_id: email_domain_blocks[3].id } }
+
+ it 'returns only the email domain blocks before max_id' do
+ get :index, params: params
+
+ email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s)
+ json = body_as_json
+
+ expect(json.pluck(:id)).to match_array(email_domain_blocks_ids[..2])
+ end
+ end
+ end
+ end
+
+ describe 'GET #show' do
+ let!(:email_domain_block) { Fabricate(:email_domain_block) }
+ let(:params) { { id: email_domain_block.id } }
+
+ context 'with wrong scope' do
+ before do
+ get :show, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'read:statuses'
+ end
+
+ context 'with wrong role' do
+ before do
+ get :show, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
+ context 'when email domain block exists' do
+ it 'returns http success' do
+ get :show, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns the correct blocked domain' do
+ get :show, params: params
+
+ json = body_as_json
+
+ expect(json[:domain]).to eq(email_domain_block.domain)
+ end
+ end
+
+ context 'when email domain block does not exist' do
+ it 'returns http not found' do
+ get :show, params: { id: 0 }
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'POST #create' do
+ let(:params) { { domain: 'example.com' } }
+
+ context 'with wrong scope' do
+ before do
+ post :create, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'read:statuses'
+ end
+
+ context 'with wrong role' do
+ before do
+ post :create, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
+ it 'returns http success' do
+ post :create, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns the correct blocked email domain' do
+ post :create, params: params
+
+ json = body_as_json
+
+ expect(json[:domain]).to eq(params[:domain])
+ end
+
+ context 'when domain param is not provided' do
+ let(:params) { { domain: '' } }
+
+ it 'returns http unprocessable entity' do
+ post :create, params: params
+
+ expect(response).to have_http_status(422)
+ end
+ end
+
+ context 'when provided domain name has an invalid character' do
+ let(:params) { { domain: 'do\uD800.com' } }
+
+ it 'returns http unprocessable entity' do
+ post :create, params: params
+
+ expect(response).to have_http_status(422)
+ end
+ end
+
+ context 'when provided domain is already blocked' do
+ before do
+ EmailDomainBlock.create(params)
+ end
+
+ it 'returns http unprocessable entity' do
+ post :create, params: params
+
+ expect(response).to have_http_status(422)
+ end
+ end
+ end
+
+ describe 'DELETE #destroy' do
+ let!(:email_domain_block) { Fabricate(:email_domain_block) }
+ let(:params) { { id: email_domain_block.id } }
+
+ context 'with wrong scope' do
+ before do
+ delete :destroy, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'read:statuses'
+ end
+
+ context 'with wrong role' do
+ before do
+ delete :destroy, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
+ it 'returns http success' do
+ delete :destroy, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns an empty body' do
+ delete :destroy, params: params
+
+ json = body_as_json
+
+ expect(json).to be_empty
+ end
+
+ it 'deletes email domain block' do
+ delete :destroy, params: params
+
+ email_domain_block = EmailDomainBlock.find_by(id: params[:id])
+
+ expect(email_domain_block).to be_nil
+ end
+
+ context 'when email domain block does not exist' do
+ it 'returns http not found' do
+ delete :destroy, params: { id: 0 }
+
+ expect(response).to have_http_status(404)
+ end
+ end
end
end
diff --git a/spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb
index 50e2ae9687..a5787883ee 100644
--- a/spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb
+++ b/spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb
@@ -5,19 +5,305 @@ require 'rails_helper'
describe Api::V1::Admin::IpBlocksController do
render_views
- let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) }
- let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') }
- let(:account) { Fabricate(:account) }
+ let(:role) { UserRole.find_by(name: 'Admin') }
+ let(:user) { Fabricate(:user, role: role) }
+ let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
+ let(:scopes) { 'admin:read:ip_blocks admin:write:ip_blocks' }
before do
allow(controller).to receive(:doorkeeper_token) { token }
end
+ shared_examples 'forbidden for wrong scope' do |wrong_scope|
+ let(:scopes) { wrong_scope }
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
+ shared_examples 'forbidden for wrong role' do |wrong_role|
+ let(:role) { UserRole.find_by(name: wrong_role) }
+
+ it 'returns http forbidden' do
+ expect(response).to have_http_status(403)
+ end
+ end
+
describe 'GET #index' do
+ context 'with wrong scope' do
+ before do
+ get :index
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks'
+ end
+
+ context 'with wrong role' do
+ before do
+ get :index
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
it 'returns http success' do
- get :index, params: { account_id: account.id, limit: 2 }
+ get :index
expect(response).to have_http_status(200)
end
+
+ context 'when there is no ip block' do
+ it 'returns an empty body' do
+ get :index
+
+ json = body_as_json
+
+ expect(json).to be_empty
+ end
+ end
+
+ context 'when there are ip blocks' do
+ let!(:ip_blocks) do
+ [
+ IpBlock.create(ip: '192.0.2.0/24', severity: :no_access),
+ IpBlock.create(ip: '172.16.0.1', severity: :sign_up_requires_approval, comment: 'Spam'),
+ IpBlock.create(ip: '2001:0db8::/32', severity: :sign_up_block, expires_in: 10.days),
+ ]
+ end
+ let(:expected_response) do
+ ip_blocks.map do |ip_block|
+ {
+ id: ip_block.id.to_s,
+ ip: ip_block.ip,
+ severity: ip_block.severity.to_s,
+ comment: ip_block.comment,
+ created_at: ip_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
+ expires_at: ip_block.expires_at&.strftime('%Y-%m-%dT%H:%M:%S.%LZ'),
+ }
+ end
+ end
+
+ it 'returns the correct blocked ips' do
+ get :index
+
+ json = body_as_json
+
+ expect(json).to match_array(expected_response)
+ end
+
+ context 'with limit param' do
+ let(:params) { { limit: 2 } }
+
+ it 'returns only the requested number of ip blocks' do
+ get :index, params: params
+
+ json = body_as_json
+
+ expect(json.size).to eq(params[:limit])
+ end
+ end
+ end
+ end
+
+ describe 'GET #show' do
+ let!(:ip_block) { IpBlock.create(ip: '192.0.2.0/24', severity: :no_access) }
+ let(:params) { { id: ip_block.id } }
+
+ context 'with wrong scope' do
+ before do
+ get :show, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks'
+ end
+
+ context 'with wrong role' do
+ before do
+ get :show, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
+ it 'returns http success' do
+ get :show, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns the correct ip block' do
+ get :show, params: params
+
+ json = body_as_json
+
+ expect(json[:ip]).to eq("#{ip_block.ip}/#{ip_block.ip.prefix}")
+ expect(json[:severity]).to eq(ip_block.severity.to_s)
+ end
+
+ context 'when ip block does not exist' do
+ it 'returns http not found' do
+ get :show, params: { id: 0 }
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'POST #create' do
+ let(:params) { { ip: '151.0.32.55', severity: 'no_access', comment: 'Spam' } }
+
+ context 'with wrong scope' do
+ before do
+ post :create, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong scope', 'admin:read:ip_blocks'
+ end
+
+ context 'with wrong role' do
+ before do
+ post :create, params: params
+ end
+
+ it_behaves_like 'forbidden for wrong role', ''
+ it_behaves_like 'forbidden for wrong role', 'Moderator'
+ end
+
+ it 'returns http success' do
+ post :create, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns the correct ip block' do
+ post :create, params: params
+
+ json = body_as_json
+
+ expect(json[:ip]).to eq("#{params[:ip]}/32")
+ expect(json[:severity]).to eq(params[:severity])
+ expect(json[:comment]).to eq(params[:comment])
+ end
+
+ context 'when ip is not provided' do
+ let(:params) { { ip: '', severity: 'no_access' } }
+
+ it 'returns http unprocessable entity' do
+ post :create, params: params
+
+ expect(response).to have_http_status(422)
+ end
+ end
+
+ context 'when severity is not provided' do
+ let(:params) { { ip: '173.65.23.1', severity: '' } }
+
+ it 'returns http unprocessable entity' do
+ post :create, params: params
+
+ expect(response).to have_http_status(422)
+ end
+ end
+
+ context 'when provided ip is already blocked' do
+ before do
+ IpBlock.create(params)
+ end
+
+ it 'returns http unprocessable entity' do
+ post :create, params: params
+
+ expect(response).to have_http_status(422)
+ end
+ end
+
+ context 'when provided ip address is invalid' do
+ let(:params) { { ip: '520.13.54.120', severity: 'no_access' } }
+
+ it 'returns http unprocessable entity' do
+ post :create, params: params
+
+ expect(response).to have_http_status(422)
+ end
+ end
+ end
+
+ describe 'PUT #update' do
+ context 'when ip block exists' do
+ let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access', comment: 'Spam', expires_in: 48.hours) }
+ let(:params) { { id: ip_block.id, severity: 'sign_up_requires_approval', comment: 'Decreasing severity' } }
+
+ it 'returns http success' do
+ put :update, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns the correct ip block' do
+ put :update, params: params
+
+ json = body_as_json
+
+ expect(json).to match(hash_including({
+ ip: "#{ip_block.ip}/#{ip_block.ip.prefix}",
+ severity: 'sign_up_requires_approval',
+ comment: 'Decreasing severity',
+ }))
+ end
+
+ it 'updates the severity correctly' do
+ expect { put :update, params: params }.to change { ip_block.reload.severity }.from('no_access').to('sign_up_requires_approval')
+ end
+
+ it 'updates the comment correctly' do
+ expect { put :update, params: params }.to change { ip_block.reload.comment }.from('Spam').to('Decreasing severity')
+ end
+ end
+
+ context 'when ip block does not exist' do
+ it 'returns http not found' do
+ put :update, params: { id: 0 }
+
+ expect(response).to have_http_status(404)
+ end
+ end
+ end
+
+ describe 'DELETE #destroy' do
+ context 'when ip block exists' do
+ let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access') }
+ let(:params) { { id: ip_block.id } }
+
+ it 'returns http success' do
+ delete :destroy, params: params
+
+ expect(response).to have_http_status(200)
+ end
+
+ it 'returns an empty body' do
+ delete :destroy, params: params
+
+ json = body_as_json
+
+ expect(json).to be_empty
+ end
+
+ it 'deletes the ip block' do
+ delete :destroy, params: params
+
+ expect(IpBlock.find_by(id: ip_block.id)).to be_nil
+ end
+ end
+
+ context 'when ip block does not exist' do
+ it 'returns http not found' do
+ delete :destroy, params: { id: 0 }
+
+ expect(response).to have_http_status(404)
+ end
+ end
end
end
diff --git a/spec/controllers/concerns/signature_verification_spec.rb b/spec/controllers/concerns/signature_verification_spec.rb
index df20a5d7ef..e6b7f18498 100644
--- a/spec/controllers/concerns/signature_verification_spec.rb
+++ b/spec/controllers/concerns/signature_verification_spec.rb
@@ -129,7 +129,7 @@ describe ApplicationController do
end
end
- context 'with request with unparseable Date header' do
+ context 'with request with unparsable Date header' do
before do
get :success
diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb
index c846dd1d63..1885814cda 100644
--- a/spec/controllers/statuses_controller_spec.rb
+++ b/spec/controllers/statuses_controller_spec.rb
@@ -719,65 +719,180 @@ describe StatusesController do
end
context 'when status is public' do
- pending
+ before do
+ status.update(visibility: :public)
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
end
context 'when status is private' do
- pending
+ before do
+ status.update(visibility: :private)
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http not_found' do
+ expect(response).to have_http_status(404)
+ end
end
context 'when status is direct' do
- pending
+ before do
+ status.update(visibility: :direct)
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http not_found' do
+ expect(response).to have_http_status(404)
+ end
end
context 'when signed-in' do
+ let(:user) { Fabricate(:user) }
+
+ before do
+ sign_in(user)
+ end
+
context 'when status is public' do
- pending
+ before do
+ status.update(visibility: :public)
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
end
context 'when status is private' do
+ before do
+ status.update(visibility: :private)
+ end
+
context 'when user is authorized to see it' do
- pending
+ before do
+ user.account.follow!(account)
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
end
context 'when user is not authorized to see it' do
- pending
+ before do
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http not_found' do
+ expect(response).to have_http_status(404)
+ end
end
end
context 'when status is direct' do
+ before do
+ status.update(visibility: :direct)
+ end
+
context 'when user is authorized to see it' do
- pending
+ before do
+ Fabricate(:mention, account: user.account, status: status)
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
end
context 'when user is not authorized to see it' do
- pending
+ before do
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http not_found' do
+ expect(response).to have_http_status(404)
+ end
end
end
end
context 'with signature' do
+ let(:remote_account) { Fabricate(:account, domain: 'example.com') }
+
+ before do
+ allow(controller).to receive(:signed_request_actor).and_return(remote_account)
+ end
+
context 'when status is public' do
- pending
+ before do
+ status.update(visibility: :public)
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(:success)
+ end
end
context 'when status is private' do
+ before do
+ status.update(visibility: :private)
+ end
+
context 'when user is authorized to see it' do
- pending
+ before do
+ remote_account.follow!(account)
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
end
context 'when user is not authorized to see it' do
- pending
+ before do
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http not_found' do
+ expect(response).to have_http_status(404)
+ end
end
end
context 'when status is direct' do
+ before do
+ status.update(visibility: :direct)
+ end
+
context 'when user is authorized to see it' do
- pending
+ before do
+ Fabricate(:mention, account: remote_account, status: status)
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
end
context 'when user is not authorized to see it' do
- pending
+ before do
+ get :activity, params: { account_username: account.username, id: status.id }
+ end
+
+ it 'returns http not_found' do
+ expect(response).to have_http_status(404)
+ end
end
end
end
diff --git a/spec/fabricators/canonical_email_block_fabricator.rb b/spec/fabricators/canonical_email_block_fabricator.rb
index 21d7c24023..3a018059fc 100644
--- a/spec/fabricators/canonical_email_block_fabricator.rb
+++ b/spec/fabricators/canonical_email_block_fabricator.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
Fabricator(:canonical_email_block) do
- email 'test@example.com'
+ email { sequence(:email) { |i| "#{i}#{Faker::Internet.email}" } }
reference_account { Fabricate(:account) }
end
diff --git a/spec/lib/activitypub/activity/flag_spec.rb b/spec/lib/activitypub/activity/flag_spec.rb
index 005e185e6b..601473069b 100644
--- a/spec/lib/activitypub/activity/flag_spec.rb
+++ b/spec/lib/activitypub/activity/flag_spec.rb
@@ -39,6 +39,37 @@ RSpec.describe ActivityPub::Activity::Flag do
end
end
+ context 'when the report comment is excessively long' do
+ subject do
+ described_class.new({
+ '@context': 'https://www.w3.org/ns/activitystreams',
+ id: flag_id,
+ type: 'Flag',
+ content: long_comment,
+ actor: ActivityPub::TagManager.instance.uri_for(sender),
+ object: [
+ ActivityPub::TagManager.instance.uri_for(flagged),
+ ActivityPub::TagManager.instance.uri_for(status),
+ ],
+ }.with_indifferent_access, sender)
+ end
+
+ let(:long_comment) { Faker::Lorem.characters(number: 6000) }
+
+ before do
+ subject.perform
+ end
+
+ it 'creates a report but with a truncated comment' do
+ report = Report.find_by(account: sender, target_account: flagged)
+
+ expect(report).to_not be_nil
+ expect(report.comment.length).to eq 5000
+ expect(report.comment).to eq long_comment[0...5000]
+ expect(report.status_ids).to eq [status.id]
+ end
+ end
+
context 'when the reported status is private and should not be visible to the remote server' do
let(:status) { Fabricate(:status, account: flagged, uri: 'foobar', visibility: :private) }
diff --git a/spec/lib/mastodon/migration_warning_spec.rb b/spec/lib/mastodon/migration_warning_spec.rb
new file mode 100644
index 0000000000..4adf0837ab
--- /dev/null
+++ b/spec/lib/mastodon/migration_warning_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+require 'mastodon/migration_warning'
+
+describe Mastodon::MigrationWarning do
+ describe 'migration_duration_warning' do
+ before do
+ allow(migration).to receive(:valid_environment?).and_return(true)
+ allow(migration).to receive(:sleep).with(1)
+ end
+
+ let(:migration) { Class.new(ActiveRecord::Migration[6.1]).extend(described_class) }
+
+ context 'with the default message' do
+ it 'warns about long migrations' do
+ expectation = expect { migration.migration_duration_warning }
+
+ expectation.to output(/interrupt this migration/).to_stdout
+ expectation.to output(/Continuing in 5/).to_stdout
+ end
+ end
+
+ context 'with an additional message' do
+ it 'warns about long migrations' do
+ expectation = expect { migration.migration_duration_warning('Get ready for it') }
+
+ expectation.to output(/interrupt this migration/).to_stdout
+ expectation.to output(/Get ready for it/).to_stdout
+ expectation.to output(/Continuing in 5/).to_stdout
+ end
+ end
+ end
+end
diff --git a/spec/models/account_migration_spec.rb b/spec/models/account_migration_spec.rb
index 0d97ea7e77..519b9a97a5 100644
--- a/spec/models/account_migration_spec.rb
+++ b/spec/models/account_migration_spec.rb
@@ -25,7 +25,7 @@ RSpec.describe AccountMigration do
end
end
- context 'with unresolveable account' do
+ context 'with unresolvable account' do
let(:target_acct) { 'target@remote' }
before do
diff --git a/spec/models/account_spec.rb b/spec/models/account_spec.rb
index bbe35f5793..6e9c608ab0 100644
--- a/spec/models/account_spec.rb
+++ b/spec/models/account_spec.rb
@@ -698,7 +698,7 @@ RSpec.describe Account do
expect(subject.match('Check this out https://medium.com/@alice/some-article#.abcdef123')).to be_nil
end
- xit 'does not match URL querystring' do
+ xit 'does not match URL query string' do
expect(subject.match('https://example.com/?x=@alice')).to be_nil
end
end
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index b006f60bb6..0093dcd8de 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -121,10 +121,17 @@ describe Report do
end
describe 'validations' do
- it 'is invalid if comment is longer than 1000 characters' 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 1000 characters only if reporter is local' do
report = Fabricate.build(:report, comment: Faker::Lorem.characters(number: 1001))
- report.valid?
+ expect(report.valid?).to be false
expect(report).to model_have_error_on_field(:comment)
end
+
+ it 'is valid if comment is longer than 1000 characters and reporter is not local' do
+ report = Fabricate.build(:report, account: remote_account, comment: Faker::Lorem.characters(number: 1001))
+ expect(report.valid?).to be true
+ end
end
end
diff --git a/spec/models/user_settings/setting_spec.rb b/spec/models/user_settings/setting_spec.rb
index 9884ae4f89..8c8d31ec54 100644
--- a/spec/models/user_settings/setting_spec.rb
+++ b/spec/models/user_settings/setting_spec.rb
@@ -90,7 +90,7 @@ RSpec.describe UserSettings::Setting do
describe '#key' do
context 'when there is no namespace' do
- it 'returnsn a symbol' do
+ it 'returns a symbol' do
expect(subject.key).to eq :foo
end
end
diff --git a/spec/services/activitypub/process_account_service_spec.rb b/spec/services/activitypub/process_account_service_spec.rb
index 4c5e6b6cc3..db454d7ad9 100644
--- a/spec/services/activitypub/process_account_service_spec.rb
+++ b/spec/services/activitypub/process_account_service_spec.rb
@@ -181,7 +181,7 @@ RSpec.describe ActivityPub::ProcessAccountService, type: :service do
'@context': ['https://www.w3.org/ns/activitystreams'],
id: "https://foo.test/users/#{i}/featured",
type: 'OrderedCollection',
- totelItems: 1,
+ totalItems: 1,
orderedItems: [status_json],
}.with_indifferent_access
webfinger = {
diff --git a/spec/services/backup_service_spec.rb b/spec/services/backup_service_spec.rb
index b961b7f675..73e0b42adb 100644
--- a/spec/services/backup_service_spec.rb
+++ b/spec/services/backup_service_spec.rb
@@ -21,6 +21,27 @@ RSpec.describe BackupService, type: :service do
end
end
+ context 'when the user has an avatar and header' do
+ before do
+ user.account.update!(avatar: attachment_fixture('avatar.gif'))
+ user.account.update!(header: attachment_fixture('emojo.png'))
+ end
+
+ it 'stores them as expected' do
+ service_call
+
+ json = Oj.load(read_zip_file(backup, 'actor.json'))
+ avatar_path = json.dig('icon', 'url')
+ header_path = json.dig('image', 'url')
+
+ expect(avatar_path).to_not be_nil
+ expect(header_path).to_not be_nil
+
+ expect(read_zip_file(backup, avatar_path)).to be_present
+ expect(read_zip_file(backup, header_path)).to be_present
+ end
+ end
+
it 'marks the backup as processed' do
expect { service_call }.to change(backup, :processed).from(false).to(true)
end
diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb
index 29207462a0..b8ceedb851 100644
--- a/spec/services/report_service_spec.rb
+++ b/spec/services/report_service_spec.rb
@@ -6,6 +6,14 @@ RSpec.describe ReportService, type: :service do
subject { described_class.new }
let(:source_account) { Fabricate(:account) }
+ let(:target_account) { Fabricate(:account) }
+
+ context 'with a local account' do
+ it 'has a uri' do
+ report = subject.call(source_account, target_account)
+ expect(report.uri).to_not be_nil
+ end
+ end
context 'with a remote account' do
let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
@@ -35,7 +43,6 @@ RSpec.describe ReportService, type: :service do
-> { described_class.new.call(source_account, target_account, status_ids: [status.id]) }
end
- let(:target_account) { Fabricate(:account) }
let(:status) { Fabricate(:status, account: target_account, visibility: :direct) }
context 'when it is addressed to the reporter' do
@@ -91,8 +98,7 @@ RSpec.describe ReportService, type: :service do
-> { described_class.new.call(source_account, target_account) }
end
- let!(:target_account) { Fabricate(:account) }
- let!(:other_report) { Fabricate(:report, target_account: target_account) }
+ let!(:other_report) { Fabricate(:report, target_account: target_account) }
before do
ActionMailer::Base.deliveries.clear
diff --git a/yarn.lock b/yarn.lock
index f22002c139..c77038f6e5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2184,11 +2184,6 @@
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
-"@types/raf@^3.4.0":
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/@types/raf/-/raf-3.4.0.tgz#2b72cbd55405e071f1c4d29992638e022b20acc2"
- integrity sha512-taW5/WYqo36N7V39oYyHP9Ipfd5pNFvGTIQsNGj86xV88YQ7GnI30/yMfKDF7Zgin0m3e+ikX88FvImnK4RjGw==
-
"@types/range-parser@*":
version "1.2.4"
resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
@@ -2800,7 +2795,7 @@ acorn@^6.4.1:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
-acorn@^8.0.4, acorn@^8.1.0, acorn@^8.5.0, acorn@^8.8.0, acorn@^8.8.1, acorn@^8.8.2:
+acorn@^8.0.4, acorn@^8.1.0, acorn@^8.5.0, acorn@^8.8.0, acorn@^8.8.1:
version "8.8.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
@@ -7537,19 +7532,16 @@ jsdom@^20.0.0:
ws "^8.11.0"
xml-name-validator "^4.0.0"
-jsdom@^21.1.2:
- version "21.1.2"
- resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-21.1.2.tgz#6433f751b8718248d646af1cdf6662dc8a1ca7f9"
- integrity sha512-sCpFmK2jv+1sjff4u7fzft+pUh2KSUbUrEHYHyfSIbGTIcmnjyp83qg6qLwdJ/I3LpTXx33ACxeRL7Lsyc6lGQ==
+jsdom@^22.0.0:
+ version "22.0.0"
+ resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-22.0.0.tgz#3295c6992c70089c4b8f5cf060489fddf7ee9816"
+ integrity sha512-p5ZTEb5h+O+iU02t0GfEjAnkdYPrQSkfuTSMkMYyIoMvUNEHsbG0bHHbfXIcfTqD2UfvjQX7mmgiFsyRwGscVw==
dependencies:
abab "^2.0.6"
- acorn "^8.8.2"
- acorn-globals "^7.0.0"
cssstyle "^3.0.0"
data-urls "^4.0.0"
decimal.js "^10.4.3"
domexception "^4.0.0"
- escodegen "^2.0.0"
form-data "^4.0.0"
html-encoding-sniffer "^3.0.0"
http-proxy-agent "^5.0.0"
@@ -9520,7 +9512,7 @@ quick-lru@^4.0.1:
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
-raf@^3.1.0, raf@^3.4.1:
+raf@^3.1.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39"
integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==