Merge tag 'v4.2.10' of https://github.com/mastodon/mastodon into paravielfalt-4.2
All checks were successful
Build Image for Deployment / build (push) Successful in 6m12s
All checks were successful
Build Image for Deployment / build (push) Successful in 6m12s
This commit is contained in:
commit
ec1f61e4b4
33 changed files with 337 additions and 108 deletions
31
CHANGELOG.md
31
CHANGELOG.md
|
@ -2,6 +2,37 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [4.2.10] - 2024-07-04
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fix incorrect permission checking on multiple API endpoints ([GHSA-58x8-3qxw-6hm7](https://github.com/mastodon/mastodon/security/advisories/GHSA-58x8-3qxw-6hm7))
|
||||||
|
- Fix incorrect authorship checking when processing some activities (CVE-2024-37903, [GHSA-xjvf-fm67-4qc3](https://github.com/mastodon/mastodon/security/advisories/GHSA-xjvf-fm67-4qc3))
|
||||||
|
- Fix ongoing streaming sessions not being invalidated when application tokens get revoked ([GHSA-vp5r-5pgw-jwqx](https://github.com/mastodon/mastodon/security/advisories/GHSA-vp5r-5pgw-jwqx))
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add yarn version specification to avoid confusion with Yarn 3 and Yarn 4
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change preview cards generation to skip unusually long URLs ([oneiros](https://github.com/mastodon/mastodon/pull/30854))
|
||||||
|
- Change search modifiers to be case-insensitive ([Gargron](https://github.com/mastodon/mastodon/pull/30865))
|
||||||
|
- Change `STATSD_ADDR` handling to emit a warning rather than crashing if the address is unreachable ([timothyjrogers](https://github.com/mastodon/mastodon/pull/30691))
|
||||||
|
- Change PWA start URL from `/home` to `/` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27377))
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed dependency on `posix-spawn` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18559))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix scheduled statuses scheduled in less than 5 minutes being immediately published ([danielmbrasil](https://github.com/mastodon/mastodon/pull/30584))
|
||||||
|
- Fix encoding detection for link cards ([oneiros](https://github.com/mastodon/mastodon/pull/30780))
|
||||||
|
- Fix `/admin/accounts/:account_id/statuses/:id` for edited posts with media attachments ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30819))
|
||||||
|
- Fix duplicate `@context` attribute in user archive export ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30653))
|
||||||
|
|
||||||
## [4.2.9] - 2024-05-30
|
## [4.2.9] - 2024-05-30
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
1
Gemfile
1
Gemfile
|
@ -65,7 +65,6 @@ gem 'nsa'
|
||||||
gem 'oj', '~> 3.14'
|
gem 'oj', '~> 3.14'
|
||||||
gem 'ox', '~> 2.14'
|
gem 'ox', '~> 2.14'
|
||||||
gem 'parslet'
|
gem 'parslet'
|
||||||
gem 'posix-spawn'
|
|
||||||
gem 'public_suffix', '~> 5.0'
|
gem 'public_suffix', '~> 5.0'
|
||||||
gem 'pundit', '~> 2.3'
|
gem 'pundit', '~> 2.3'
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
|
|
128
Gemfile.lock
128
Gemfile.lock
|
@ -28,47 +28,47 @@ GIT
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.0.8.1)
|
actioncable (7.0.8.4)
|
||||||
actionpack (= 7.0.8.1)
|
actionpack (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (7.0.8.1)
|
actionmailbox (7.0.8.4)
|
||||||
actionpack (= 7.0.8.1)
|
actionpack (= 7.0.8.4)
|
||||||
activejob (= 7.0.8.1)
|
activejob (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8.1)
|
activerecord (= 7.0.8.4)
|
||||||
activestorage (= 7.0.8.1)
|
activestorage (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
actionmailer (7.0.8.1)
|
actionmailer (7.0.8.4)
|
||||||
actionpack (= 7.0.8.1)
|
actionpack (= 7.0.8.4)
|
||||||
actionview (= 7.0.8.1)
|
actionview (= 7.0.8.4)
|
||||||
activejob (= 7.0.8.1)
|
activejob (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (7.0.8.1)
|
actionpack (7.0.8.4)
|
||||||
actionview (= 7.0.8.1)
|
actionview (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
rack (~> 2.0, >= 2.2.4)
|
rack (~> 2.0, >= 2.2.4)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (7.0.8.1)
|
actiontext (7.0.8.4)
|
||||||
actionpack (= 7.0.8.1)
|
actionpack (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8.1)
|
activerecord (= 7.0.8.4)
|
||||||
activestorage (= 7.0.8.1)
|
activestorage (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.0.8.1)
|
actionview (7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
|
@ -78,22 +78,22 @@ GEM
|
||||||
activemodel (>= 4.1, < 7.1)
|
activemodel (>= 4.1, < 7.1)
|
||||||
case_transform (>= 0.2)
|
case_transform (>= 0.2)
|
||||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||||
activejob (7.0.8.1)
|
activejob (7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.0.8.1)
|
activemodel (7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
activerecord (7.0.8.1)
|
activerecord (7.0.8.4)
|
||||||
activemodel (= 7.0.8.1)
|
activemodel (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
activestorage (7.0.8.1)
|
activestorage (7.0.8.4)
|
||||||
actionpack (= 7.0.8.1)
|
actionpack (= 7.0.8.4)
|
||||||
activejob (= 7.0.8.1)
|
activejob (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8.1)
|
activerecord (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
mini_mime (>= 1.1.0)
|
||||||
activesupport (7.0.8.1)
|
activesupport (7.0.8.4)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
|
@ -191,8 +191,8 @@ GEM
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
case_transform (0.2)
|
case_transform (0.2)
|
||||||
activesupport
|
activesupport
|
||||||
cbor (0.5.9.6)
|
cbor (0.5.9.8)
|
||||||
charlock_holmes (0.7.7)
|
charlock_holmes (0.7.8)
|
||||||
chewy (7.3.4)
|
chewy (7.3.4)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
elasticsearch (>= 7.12.0, < 7.14.0)
|
elasticsearch (>= 7.12.0, < 7.14.0)
|
||||||
|
@ -350,7 +350,7 @@ GEM
|
||||||
httplog (1.6.2)
|
httplog (1.6.2)
|
||||||
rack (>= 2.0)
|
rack (>= 2.0)
|
||||||
rainbow (>= 2.0.0)
|
rainbow (>= 2.0.0)
|
||||||
i18n (1.14.1)
|
i18n (1.14.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
i18n-tasks (1.0.12)
|
i18n-tasks (1.0.12)
|
||||||
activesupport (>= 4.0.2)
|
activesupport (>= 4.0.2)
|
||||||
|
@ -432,7 +432,7 @@ GEM
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
marcel (1.0.2)
|
marcel (1.0.4)
|
||||||
mario-redis-lock (1.2.1)
|
mario-redis-lock (1.2.1)
|
||||||
redis (>= 3.0.5)
|
redis (>= 3.0.5)
|
||||||
matrix (0.4.2)
|
matrix (0.4.2)
|
||||||
|
@ -446,7 +446,7 @@ GEM
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2023.0808)
|
mime-types-data (3.2023.0808)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.5)
|
mini_portile2 (2.8.7)
|
||||||
minitest (5.19.0)
|
minitest (5.19.0)
|
||||||
msgpack (1.7.1)
|
msgpack (1.7.1)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
|
@ -468,8 +468,8 @@ GEM
|
||||||
net-smtp (0.3.4)
|
net-smtp (0.3.4)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-ssh (7.1.0)
|
net-ssh (7.1.0)
|
||||||
nio4r (2.7.0)
|
nio4r (2.7.3)
|
||||||
nokogiri (1.16.5)
|
nokogiri (1.16.6)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
nsa (0.3.0)
|
nsa (0.3.0)
|
||||||
|
@ -517,7 +517,6 @@ GEM
|
||||||
pg (1.5.5)
|
pg (1.5.5)
|
||||||
pghero (3.3.4)
|
pghero (3.3.4)
|
||||||
activerecord (>= 6)
|
activerecord (>= 6)
|
||||||
posix-spawn (0.3.15)
|
|
||||||
premailer (1.21.0)
|
premailer (1.21.0)
|
||||||
addressable
|
addressable
|
||||||
css_parser (>= 1.12.0)
|
css_parser (>= 1.12.0)
|
||||||
|
@ -534,7 +533,7 @@ GEM
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.7.3)
|
racc (1.7.3)
|
||||||
rack (2.2.8.1)
|
rack (2.2.9)
|
||||||
rack-attack (6.7.0)
|
rack-attack (6.7.0)
|
||||||
rack (>= 1.0, < 4)
|
rack (>= 1.0, < 4)
|
||||||
rack-cors (2.0.2)
|
rack-cors (2.0.2)
|
||||||
|
@ -551,20 +550,20 @@ GEM
|
||||||
rack
|
rack
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rails (7.0.8.1)
|
rails (7.0.8.4)
|
||||||
actioncable (= 7.0.8.1)
|
actioncable (= 7.0.8.4)
|
||||||
actionmailbox (= 7.0.8.1)
|
actionmailbox (= 7.0.8.4)
|
||||||
actionmailer (= 7.0.8.1)
|
actionmailer (= 7.0.8.4)
|
||||||
actionpack (= 7.0.8.1)
|
actionpack (= 7.0.8.4)
|
||||||
actiontext (= 7.0.8.1)
|
actiontext (= 7.0.8.4)
|
||||||
actionview (= 7.0.8.1)
|
actionview (= 7.0.8.4)
|
||||||
activejob (= 7.0.8.1)
|
activejob (= 7.0.8.4)
|
||||||
activemodel (= 7.0.8.1)
|
activemodel (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8.1)
|
activerecord (= 7.0.8.4)
|
||||||
activestorage (= 7.0.8.1)
|
activestorage (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.0.8.1)
|
railties (= 7.0.8.4)
|
||||||
rails-controller-testing (1.0.5)
|
rails-controller-testing (1.0.5)
|
||||||
actionpack (>= 5.0.1.rc1)
|
actionpack (>= 5.0.1.rc1)
|
||||||
actionview (>= 5.0.1.rc1)
|
actionview (>= 5.0.1.rc1)
|
||||||
|
@ -579,9 +578,9 @@ GEM
|
||||||
rails-i18n (7.0.7)
|
rails-i18n (7.0.7)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 8)
|
railties (>= 6.0.0, < 8)
|
||||||
railties (7.0.8.1)
|
railties (7.0.8.4)
|
||||||
actionpack (= 7.0.8.1)
|
actionpack (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8.1)
|
activesupport (= 7.0.8.4)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
|
@ -744,7 +743,7 @@ GEM
|
||||||
terrapin (0.6.0)
|
terrapin (0.6.0)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
test-prof (1.2.3)
|
test-prof (1.2.3)
|
||||||
thor (1.3.0)
|
thor (1.3.1)
|
||||||
tilt (2.2.0)
|
tilt (2.2.0)
|
||||||
timeout (0.4.1)
|
timeout (0.4.1)
|
||||||
tpm-key_attestation (0.12.0)
|
tpm-key_attestation (0.12.0)
|
||||||
|
@ -810,7 +809,7 @@ GEM
|
||||||
xorcist (1.1.3)
|
xorcist (1.1.3)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.6.13)
|
zeitwerk (2.6.16)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
@ -892,7 +891,6 @@ DEPENDENCIES
|
||||||
parslet
|
parslet
|
||||||
pg (~> 1.5)
|
pg (~> 1.5)
|
||||||
pghero
|
pghero
|
||||||
posix-spawn
|
|
||||||
premailer-rails
|
premailer-rails
|
||||||
private_address_check (~> 0.5)
|
private_address_check (~> 0.5)
|
||||||
public_suffix (~> 5.0)
|
public_suffix (~> 5.0)
|
||||||
|
|
|
@ -6,6 +6,7 @@ class Api::V1::ScheduledStatusesController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, except: [:update, :destroy]
|
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, except: [:update, :destroy]
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:update, :destroy]
|
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:update, :destroy]
|
||||||
|
|
||||||
|
before_action :require_user!
|
||||||
before_action :set_statuses, only: :index
|
before_action :set_statuses, only: :index
|
||||||
before_action :set_status, except: :index
|
before_action :set_status, except: :index
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ class Api::V1::Statuses::TranslationsController < Api::BaseController
|
||||||
include Authorization
|
include Authorization
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
|
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
|
||||||
|
before_action :require_user!
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
before_action :set_translation
|
before_action :set_translation
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Timelines::PublicController < Api::BaseController
|
class Api::V1::Timelines::PublicController < Api::BaseController
|
||||||
|
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
|
||||||
before_action :require_user!, only: [:show], if: :require_auth?
|
before_action :require_user!, only: [:show], if: :require_auth?
|
||||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Timelines::TagController < Api::BaseController
|
class Api::V1::Timelines::TagController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth?
|
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
|
||||||
|
before_action :require_user!, if: :require_auth?
|
||||||
before_action :load_tag
|
before_action :load_tag
|
||||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
Web::PushSubscription.unsubscribe_for(params[:id], current_resource_owner)
|
Web::PushSubscription.unsubscribe_for(params[:id], current_resource_owner)
|
||||||
|
Doorkeeper::Application.find_by(id: params[:id])&.close_streaming_sessions(current_resource_owner)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
def find_existing_status
|
def find_existing_status
|
||||||
status = status_from_uri(object_uri)
|
status = status_from_uri(object_uri)
|
||||||
status ||= Status.find_by(uri: @object['atomUri']) if @object['atomUri'].present?
|
status ||= Status.find_by(uri: @object['atomUri']) if @object['atomUri'].present?
|
||||||
status
|
status if status&.account_id == @account.id
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_status_params
|
def process_status_params
|
||||||
|
|
|
@ -16,17 +16,19 @@ module ApplicationExtension
|
||||||
# dependent: delete_all, which means the ActiveRecord callback in
|
# dependent: delete_all, which means the ActiveRecord callback in
|
||||||
# AccessTokenExtension is not run, so instead we manually announce to
|
# AccessTokenExtension is not run, so instead we manually announce to
|
||||||
# streaming that these tokens are being deleted.
|
# streaming that these tokens are being deleted.
|
||||||
before_destroy :push_to_streaming_api, prepend: true
|
before_destroy :close_streaming_sessions, prepend: true
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirmation_redirect_uri
|
def confirmation_redirect_uri
|
||||||
redirect_uri.lines.first.strip
|
redirect_uri.lines.first.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
def push_to_streaming_api
|
def close_streaming_sessions(resource_owner = nil)
|
||||||
# TODO: #28793 Combine into a single topic
|
# TODO: #28793 Combine into a single topic
|
||||||
payload = Oj.dump(event: :kill)
|
payload = Oj.dump(event: :kill)
|
||||||
access_tokens.in_batches do |tokens|
|
scope = access_tokens
|
||||||
|
scope = scope.where(resource_owner_id: resource_owner.id) unless resource_owner.nil?
|
||||||
|
scope.in_batches do |tokens|
|
||||||
redis.pipelined do |pipeline|
|
redis.pipelined do |pipeline|
|
||||||
tokens.ids.each do |id|
|
tokens.ids.each do |id|
|
||||||
pipeline.publish("timeline:access_token:#{id}", payload)
|
pipeline.publish("timeline:access_token:#{id}", payload)
|
||||||
|
|
|
@ -265,15 +265,20 @@ class LinkDetailsExtractor
|
||||||
end
|
end
|
||||||
|
|
||||||
def document
|
def document
|
||||||
@document ||= Nokogiri::HTML(@html, nil, encoding)
|
@document ||= detect_encoding_and_parse_document
|
||||||
end
|
end
|
||||||
|
|
||||||
def encoding
|
def detect_encoding_and_parse_document
|
||||||
@encoding ||= begin
|
[detect_encoding, nil, @html_charset, 'UTF-8'].uniq.each do |encoding|
|
||||||
|
document = Nokogiri::HTML(@html, nil, encoding)
|
||||||
|
return document if document.to_s.valid_encoding?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def detect_encoding
|
||||||
guess = detector.detect(@html, @html_charset)
|
guess = detector.detect(@html, @html_charset)
|
||||||
guess&.fetch(:confidence, 0).to_i > 60 ? guess&.fetch(:encoding, nil) : nil
|
guess&.fetch(:confidence, 0).to_i > 60 ? guess&.fetch(:encoding, nil) : nil
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def detector
|
def detector
|
||||||
@detector ||= CharlockHolmes::EncodingDetector.new.tap do |detector|
|
@detector ||= CharlockHolmes::EncodingDetector.new.tap do |detector|
|
||||||
|
|
|
@ -223,7 +223,7 @@ class SearchQueryTransformer < Parslet::Transform
|
||||||
end
|
end
|
||||||
|
|
||||||
rule(clause: subtree(:clause)) do
|
rule(clause: subtree(:clause)) do
|
||||||
prefix = clause[:prefix][:term].to_s if clause[:prefix]
|
prefix = clause[:prefix][:term].to_s.downcase if clause[:prefix]
|
||||||
operator = clause[:operator]&.to_s
|
operator = clause[:operator]&.to_s
|
||||||
term = clause[:phrase] ? clause[:phrase].map { |term| term[:term].to_s }.join(' ') : clause[:term].to_s
|
term = clause[:phrase] ? clause[:phrase].map { |term| term[:term].to_s }.join(' ') : clause[:term].to_s
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ class StatusEdit < ApplicationRecord
|
||||||
default_scope { order(id: :asc) }
|
default_scope { order(id: :asc) }
|
||||||
|
|
||||||
delegate :local?, :application, :edited?, :edited_at,
|
delegate :local?, :application, :edited?, :edited_at,
|
||||||
:discarded?, :visibility, to: :status
|
:discarded?, :visibility, :language, to: :status
|
||||||
|
|
||||||
def emojis
|
def emojis
|
||||||
return @emojis if defined?(@emojis)
|
return @emojis if defined?(@emojis)
|
||||||
|
|
|
@ -16,11 +16,18 @@ class ManifestSerializer < ActiveModel::Serializer
|
||||||
512
|
512
|
||||||
).freeze
|
).freeze
|
||||||
|
|
||||||
attributes :name, :short_name,
|
attributes :id, :name, :short_name,
|
||||||
:icons, :theme_color, :background_color,
|
:icons, :theme_color, :background_color,
|
||||||
:display, :start_url, :scope,
|
:display, :start_url, :scope,
|
||||||
:share_target, :shortcuts
|
:share_target, :shortcuts
|
||||||
|
|
||||||
|
def id
|
||||||
|
# This is set to `/home` because that was the old value of `start_url` and
|
||||||
|
# thus the fallback ID computed by Chrome:
|
||||||
|
# https://developer.chrome.com/blog/pwa-manifest-id/
|
||||||
|
'/home'
|
||||||
|
end
|
||||||
|
|
||||||
def name
|
def name
|
||||||
object.title
|
object.title
|
||||||
end
|
end
|
||||||
|
@ -53,7 +60,7 @@ class ManifestSerializer < ActiveModel::Serializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def start_url
|
def start_url
|
||||||
'/home'
|
'/'
|
||||||
end
|
end
|
||||||
|
|
||||||
def scope
|
def scope
|
||||||
|
|
|
@ -19,8 +19,8 @@ class BackupService < BaseService
|
||||||
|
|
||||||
def build_outbox_json!(file)
|
def build_outbox_json!(file)
|
||||||
skeleton = serialize(collection_presenter, ActivityPub::CollectionSerializer)
|
skeleton = serialize(collection_presenter, ActivityPub::CollectionSerializer)
|
||||||
skeleton[:@context] = full_context
|
skeleton['@context'] = full_context
|
||||||
skeleton[:orderedItems] = ['!PLACEHOLDER!']
|
skeleton['orderedItems'] = ['!PLACEHOLDER!']
|
||||||
skeleton = Oj.dump(skeleton)
|
skeleton = Oj.dump(skeleton)
|
||||||
prepend, append = skeleton.split('"!PLACEHOLDER!"')
|
prepend, append = skeleton.split('"!PLACEHOLDER!"')
|
||||||
add_comma = false
|
add_comma = false
|
||||||
|
|
|
@ -15,6 +15,9 @@ class FetchLinkCardService < BaseService
|
||||||
)
|
)
|
||||||
}iox
|
}iox
|
||||||
|
|
||||||
|
# URL size limit to safely store in PosgreSQL's unique indexes
|
||||||
|
BYTESIZE_LIMIT = 2692
|
||||||
|
|
||||||
def call(status)
|
def call(status)
|
||||||
@status = status
|
@status = status
|
||||||
@original_url = parse_urls
|
@original_url = parse_urls
|
||||||
|
@ -85,7 +88,7 @@ class FetchLinkCardService < BaseService
|
||||||
|
|
||||||
def bad_url?(uri)
|
def bad_url?(uri)
|
||||||
# Avoid local instance URLs and invalid URLs
|
# Avoid local instance URLs and invalid URLs
|
||||||
uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme)
|
uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme) || uri.to_s.bytesize > BYTESIZE_LIMIT
|
||||||
end
|
end
|
||||||
|
|
||||||
def mention_link?(anchor)
|
def mention_link?(anchor)
|
||||||
|
|
|
@ -171,7 +171,7 @@ class PostStatusService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def scheduled_in_the_past?
|
def scheduled_in_the_past?
|
||||||
@scheduled_at.present? && @scheduled_at <= Time.now.utc + MIN_SCHEDULE_OFFSET
|
@scheduled_at.present? && @scheduled_at <= Time.now.utc
|
||||||
end
|
end
|
||||||
|
|
||||||
def bump_potential_friendship!
|
def bump_potential_friendship!
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
if ENV['STATSD_ADDR'].present?
|
if ENV['STATSD_ADDR'].present?
|
||||||
host, port = ENV['STATSD_ADDR'].split(':')
|
host, port = ENV['STATSD_ADDR'].split(':')
|
||||||
|
|
||||||
|
begin
|
||||||
statsd = Statsd.new(host, port)
|
statsd = Statsd.new(host, port)
|
||||||
statsd.namespace = ENV.fetch('STATSD_NAMESPACE') { ['Mastodon', Rails.env].join('.') }
|
statsd.namespace = ENV.fetch('STATSD_NAMESPACE') { ['Mastodon', Rails.env].join('.') }
|
||||||
|
|
||||||
|
@ -12,4 +13,7 @@ if ENV['STATSD_ADDR'].present?
|
||||||
informant.collect(:active_support_cache, :cache)
|
informant.collect(:active_support_cache, :cache)
|
||||||
informant.collect(:sidekiq, :sidekiq) if ENV['STATSD_SIDEKIQ'] == 'true'
|
informant.collect(:sidekiq, :sidekiq) if ENV['STATSD_SIDEKIQ'] == 'true'
|
||||||
end
|
end
|
||||||
|
rescue
|
||||||
|
Rails.logger.warn("statsd address #{ENV['STATSD_ADDR']} not reachable, proceeding without statsd")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,7 +56,7 @@ services:
|
||||||
|
|
||||||
web:
|
web:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.2.9
|
image: ghcr.io/mastodon/mastodon:v4.2.10
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
|
command: bash -c "rm -f /mastodon/tmp/pids/server.pid; bundle exec rails s -p 3000"
|
||||||
|
@ -77,7 +77,7 @@ services:
|
||||||
|
|
||||||
streaming:
|
streaming:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.2.9
|
image: ghcr.io/mastodon/mastodon:v4.2.10
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: node ./streaming
|
command: node ./streaming
|
||||||
|
@ -95,7 +95,7 @@ services:
|
||||||
|
|
||||||
sidekiq:
|
sidekiq:
|
||||||
build: .
|
build: .
|
||||||
image: ghcr.io/mastodon/mastodon:v4.2.9
|
image: ghcr.io/mastodon/mastodon:v4.2.10
|
||||||
restart: always
|
restart: always
|
||||||
env_file: .env.production
|
env_file: .env.production
|
||||||
command: bundle exec sidekiq
|
command: bundle exec sidekiq
|
||||||
|
|
|
@ -13,7 +13,7 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
def patch
|
def patch
|
||||||
9
|
10
|
||||||
end
|
end
|
||||||
|
|
||||||
def default_prerelease
|
def default_prerelease
|
||||||
|
|
|
@ -226,5 +226,6 @@
|
||||||
"Capfile|Gemfile|*.{rb,ruby,ru,rake}": "bundle exec rubocop --force-exclusion -a",
|
"Capfile|Gemfile|*.{rb,ruby,ru,rake}": "bundle exec rubocop --force-exclusion -a",
|
||||||
"*.{js,jsx,ts,tsx}": "eslint --fix",
|
"*.{js,jsx,ts,tsx}": "eslint --fix",
|
||||||
"*.{css,scss}": "stylelint --fix"
|
"*.{css,scss}": "stylelint --fix"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@1.22.22"
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,11 @@ describe Admin::StatusesController do
|
||||||
|
|
||||||
describe 'GET #show' do
|
describe 'GET #show' do
|
||||||
before do
|
before do
|
||||||
|
status.media_attachments << Fabricate(:media_attachment, type: :image, account: status.account)
|
||||||
|
status.save!
|
||||||
|
status.snapshot!(at_time: status.created_at, rate_limit: false)
|
||||||
|
status.update!(text: 'Hello, this is an edited post')
|
||||||
|
status.snapshot!(rate_limit: false)
|
||||||
get :show, params: { account_id: account.id, id: status.id }
|
get :show, params: { account_id: account.id, id: status.id }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,17 @@ describe Api::V1::ScheduledStatusesController do
|
||||||
allow(controller).to receive(:doorkeeper_token) { token }
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with an application token' do
|
||||||
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read:statuses') }
|
||||||
|
|
||||||
|
it 'returns http unprocessable entity' do
|
||||||
|
get :index
|
||||||
|
|
||||||
|
expect(response)
|
||||||
|
.to have_http_status(422)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'GET #index' do
|
describe 'GET #index' do
|
||||||
it 'returns http success' do
|
it 'returns http success' do
|
||||||
get :index
|
get :index
|
||||||
|
|
|
@ -9,6 +9,26 @@ describe Api::V1::Statuses::TranslationsController do
|
||||||
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
|
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
|
||||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses', application: app) }
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses', application: app) }
|
||||||
|
|
||||||
|
context 'with an application token' do
|
||||||
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read:statuses', application: app) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/statuses/:status_id/translate' do
|
||||||
|
let(:status) { Fabricate(:status, account: user.account, text: 'Hola', language: 'es') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
post :create, params: { status_id: status.id }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http unprocessable entity' do
|
||||||
|
expect(response).to have_http_status(422)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with an oauth token' do
|
context 'with an oauth token' do
|
||||||
before do
|
before do
|
||||||
allow(controller).to receive(:doorkeeper_token) { token }
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
|
|
|
@ -182,6 +182,46 @@ RSpec.describe Api::V1::StatusesController do
|
||||||
expect(response.headers['X-RateLimit-Remaining']).to eq '0'
|
expect(response.headers['X-RateLimit-Remaining']).to eq '0'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with missing thread' do
|
||||||
|
subject { post :create, params: params }
|
||||||
|
|
||||||
|
let(:params) { { status: 'Hello world', in_reply_to_id: 0 } }
|
||||||
|
|
||||||
|
it 'returns http not found' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_http_status(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when scheduling a status' do
|
||||||
|
subject { post :create, params: params }
|
||||||
|
|
||||||
|
let(:params) { { status: 'Hello world', scheduled_at: 10.minutes.from_now } }
|
||||||
|
let(:account) { user.account }
|
||||||
|
|
||||||
|
it 'returns HTTP 200' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a scheduled status' do
|
||||||
|
expect { subject }.to change { account.scheduled_statuses.count }.from(0).to(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the scheduling time is less than 5 minutes' do
|
||||||
|
let(:params) { { status: 'Hello world', scheduled_at: 4.minutes.from_now } }
|
||||||
|
|
||||||
|
it 'does not create a scheduled status', :aggregate_failures do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_http_status(422)
|
||||||
|
expect(account.scheduled_statuses).to be_empty
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'DELETE #destroy' do
|
describe 'DELETE #destroy' do
|
||||||
|
|
|
@ -6,7 +6,8 @@ describe Api::V1::Timelines::TagController do
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses') }
|
let(:scopes) { 'read:statuses' }
|
||||||
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(controller).to receive(:doorkeeper_token) { token }
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
|
@ -48,13 +49,23 @@ describe Api::V1::Timelines::TagController do
|
||||||
Form::AdminSettings.new(timeline_preview: false).save
|
Form::AdminSettings.new(timeline_preview: false).save
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the user is not authenticated' do
|
context 'without an access token' do
|
||||||
let(:token) { nil }
|
let(:token) { nil }
|
||||||
|
|
||||||
it 'returns http unauthorized' do
|
it 'returns http unprocessable entity' do
|
||||||
subject
|
subject
|
||||||
|
|
||||||
expect(response).to have_http_status(401)
|
expect(response).to have_http_status(422)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an application access token, not bound to a user' do
|
||||||
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: scopes) }
|
||||||
|
|
||||||
|
it 'returns http unprocessable entity' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_http_status(422)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -50,9 +50,11 @@ describe Oauth::AuthorizedApplicationsController do
|
||||||
let!(:application) { Fabricate(:application) }
|
let!(:application) { Fabricate(:application) }
|
||||||
let!(:access_token) { Fabricate(:accessible_access_token, application: application, resource_owner_id: user.id) }
|
let!(:access_token) { Fabricate(:accessible_access_token, application: application, resource_owner_id: user.id) }
|
||||||
let!(:web_push_subscription) { Fabricate(:web_push_subscription, user: user, access_token: access_token) }
|
let!(:web_push_subscription) { Fabricate(:web_push_subscription, user: user, access_token: access_token) }
|
||||||
|
let(:redis_pipeline_stub) { instance_double(Redis::Namespace, publish: nil) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
sign_in user, scope: :user
|
sign_in user, scope: :user
|
||||||
|
allow(redis).to receive(:pipelined).and_yield(redis_pipeline_stub)
|
||||||
post :destroy, params: { id: application.id }
|
post :destroy, params: { id: application.id }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -63,5 +65,13 @@ describe Oauth::AuthorizedApplicationsController do
|
||||||
it 'removes subscriptions for the application\'s access tokens' do
|
it 'removes subscriptions for the application\'s access tokens' do
|
||||||
expect(Web::PushSubscription.where(user: user).count).to eq 0
|
expect(Web::PushSubscription.where(user: user).count).to eq 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'removes the web_push_subscription' do
|
||||||
|
expect { web_push_subscription.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sends a session kill payload to the streaming server' do
|
||||||
|
expect(redis_pipeline_stub).to have_received(:publish).with("timeline:access_token:#{access_token.id}", '{"event":"kill"}')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -166,7 +166,11 @@ describe Settings::ApplicationsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'destroy' do
|
describe 'destroy' do
|
||||||
|
let(:redis_pipeline_stub) { instance_double(Redis::Namespace, publish: nil) }
|
||||||
|
let!(:access_token) { Fabricate(:accessible_access_token, application: app) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
allow(redis).to receive(:pipelined).and_yield(redis_pipeline_stub)
|
||||||
post :destroy, params: { id: app.id }
|
post :destroy, params: { id: app.id }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -177,6 +181,10 @@ describe Settings::ApplicationsController do
|
||||||
it 'removes the app' do
|
it 'removes the app' do
|
||||||
expect(Doorkeeper::Application.find_by(id: app.id)).to be_nil
|
expect(Doorkeeper::Application.find_by(id: app.id)).to be_nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'sends a session kill payload to the streaming server' do
|
||||||
|
expect(redis_pipeline_stub).to have_received(:publish).with("timeline:access_token:#{access_token.id}", '{"event":"kill"}')
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'regenerate' do
|
describe 'regenerate' do
|
||||||
|
|
17
spec/fixtures/requests/low_confidence_latin1.txt
vendored
Normal file
17
spec/fixtures/requests/low_confidence_latin1.txt
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
server: nginx
|
||||||
|
date: Thu, 13 Jun 2024 14:33:13 GMT
|
||||||
|
content-type: text/html; charset=ISO-8859-1
|
||||||
|
content-length: 158
|
||||||
|
accept-ranges: bytes
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Tofu á l'orange</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Tofu á l'orange</h2>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -32,6 +32,8 @@ describe 'Public' do
|
||||||
context 'when the instance allows public preview' do
|
context 'when the instance allows public preview' do
|
||||||
let(:expected_statuses) { [local_status, remote_status, media_status] }
|
let(:expected_statuses) { [local_status, remote_status, media_status] }
|
||||||
|
|
||||||
|
it_behaves_like 'forbidden for wrong scope', 'profile'
|
||||||
|
|
||||||
context 'with an authorized user' do
|
context 'with an authorized user' do
|
||||||
it_behaves_like 'a successful request to the public timeline'
|
it_behaves_like 'a successful request to the public timeline'
|
||||||
end
|
end
|
||||||
|
@ -96,13 +98,9 @@ describe 'Public' do
|
||||||
Form::AdminSettings.new(timeline_preview: false).save
|
Form::AdminSettings.new(timeline_preview: false).save
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with an authenticated user' do
|
it_behaves_like 'forbidden for wrong scope', 'profile'
|
||||||
let(:expected_statuses) { [local_status, remote_status, media_status] }
|
|
||||||
|
|
||||||
it_behaves_like 'a successful request to the public timeline'
|
context 'without an authentication token' do
|
||||||
end
|
|
||||||
|
|
||||||
context 'with an unauthenticated user' do
|
|
||||||
let(:headers) { {} }
|
let(:headers) { {} }
|
||||||
|
|
||||||
it 'returns http unprocessable entity' do
|
it 'returns http unprocessable entity' do
|
||||||
|
@ -111,6 +109,22 @@ describe 'Public' do
|
||||||
expect(response).to have_http_status(422)
|
expect(response).to have_http_status(422)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with an application access token, not bound to a user' do
|
||||||
|
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: scopes) }
|
||||||
|
|
||||||
|
it 'returns http unprocessable entity' do
|
||||||
|
subject
|
||||||
|
|
||||||
|
expect(response).to have_http_status(422)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with an authenticated user' do
|
||||||
|
let(:expected_statuses) { [local_status, remote_status, media_status] }
|
||||||
|
|
||||||
|
it_behaves_like 'a successful request to the public timeline'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -55,9 +55,11 @@ RSpec.describe BackupService, type: :service do
|
||||||
end
|
end
|
||||||
|
|
||||||
def expect_outbox_export
|
def expect_outbox_export
|
||||||
json = export_json(:outbox)
|
body = export_json_raw(:outbox)
|
||||||
|
json = Oj.load(body)
|
||||||
|
|
||||||
aggregate_failures do
|
aggregate_failures do
|
||||||
|
expect(body.scan('@context').count).to eq 1
|
||||||
expect(json['@context']).to_not be_nil
|
expect(json['@context']).to_not be_nil
|
||||||
expect(json['type']).to eq 'OrderedCollection'
|
expect(json['type']).to eq 'OrderedCollection'
|
||||||
expect(json['totalItems']).to eq 2
|
expect(json['totalItems']).to eq 2
|
||||||
|
@ -85,8 +87,12 @@ RSpec.describe BackupService, type: :service do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def export_json_raw(type)
|
||||||
|
read_zip_file(backup, "#{type}.json")
|
||||||
|
end
|
||||||
|
|
||||||
def export_json(type)
|
def export_json(type)
|
||||||
Oj.load(read_zip_file(backup, "#{type}.json"))
|
Oj.load(export_json_raw(type))
|
||||||
end
|
end
|
||||||
|
|
||||||
def include_create_item(status)
|
def include_create_item(status)
|
||||||
|
|
|
@ -26,6 +26,7 @@ RSpec.describe FetchLinkCardService, type: :service do
|
||||||
stub_request(:get, 'http://example.com/sjis_with_wrong_charset').to_return(request_fixture('sjis_with_wrong_charset.txt'))
|
stub_request(:get, 'http://example.com/sjis_with_wrong_charset').to_return(request_fixture('sjis_with_wrong_charset.txt'))
|
||||||
stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt'))
|
stub_request(:get, 'http://example.com/koi8-r').to_return(request_fixture('koi8-r.txt'))
|
||||||
stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt'))
|
stub_request(:get, 'http://example.com/windows-1251').to_return(request_fixture('windows-1251.txt'))
|
||||||
|
stub_request(:get, 'http://example.com/low_confidence_latin1').to_return(request_fixture('low_confidence_latin1.txt'))
|
||||||
|
|
||||||
Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache
|
Rails.cache.write('oembed_endpoint:example.com', oembed_cache) if oembed_cache
|
||||||
|
|
||||||
|
@ -148,6 +149,14 @@ RSpec.describe FetchLinkCardService, type: :service do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a URL of a page in ISO-8859-1 encoding, that charlock_holmes cannot detect' do
|
||||||
|
let(:status) { Fabricate(:status, text: 'Check out http://example.com/low_confidence_latin1') }
|
||||||
|
|
||||||
|
it 'decodes the HTML' do
|
||||||
|
expect(status.preview_card.title).to eq("Tofu á l'orange")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with a Japanese path URL' do
|
context 'with a Japanese path URL' do
|
||||||
let(:status) { Fabricate(:status, text: 'テストhttp://example.com/日本語') }
|
let(:status) { Fabricate(:status, text: 'テストhttp://example.com/日本語') }
|
||||||
|
|
||||||
|
@ -184,6 +193,19 @@ RSpec.describe FetchLinkCardService, type: :service do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with an URL too long for PostgreSQL unique indexes' do
|
||||||
|
let(:url) { "http://example.com/#{'a' * 2674}" }
|
||||||
|
let(:status) { Fabricate(:status, text: url) }
|
||||||
|
|
||||||
|
it 'does not fetch the URL' do
|
||||||
|
expect(a_request(:get, url)).to_not have_been_made
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not create a preview card' do
|
||||||
|
expect(status.preview_card).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with a URL of a page with oEmbed support' do
|
context 'with a URL of a page with oEmbed support' do
|
||||||
let(:html) { '<!doctype html><title>Hello world</title><link rel="alternate" type="application/json+oembed" href="http://example.com/oembed?url=http://example.com/html">' }
|
let(:html) { '<!doctype html><title>Hello world</title><link rel="alternate" type="application/json+oembed" href="http://example.com/oembed?url=http://example.com/html">' }
|
||||||
let(:status) { Fabricate(:status, text: 'http://example.com/html') }
|
let(:status) { Fabricate(:status, text: 'http://example.com/html') }
|
||||||
|
|
|
@ -61,6 +61,16 @@ RSpec.describe PostStatusService, type: :service do
|
||||||
status2 = subject.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future)
|
status2 = subject.call(account, text: 'test', idempotency: 'meepmeep', scheduled_at: future)
|
||||||
expect(status2.id).to eq status1.id
|
expect(status2.id).to eq status1.id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when scheduled_at is less than min offset' do
|
||||||
|
let(:invalid_scheduled_time) { 4.minutes.from_now }
|
||||||
|
|
||||||
|
it 'raises invalid record error' do
|
||||||
|
expect do
|
||||||
|
subject.call(account, text: 'Hi future!', scheduled_at: invalid_scheduled_time)
|
||||||
|
end.to raise_error(ActiveRecord::RecordInvalid)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates response to the original status of boost' do
|
it 'creates response to the original status of boost' do
|
||||||
|
|
Loading…
Reference in a new issue