From 7d2cd7195bfc53a3bf2b57359fcfcb570813fe3d Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 11 Oct 2023 15:14:18 +0200 Subject: [PATCH 01/69] Add variable delay before link verification of remote account links (#27351) --- app/services/update_account_service.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/services/update_account_service.rb b/app/services/update_account_service.rb index 4604d71b2..a98f4d31e 100644 --- a/app/services/update_account_service.rb +++ b/app/services/update_account_service.rb @@ -28,7 +28,13 @@ class UpdateAccountService < BaseService end def check_links(account) - VerifyAccountLinksWorker.perform_async(account.id) if account.fields.any?(&:requires_verification?) + return unless account.fields.any?(&:requires_verification?) + + if account.local? + VerifyAccountLinksWorker.perform_async(account.id) + else + VerifyAccountLinksWorker.perform_in(rand(10.minutes.to_i), account.id) + end end def process_hashtags(account) From 66becf9a1e8d44c5579e777a546421a54d15dbb0 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 11 Oct 2023 09:58:33 -0400 Subject: [PATCH 02/69] Fix haml-lint `InstanceVariables` rule for admin/reports/actions (#27339) --- .haml-lint_todo.yml | 7 +++---- app/views/admin/reports/_actions.html.haml | 8 ++++---- app/views/admin/reports/show.html.haml | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 69937c4c4..4a141b2e1 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -1,13 +1,13 @@ # This configuration was generated by # `haml-lint --auto-gen-config` -# on 2023-10-03 08:32:28 -0400 using Haml-Lint version 0.51.0. +# on 2023-10-09 10:24:59 -0400 using Haml-Lint version 0.51.0. # The point is for the user to remove these configuration records # one by one as the lints are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of Haml-Lint, may require this file to be generated again. linters: - # Offense count: 944 + # Offense count: 945 LineLength: enabled: false @@ -26,10 +26,9 @@ linters: - 'app/views/admin/reports/show.html.haml' - 'app/views/disputes/strikes/show.html.haml' - # Offense count: 15 + # Offense count: 11 InstanceVariables: exclude: - - 'app/views/admin/reports/_actions.html.haml' - 'app/views/auth/registrations/_status.html.haml' - 'app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml' - 'app/views/relationships/_account.html.haml' diff --git a/app/views/admin/reports/_actions.html.haml b/app/views/admin/reports/_actions.html.haml index aad441625..da9ac8931 100644 --- a/app/views/admin/reports/_actions.html.haml +++ b/app/views/admin/reports/_actions.html.haml @@ -1,11 +1,11 @@ -= form_tag preview_admin_report_actions_path(@report), method: :post do += form_tag preview_admin_report_actions_path(report), method: :post do .report-actions .report-actions__item .report-actions__item__button - = link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), method: :post, class: 'button' + = link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(report), method: :post, class: 'button' .report-actions__item__description = t('admin.reports.actions.resolve_description_html') - - if @statuses.any? { |status| (status.with_media? || status.with_preview_card?) && !status.discarded? } + - if statuses.any? { |status| (status.with_media? || status.with_preview_card?) && !status.discarded? } .report-actions__item .report-actions__item__button = button_tag t('admin.reports.mark_as_sensitive'), name: :mark_as_sensitive, class: 'button' @@ -28,6 +28,6 @@ = t('admin.reports.actions.suspend_description_html') .report-actions__item .report-actions__item__button - = link_to t('admin.accounts.custom'), new_admin_account_action_path(@report.target_account_id, report_id: @report.id), class: 'button' + = link_to t('admin.accounts.custom'), new_admin_account_action_path(report.target_account_id, report_id: report.id), class: 'button' .report-actions__item__description = t('admin.reports.actions.other_description_html') diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index 2508bc2b5..41ce73cfc 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -179,7 +179,7 @@ %p#actions= t(@report.target_account.local? ? 'admin.reports.actions_description_html' : 'admin.reports.actions_description_remote_html') - = render partial: 'admin/reports/actions' + = render partial: 'admin/reports/actions', locals: { report: @report, statuses: @statuses } - unless @action_logs.empty? %hr.spacer/ From 0111e48893f1f29568ea86ae34ca82fa23d6ae0a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:02:51 +0200 Subject: [PATCH 03/69] Update dependency sidekiq to v6.5.12 (#27358) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index affe1bf7f..8dd61ae5e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -671,7 +671,7 @@ GEM rubyzip (>= 1.2.2, < 3.0) websocket (~> 1.0) semantic_range (3.0.0) - sidekiq (6.5.11) + sidekiq (6.5.12) connection_pool (>= 2.2.5, < 3) rack (~> 2.0) redis (>= 4.5.0, < 5) From f9abe700e73df76963aa0f74a87e518ddc5c4584 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 11 Oct 2023 10:20:26 -0400 Subject: [PATCH 04/69] Fix haml-lint `InstanceVariables` rule for auth/registrations/status (#27341) --- .haml-lint_todo.yml | 5 ++--- app/views/auth/registrations/_status.html.haml | 18 +++++++++--------- app/views/auth/registrations/edit.html.haml | 2 +- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 4a141b2e1..cfa39a24e 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `haml-lint --auto-gen-config` -# on 2023-10-09 10:24:59 -0400 using Haml-Lint version 0.51.0. +# on 2023-10-11 10:00:39 -0400 using Haml-Lint version 0.51.0. # The point is for the user to remove these configuration records # one by one as the lints are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -26,10 +26,9 @@ linters: - 'app/views/admin/reports/show.html.haml' - 'app/views/disputes/strikes/show.html.haml' - # Offense count: 11 + # Offense count: 2 InstanceVariables: exclude: - - 'app/views/auth/registrations/_status.html.haml' - 'app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml' - 'app/views/relationships/_account.html.haml' diff --git a/app/views/auth/registrations/_status.html.haml b/app/views/auth/registrations/_status.html.haml index 759bbc41c..8f44eee01 100644 --- a/app/views/auth/registrations/_status.html.haml +++ b/app/views/auth/registrations/_status.html.haml @@ -1,30 +1,30 @@ -- if !@user.confirmed? +- if !user.confirmed? .flash-message.warning = t('auth.status.confirming') = link_to t('auth.didnt_get_confirmation'), new_user_confirmation_path -- elsif !@user.approved? +- elsif !user.approved? .flash-message.warning = t('auth.status.pending') -- elsif @user.account.moved_to_account_id.present? +- elsif user.account.moved_to_account_id.present? .flash-message.warning - = t('auth.status.redirecting_to', acct: @user.account.moved_to_account.pretty_acct) + = t('auth.status.redirecting_to', acct: user.account.moved_to_account.pretty_acct) = link_to t('migrations.cancel'), settings_migration_path %h3= t('auth.status.account_status') %p.hint - - if @user.account.suspended? + - if user.account.suspended? %span.negative-hint= t('user_mailer.warning.explanation.suspend') - - elsif @user.disabled? + - elsif user.disabled? %span.negative-hint= t('user_mailer.warning.explanation.disable') - - elsif @user.account.silenced? + - elsif user.account.silenced? %span.warning-hint= t('user_mailer.warning.explanation.silence') - else %span.positive-hint= t('auth.status.functional') -= render partial: 'account_warning', collection: @strikes += render partial: 'account_warning', collection: strikes -- if @user.account.strikes.exists? +- if user.account.strikes.exists? %hr.spacer/ %p.muted-hint diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml index 3e9b0cb6b..908119a21 100644 --- a/app/views/auth/registrations/edit.html.haml +++ b/app/views/auth/registrations/edit.html.haml @@ -1,7 +1,7 @@ - content_for :page_title do = t('settings.account_settings') -= render 'status' += render partial: 'status', locals: { user: @user, strikes: @strikes } %h3= t('auth.security') From 1afb7f41049ebcea9d92a3ec8953c5eba708725a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:21:59 +0200 Subject: [PATCH 05/69] Update dependency lograge to v0.14.0 (#27363) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8dd61ae5e..153348464 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -412,12 +412,12 @@ GEM llhttp-ffi (0.4.0) ffi-compiler (~> 1.0) rake (~> 13.0) - lograge (0.13.0) + lograge (0.14.0) actionpack (>= 4) activesupport (>= 4) railties (>= 4) request_store (~> 1.0) - loofah (2.21.3) + loofah (2.21.4) crass (~> 1.0.2) nokogiri (>= 1.12.0) mail (2.8.1) @@ -440,7 +440,7 @@ GEM mime-types-data (3.2023.0808) mini_mime (1.1.5) mini_portile2 (2.8.4) - minitest (5.19.0) + minitest (5.20.0) msgpack (1.7.1) multi_json (1.15.0) multipart-post (2.3.0) @@ -554,7 +554,7 @@ GEM actionpack (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1) activesupport (>= 5.0.1.rc1) - rails-dom-testing (2.1.1) + rails-dom-testing (2.2.0) activesupport (>= 5.0.0) minitest nokogiri (>= 1.6) @@ -789,7 +789,7 @@ GEM xorcist (1.1.3) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.6.11) + zeitwerk (2.6.12) PLATFORMS ruby From 608cd6623935e1846e315d76a6cf08df866fa659 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Hodierne?= Date: Wed, 11 Oct 2023 16:24:28 +0200 Subject: [PATCH 06/69] Update README requirements from node 14 to node 16 (#27369) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d6d34479..ce9b5cfbd 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre - **PostgreSQL** 9.5+ - **Redis** 4+ - **Ruby** 2.7+ -- **Node.js** 14+ +- **Node.js** 16+ The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation. From d0a3331caafe61327350ef4b4eddc27271302509 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:36:50 +0200 Subject: [PATCH 07/69] Update dependency rubocop to v1.57.0 (#27371) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 153348464..fa84bdc5e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -493,7 +493,7 @@ GEM orm_adapter (0.5.0) ox (2.14.17) parallel (1.23.0) - parser (3.2.2.3) + parser (3.2.2.4) ast (~> 2.4.1) racc parslet (2.0.0) @@ -583,7 +583,7 @@ GEM redis (>= 4) redlock (1.3.2) redis (>= 3.0.0, < 6.0) - regexp_parser (2.8.1) + regexp_parser (2.8.2) request_store (1.5.1) rack (>= 1.4) responders (3.1.0) @@ -620,12 +620,12 @@ GEM sidekiq (>= 5, < 8) rspec-support (3.12.1) rspec_chunked (0.6) - rubocop (1.56.4) + rubocop (1.57.0) base64 (~> 0.1.1) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.2.2.3) + parser (>= 3.2.2.4) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml (>= 3.2.5, < 4.0) From f1ae72ba4622e48809a9f5b4692efb122d6b3c85 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:37:01 +0200 Subject: [PATCH 08/69] New Crowdin Translations (automated) (#27364) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/fi.json | 50 +++++++++++----------- app/javascript/mastodon/locales/gd.json | 2 +- app/javascript/mastodon/locales/ja.json | 2 +- app/javascript/mastodon/locales/oc.json | 37 ++++++++++++++++ app/javascript/mastodon/locales/zh-TW.json | 2 +- config/locales/activerecord.fi.yml | 2 +- config/locales/fi.yml | 20 ++++----- config/locales/simple_form.fi.yml | 10 ++--- 8 files changed, 81 insertions(+), 44 deletions(-) diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 87d7c90c4..cef225033 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -1,6 +1,6 @@ { "about.blocks": "Valvotut palvelimet", - "about.contact": "Yhteydenotto:", + "about.contact": "Ota yhteyttä:", "about.disclaimer": "Mastodon on vapaa avoimen lähdekoodin ohjelmisto ja Mastodon gGmbH:n tavaramerkki.", "about.domain_blocks.no_reason_available": "Syytä ei ole ilmoitettu", "about.domain_blocks.preamble": "Mastodonin avulla voidaan yleensä tarkastella minkä tahansa fediversumiin kuuluvan palvelimen sisältöä ja vuorovaikuttaa eri palvelinten käyttäjien kanssa. Nämä ovat tälle palvelimelle määritetyt poikkeukset.", @@ -31,10 +31,10 @@ "account.featured_tags.last_status_never": "Ei julkaisuja", "account.featured_tags.title": "Käyttäjän {name} esille nostetut aihetunnisteet", "account.follow": "Seuraa", - "account.followers": "seuraaja(t)", + "account.followers": "Seuraajat", "account.followers.empty": "Kukaan ei seuraa tätä käyttäjää vielä.", "account.followers_counter": "{count, plural, one {{counter} seuraaja} other {{counter} seuraajaa}}", - "account.following": "Seurataan", + "account.following": "Seuratut", "account.following_counter": "{count, plural, one {{counter} seurattu} other {{counter} seurattua}}", "account.follows.empty": "Tämä käyttäjä ei vielä seuraa ketään.", "account.follows_you": "Seuraa sinua", @@ -194,7 +194,7 @@ "copypaste.copied": "Kopioitu", "copypaste.copy_to_clipboard": "Kopioi leikepöydälle", "directory.federated": "Koko tunnettu fediversumi", - "directory.local": "Vain palvelusta {domain}", + "directory.local": "Vain palvelimelta {domain}", "directory.new_arrivals": "Äskettäin saapuneet", "directory.recently_active": "Hiljattain aktiiviset", "disabled_account_banner.account_settings": "Tilin asetukset", @@ -266,7 +266,7 @@ "filter_modal.select_filter.context_mismatch": "ei sovellu tähän kontekstiin", "filter_modal.select_filter.expired": "vanhentunut", "filter_modal.select_filter.prompt_new": "Uusi luokka: {name}", - "filter_modal.select_filter.search": "Etsi tai luo", + "filter_modal.select_filter.search": "Hae tai luo", "filter_modal.select_filter.subtitle": "Käytä olemassa olevaa luokkaa tai luo uusi", "filter_modal.select_filter.title": "Suodata tämä julkaisu", "filter_modal.title.status": "Suodata julkaisu", @@ -336,9 +336,9 @@ "keyboard_shortcuts.blocked": "Avaa estettyjen käyttäjien luettelo", "keyboard_shortcuts.boost": "Tehosta julkaisua", "keyboard_shortcuts.column": "Kohdista sarakkeeseen", - "keyboard_shortcuts.compose": "siirry tekstinsyöttöön", + "keyboard_shortcuts.compose": "Kohdista kirjoituskenttään", "keyboard_shortcuts.description": "Kuvaus", - "keyboard_shortcuts.direct": "avataksesi yksityisten mainintojen sarakkeen", + "keyboard_shortcuts.direct": "Avaa yksityisten mainintojen sarake", "keyboard_shortcuts.down": "Siirry listassa alaspäin", "keyboard_shortcuts.enter": "Avaa julkaisu", "keyboard_shortcuts.favourite": "Lisää julkaisu suosikkeihin", @@ -347,22 +347,22 @@ "keyboard_shortcuts.heading": "Pikanäppäimet", "keyboard_shortcuts.home": "Avaa kotiaikajana", "keyboard_shortcuts.hotkey": "Pikanäppäin", - "keyboard_shortcuts.legend": "Näytä tämä selite", + "keyboard_shortcuts.legend": "Näytä tämä ohje", "keyboard_shortcuts.local": "Avaa paikallinen aikajana", - "keyboard_shortcuts.mention": "Mainitse julkaisija", - "keyboard_shortcuts.muted": "Avaa lista mykistetyistä käyttäjistä", + "keyboard_shortcuts.mention": "Mainitse kirjoittaja", + "keyboard_shortcuts.muted": "Avaa mykistettyjen käyttäjien luettelo", "keyboard_shortcuts.my_profile": "Avaa profiilisi", - "keyboard_shortcuts.notifications": "Avaa ilmoitukset-valikko", + "keyboard_shortcuts.notifications": "Avaa ilmoitussarake", "keyboard_shortcuts.open_media": "Avaa media", "keyboard_shortcuts.pinned": "Avaa kiinnitettyjen julkaisujen luettelo", "keyboard_shortcuts.profile": "Avaa kirjoittajan profiili", "keyboard_shortcuts.reply": "Vastaa julkaisuun", "keyboard_shortcuts.requests": "Avaa seuraamispyyntöjen luettelo", - "keyboard_shortcuts.search": "siirry hakukenttään", + "keyboard_shortcuts.search": "Kohdista hakukenttään", "keyboard_shortcuts.spoilers": "Näytä/piilota sisältövaroituskenttä", - "keyboard_shortcuts.start": "avaa \"Aloitus\"", - "keyboard_shortcuts.toggle_hidden": "näytä/piilota sisältövaroituksella merkitty teksti", - "keyboard_shortcuts.toggle_sensitivity": "näytä/piilota media", + "keyboard_shortcuts.start": "Avaa Näin pääset alkuun -sarake", + "keyboard_shortcuts.toggle_hidden": "Näytä/piilota sisältövaroituksella merkitty teksti", + "keyboard_shortcuts.toggle_sensitivity": "Näytä/piilota media", "keyboard_shortcuts.toot": "Luo uusi julkaisu", "keyboard_shortcuts.unfocus": "Poistu teksti-/hakukentästä", "keyboard_shortcuts.up": "Siirry listassa ylöspäin", @@ -419,7 +419,7 @@ "navigation_bar.pins": "Kiinnitetyt julkaisut", "navigation_bar.preferences": "Asetukset", "navigation_bar.public_timeline": "Yleinen aikajana", - "navigation_bar.search": "Haku", + "navigation_bar.search": "Hae", "navigation_bar.security": "Turvallisuus", "not_signed_in_indicator.not_signed_in": "Sinun on kirjauduttava sisään käyttääksesi resurssia.", "notification.admin.report": "{name} teki ilmoituksen käytäjästä {target}", @@ -475,11 +475,11 @@ "onboarding.actions.go_to_explore": "Siirry suosituimpien aiheiden syötteeseen", "onboarding.actions.go_to_home": "Siirry kotisyötteeseeni", "onboarding.compose.template": "Tervehdys #Mastodon!", - "onboarding.follows.empty": "Valitettavasti tuloksia ei voida näyttää juuri nyt. Voit kokeilla hakua tai selata tutustumissivua löytääksesi seurattavaa, tai yrittää myöhemmin uudelleen.", + "onboarding.follows.empty": "Valitettavasti tuloksia ei voida näyttää juuri nyt. Voit kokeilla hakua tai selata tutustumissivua löytääksesi seurattavaa tai yrittää myöhemmin uudelleen.", "onboarding.follows.lead": "Kokoat oman kotisyötteesi itse. Mitä enemmän ihmisiä seuraat, sitä aktiivisempi ja kiinnostavampi syöte on. Nämä profiilit voivat olla alkuun hyvä lähtökohta — voit aina lopettaa niiden seuraamisen myöhemmin!", "onboarding.follows.title": "Mukauta kotisyötettäsi", "onboarding.share.lead": "Kerro ihmisille, kuinka he voivat löytää sinut Mastodonista!", - "onboarding.share.message": "Olen {username} #Mastodon'issa! Seuraa minua osoitteessa {url}", + "onboarding.share.message": "Olen {username} #Mastodon⁠issa! Seuraa minua osoitteessa {url}", "onboarding.share.next_steps": "Mahdolliset seuraavat vaiheet:", "onboarding.share.title": "Jaa profiilisi", "onboarding.start.lead": "Uusi Mastodon-tilisi on nyt valmiina käyttöön. Kyseessä on ainutlaatuinen, hajautettu sosiaalisen median alusta, jolla sinä itse – algoritmin sijaan – määrität käyttökokemuksesi. Näin hyödyt Mastodonista eniten:", @@ -584,17 +584,17 @@ "report_notification.open": "Avaa raportti", "search.no_recent_searches": "Ei viimeaikaisia hakuja", "search.placeholder": "Hae", - "search.quick_action.account_search": "Profiilit, jotka vastaavat hakua {x}", - "search.quick_action.go_to_account": "Avaa profiili {x}", + "search.quick_action.account_search": "Profiilit haulla {x}", + "search.quick_action.go_to_account": "Siirry profiiliin {x}", "search.quick_action.go_to_hashtag": "Siirry aihetunnisteeseen {x}", "search.quick_action.open_url": "Avaa URL-osoite Mastodonissa", "search.quick_action.status_search": "Julkaisut haulla {x}", - "search.search_or_paste": "Etsi tai kirjoita URL-osoite", + "search.search_or_paste": "Hae tai kirjoita URL-osoite", "search_popout.full_text_search_disabled_message": "Ei saatavilla palvelimella {domain}.", "search_popout.language_code": "ISO-kielikoodi", "search_popout.options": "Haun asetukset", "search_popout.quick_actions": "Pikatoiminnot", - "search_popout.recent": "Viime haut", + "search_popout.recent": "Viimeaikaiset haut", "search_popout.specific_date": "tietty päivämäärä", "search_popout.user": "käyttäjä", "search_results.accounts": "Profiilit", @@ -637,7 +637,7 @@ "status.history.created": "{name} luotu {date}", "status.history.edited": "{name} muokkasi {date}", "status.load_more": "Lataa lisää", - "status.media.open": "Napsauta avataksesi", + "status.media.open": "Avaa napsauttamalla", "status.media.show": "Napsauta näyttääksesi", "status.media_hidden": "Media piilotettu", "status.mention": "Mainitse @{name}", @@ -654,7 +654,7 @@ "status.reblogs.empty": "Kukaan ei ole vielä tehostanut tätä julkaisua. Kun joku tekee niin, tulee hän tähän näkyviin.", "status.redraft": "Poista ja palauta muokattavaksi", "status.remove_bookmark": "Poista kirjanmerkki", - "status.replied_to": "Vastattu {name}", + "status.replied_to": "Vastaus käyttäjälle {name}", "status.reply": "Vastaa", "status.replyAll": "Vastaa ketjuun", "status.report": "Raportoi @{name}", @@ -715,7 +715,7 @@ "upload_modal.preview_label": "Esikatselu ({ratio})", "upload_progress.label": "Ladataan...", "upload_progress.processing": "Käsitellään…", - "username.taken": "Käyttäjätunnus on jo varattu. Kokeile toista", + "username.taken": "Käyttäjänimi on jo varattu. Kokeile toista", "video.close": "Sulje video", "video.download": "Lataa tiedosto", "video.exit_fullscreen": "Poistu koko näytön tilasta", diff --git a/app/javascript/mastodon/locales/gd.json b/app/javascript/mastodon/locales/gd.json index ea1ce59e8..4f485cfc5 100644 --- a/app/javascript/mastodon/locales/gd.json +++ b/app/javascript/mastodon/locales/gd.json @@ -7,7 +7,7 @@ "about.domain_blocks.silenced.explanation": "San fharsaingeachd, chan fhaic thu pròifilean agus susbaint an fhrithealaiche seo ach ma nì thu lorg no ma tha thu ga leantainn.", "about.domain_blocks.silenced.title": "Cuingichte", "about.domain_blocks.suspended.explanation": "Cha dèid dàta sam bith on fhrithealaiche seo a phròiseasadh, a stòradh no iomlaid agus chan urrainn do na cleachdaichean on fhrithealaiche sin conaltradh no eadar-ghnìomh a ghabhail an-seo.", - "about.domain_blocks.suspended.title": "’Na dhàil", + "about.domain_blocks.suspended.title": "À rèim", "about.not_available": "Cha deach am fiosrachadh seo a sholar air an fhrithealaiche seo.", "about.powered_by": "Lìonra sòisealta sgaoilte le cumhachd {mastodon}", "about.rules": "Riaghailtean an fhrithealaiche", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 0bf0a96a2..0c8e417b9 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -250,7 +250,7 @@ "errors.unexpected_crash.report_issue": "問題を報告", "explore.search_results": "検索結果", "explore.suggested_follows": "ユーザー", - "explore.title": "エクスプローラー", + "explore.title": "探索する", "explore.trending_links": "ニュース", "explore.trending_statuses": "投稿", "explore.trending_tags": "ハッシュタグ", diff --git a/app/javascript/mastodon/locales/oc.json b/app/javascript/mastodon/locales/oc.json index 1a2219455..6b8265450 100644 --- a/app/javascript/mastodon/locales/oc.json +++ b/app/javascript/mastodon/locales/oc.json @@ -65,6 +65,7 @@ "account.unendorse": "Mostrar pas pel perfil", "account.unfollow": "Quitar de sègre", "account.unmute": "Quitar de rescondre @{name}", + "account.unmute_notifications_short": "Restablir las notificacions", "account.unmute_short": "Tornar afichar", "account_note.placeholder": "Clicar per ajustar una nòta", "admin.dashboard.retention.average": "Mejana", @@ -97,6 +98,8 @@ "column.direct": "Mencions privadas", "column.directory": "Percórrer los perfils", "column.domain_blocks": "Domenis resconduts", + "column.favourites": "Favorits", + "column.firehose": "Tuts en dirèct", "column.follow_requests": "Demandas d’abonament", "column.home": "Acuèlh", "column.lists": "Listas", @@ -117,6 +120,7 @@ "community.column_settings.remote_only": "Sonque alonhat", "compose.language.change": "Cambiar de lenga", "compose.language.search": "Recercar de lengas...", + "compose.published.body": "Tut publicat.", "compose.published.open": "Dobrir", "compose.saved.body": "Publicacion enregistrada.", "compose_form.direct_message_warning_learn_more": "Ne saber mai", @@ -170,6 +174,7 @@ "conversation.open": "Veire la conversacion", "conversation.with": "Amb {names}", "copypaste.copied": "Copiat", + "copypaste.copy_to_clipboard": "Copiar al quichapapièr", "directory.federated": "Del fediverse conegut", "directory.local": "Solament de {domain}", "directory.new_arrivals": "Nòus-venguts", @@ -220,6 +225,7 @@ "errors.unexpected_crash.copy_stacktrace": "Copiar las traças al quichapapièrs", "errors.unexpected_crash.report_issue": "Senhalar un problèma", "explore.search_results": "Resultats de recèrca", + "explore.suggested_follows": "Personas", "explore.title": "Explorar", "explore.trending_links": "Novèlas", "explore.trending_statuses": "Publicacions", @@ -234,6 +240,7 @@ "filter_modal.select_filter.search": "Cercar o crear", "filter_modal.select_filter.title": "Filtrar aquesta publicacion", "filter_modal.title.status": "Filtrar una publicacion", + "firehose.local": "Aqueste servidor", "follow_request.authorize": "Acceptar", "follow_request.reject": "Regetar", "follow_requests.unlocked_explanation": "Encara que vòstre compte siasque pas verrolhat, la còla de {domain} pensèt que volriatz benlèu repassar las demandas d’abonament d’aquestes comptes.", @@ -257,12 +264,19 @@ "hashtag.column_settings.tag_mode.any": "Un d’aquestes", "hashtag.column_settings.tag_mode.none": "Cap d’aquestes", "hashtag.column_settings.tag_toggle": "Inclure las etiquetas suplementàrias dins aquesta colomna", + "hashtag.counter_by_accounts": "{count, plural, one {{counter} participant} other {{counter} participants}}", + "hashtag.counter_by_uses": "{count, plural, one {{counter} tut} other {{counter} tuts}}", + "hashtag.counter_by_uses_today": "{count, plural, one {{counter} tut} other {{counter} tuts}} uèi", "hashtag.follow": "Sègre l’etiqueta", "hashtag.unfollow": "Quitar de sègre l’etiqueta", + "hashtags.and_other": "…e {count, plural, one {}other {# de mai}}", + "home.actions.go_to_explore": "Agachatz las tendéncias", + "home.actions.go_to_suggestions": "Trobatz de monde de sègre", "home.column_settings.basic": "Basic", "home.column_settings.show_reblogs": "Mostrar los partatges", "home.column_settings.show_replies": "Mostrar las responsas", "home.hide_announcements": "Rescondre las anóncias", + "home.pending_critical_update.link": "Veire las mesas a jorn", "home.show_announcements": "Mostrar las anóncias", "interaction_modal.on_another_server": "Sus un autre servidor", "interaction_modal.on_this_server": "Sus aqueste servidor", @@ -332,14 +346,17 @@ "mute_modal.hide_notifications": "Rescondre las notificacions d’aquesta persona ?", "mute_modal.indefinite": "Cap de data de fin", "navigation_bar.about": "A prepaus", + "navigation_bar.advanced_interface": "Dobrir l’interfàcia web avançada", "navigation_bar.blocks": "Personas blocadas", "navigation_bar.bookmarks": "Marcadors", "navigation_bar.community_timeline": "Flux public local", "navigation_bar.compose": "Escriure un nòu tut", + "navigation_bar.direct": "Mencions privadas", "navigation_bar.discover": "Trobar", "navigation_bar.domain_blocks": "Domenis resconduts", "navigation_bar.edit_profile": "Modificar lo perfil", "navigation_bar.explore": "Explorar", + "navigation_bar.favourites": "Favorits", "navigation_bar.filters": "Mots ignorats", "navigation_bar.follow_requests": "Demandas d’abonament", "navigation_bar.followed_tags": "Etiquetas seguidas", @@ -369,6 +386,7 @@ "notifications.column_settings.admin.report": "Senhalaments novèls :", "notifications.column_settings.admin.sign_up": "Nòus inscrits :", "notifications.column_settings.alert": "Notificacions localas", + "notifications.column_settings.favourite": "Favorits :", "notifications.column_settings.filter_bar.advanced": "Mostrar totas las categorias", "notifications.column_settings.filter_bar.category": "Barra de recèrca rapida", "notifications.column_settings.filter_bar.show_bar": "Afichar la barra de filtres", @@ -386,6 +404,7 @@ "notifications.column_settings.update": "Modificacions :", "notifications.filter.all": "Totas", "notifications.filter.boosts": "Partages", + "notifications.filter.favourites": "Favorits", "notifications.filter.follows": "Seguiments", "notifications.filter.mentions": "Mencions", "notifications.filter.polls": "Resultats del sondatge", @@ -399,15 +418,21 @@ "notifications_permission_banner.enable": "Activar las notificacions burèu", "notifications_permission_banner.how_to_control": "Per recebre las notificacions de Mastodon quand es pas dobèrt, activatz las notificacions de burèu. Podètz precisar quin tipe de notificacion generarà una notificacion de burèu via lo boton {icon} dessús un còp activadas.", "notifications_permission_banner.title": "Manquetz pas jamai res", + "onboarding.action.back": "Tornar en rèire", + "onboarding.actions.back": "Tornar en rèire", "onboarding.actions.go_to_explore": "See what's trending", "onboarding.actions.go_to_home": "Go to your home feed", + "onboarding.compose.template": "Adiu #Mastodon !", "onboarding.follows.lead": "You curate your own home feed. The more people you follow, the more active and interesting it will be. These profiles may be a good starting point—you can always unfollow them later!", "onboarding.follows.title": "Popular on Mastodon", + "onboarding.share.title": "Partejar vòstre perfil", "onboarding.start.lead": "Your new Mastodon account is ready to go. Here's how you can make the most of it:", "onboarding.start.skip": "Want to skip right ahead?", + "onboarding.start.title": "Tot es prèst !", "onboarding.steps.follow_people.body": "You curate your own feed. Lets fill it with interesting people.", "onboarding.steps.follow_people.title": "Follow {count, plural, one {one person} other {# people}}", "onboarding.steps.publish_status.body": "Say hello to the world.", + "onboarding.steps.publish_status.title": "Escrivètz vòstre primièr tut", "onboarding.steps.setup_profile.body": "Others are more likely to interact with you with a filled out profile.", "onboarding.steps.setup_profile.title": "Customize your profile", "onboarding.steps.share_profile.body": "Let your friends know how to find you on Mastodon!", @@ -415,6 +440,7 @@ "picture_in_picture.restore": "Lo tornar", "poll.closed": "Tampat", "poll.refresh": "Actualizar", + "poll.reveal": "Veire los resultats", "poll.total_people": "{count, plural, one {# persona} other {# personas}}", "poll.total_votes": "{count, plural, one {# vòte} other {# vòtes}}", "poll.vote": "Votar", @@ -482,11 +508,17 @@ "report_notification.open": "Dobrir lo senhalament", "search.placeholder": "Recercar", "search.search_or_paste": "Recercar o picar una URL", + "search_popout.language_code": "Còdi ISO de lenga", + "search_popout.options": "Opcions de recèrca", + "search_popout.quick_actions": "Accions rapidas", + "search_popout.recent": "Recèrcas recentas", + "search_popout.specific_date": "data especifica", "search_popout.user": "utilizaire", "search_results.accounts": "Perfils", "search_results.all": "Tot", "search_results.hashtags": "Etiquetas", "search_results.nothing_found": "Cap de resultat per aquestes tèrmes de recèrca", + "search_results.see_all": "O veire tot", "search_results.statuses": "Tuts", "search_results.title": "Recèrca : {q}", "server_banner.active_users": "utilizaires actius", @@ -506,16 +538,20 @@ "status.copy": "Copiar lo ligam de l’estatut", "status.delete": "Escafar", "status.detailed_status": "Vista detalhada de la convèrsa", + "status.direct_indicator": "Mencion privada", "status.edit": "Modificar", "status.edited": "Modificat {date}", "status.edited_x_times": "Modificat {count, plural, un {{count} còp} other {{count} còps}}", "status.embed": "Embarcar", + "status.favourite": "Apondre als favorits", "status.filter": "Filtrar aquesta publicacion", "status.filtered": "Filtrat", "status.hide": "Amagar la publicacion", "status.history.created": "{name} o creèt lo {date}", "status.history.edited": "{name} o modifiquèt lo {date}", "status.load_more": "Cargar mai", + "status.media.open": "Clicar per dobrir", + "status.media.show": "Clicar per mostar", "status.media_hidden": "Mèdia rescondut", "status.mention": "Mencionar", "status.more": "Mai", @@ -546,6 +582,7 @@ "status.title.with_attachments": "{user} posted {attachmentCount, plural, one {an attachment} other {# attachments}}", "status.translate": "Traduire", "status.translated_from_with": "Traduch del {lang} amb {provider}", + "status.uncached_media_warning": "Apercebut indisponible", "status.unmute_conversation": "Tornar mostrar la conversacion", "status.unpin": "Tirar del perfil", "subscribed_languages.lead": "Sonque las publicacions dins las lengas seleccionadas apreissaràn dins vòstre acuèlh e linha cronologica aprèp aqueste cambiament. Seleccionatz pas res per recebre las publicacions en quina lenga que siá.", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 8b5f7de2f..27fd0ad35 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -409,7 +409,7 @@ "navigation_bar.favourites": "最愛", "navigation_bar.filters": "已靜音的關鍵字", "navigation_bar.follow_requests": "跟隨請求", - "navigation_bar.followed_tags": "已跟隨的主題標籤", + "navigation_bar.followed_tags": "已跟隨主題標籤", "navigation_bar.follows_and_followers": "跟隨中與跟隨者", "navigation_bar.lists": "列表", "navigation_bar.logout": "登出", diff --git a/config/locales/activerecord.fi.yml b/config/locales/activerecord.fi.yml index a06cd399a..93f338b5d 100644 --- a/config/locales/activerecord.fi.yml +++ b/config/locales/activerecord.fi.yml @@ -11,7 +11,7 @@ fi: locale: Alue password: Salasana user/account: - username: Käyttäjätunnus + username: Käyttäjänimi user/invite_request: text: Syy errors: diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 2043d327a..d1f345abd 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -163,7 +163,7 @@ fi: unsilenced_msg: Tilin %{username} rajoituksen kumoaminen onnistui unsubscribe: Lopeta tilaus unsuspended_msg: Tilin %{username} jäädytyksen kumoaminen onnistui - username: Käyttäjätunnus + username: Käyttäjänimi view_domain: Näytä verkkotunnuksen yhteenveto warn: Varoita web: Verkko @@ -833,7 +833,7 @@ fi: database_schema_check: message_html: Tietokannan siirto on vireillä. Suorita ne varmistaaksesi, että sovellus toimii odotetulla tavalla elasticsearch_health_red: - message_html: Elasticsearch-klusteri on vikatilassa (punainen tila); hakuominaisuudet eivät ole käytettävissä + message_html: Elasticsearch-klusteri on vikatilassa (punainen tila), joten hakuominaisuudet eivät ole käytettävissä elasticsearch_health_yellow: message_html: Elasticsearch-klusteri on häiriötilassa (keltainen tila), joten suosittelemme tutkimaan syyn elasticsearch_index_mismatch: @@ -843,13 +843,13 @@ fi: message_html: Elasticsearch-klusterissa on useampi kuin yksi solmu, mutta Mastodonia ei ole määritetty käyttämään niitä. elasticsearch_preset_single_node: action: Katso käyttöohjeet - message_html: Elasticsearch-klusterissa on vain yksi solmu, ES_PRESET tulisi asettaa arvoon single_node_cluster. + message_html: Elasticsearch-klusterissa on vain yksi solmu. ES_PRESET tulisi asettaa arvoon single_node_cluster. elasticsearch_reset_chewy: message_html: Elasticsearch-järjestelmäindeksi on vanhentunut asetusmuutoksen vuoksi. Suorita tootctl search deploy --reset-chewy päivittääksesi sen. elasticsearch_running_check: - message_html: Ei saatu yhteyttä Elasticsearch. Tarkista, että se on käynnissä tai poista kokotekstihaku käytöstä + message_html: Ei saatu yhteyttä Elasticsearchiin. Tarkista, että se on käynnissä tai poista kokotekstihaku käytöstä elasticsearch_version_check: - message_html: 'Yhteensopimaton Elasticsearch versio: %{value}' + message_html: 'Yhteensopimaton Elasticsearch-versio: %{value}' version_comparison: Elasticsearch %{running_version} on käynnissä, kun %{required_version} vaaditaan rules_check: action: Hallitse palvelimen sääntöjä @@ -1089,7 +1089,7 @@ fi: new_confirmation_instructions_sent: Saat uuden vahvistuslinkin sisältävän sähköpostiviestin muutamassa minuutissa! title: Tarkista sähköpostilaatikkosi sign_in: - preamble_html: Kirjaudu %{domain}-tunnuksellasi. Jos tilisi sijaitsee eri palvelimella, et voi kirjautua täällä. + preamble_html: Kirjaudu %{domain}-tunnuksellasi. Jos tilisi sijaitsee eri palvelimella, et voi kirjautua tässä. title: Kirjaudu palvelimelle %{domain} sign_up: manual_review: Palvelimen %{domain} valvojat tarkistavat rekisteröitymiset käsin. Helpottaaksesi rekisteröitymisesi käsittelyä kerro hieman itsestäsi ja miksi haluat luoda käyttäjätilin palvelimelle %{domain}. @@ -1271,7 +1271,7 @@ fi: other: Kaikki %{count} kohdetta tällä sivulla on valittu. all_matching_items_selected_html: one: "%{count} kohde, joka vastaa hakuasi." - other: Kaikki %{count} kohdetta, jotka vastaavat hakuasi. + other: Kaikki %{count} hakuasi vastaavaa kohdetta. cancel: Peruuta changes_saved_msg: Muutosten tallennus onnistui! confirm: Vahvista @@ -1283,7 +1283,7 @@ fi: save_changes: Tallenna muutokset select_all_matching_items: one: Valitse %{count} kohde, joka vastaa hakuasi. - other: Valitse kaikki %{count} kohdetta, jotka vastaavat hakuasi. + other: Valitse kaikki %{count} hakuasi vastaavaa kohdetta. today: tänään validation_errors: one: Kaikki ei ole aivan oikein! Tarkasta alla oleva virhe @@ -1403,7 +1403,7 @@ fi: not_ready: Ei voi liittää tiedostoja, joiden käsittely on kesken. Yritä hetken kuluttua uudelleen! too_many: Tiedostoja voi liittää enintään 4 migrations: - acct: uuden tilin käyttäjätunnus@verkkotunnus + acct: Muuttanut tunnukselle cancel: Peruuta uudelleenohjaus cancel_explanation: Uudelleenohjauksen peruuttaminen aktivoi uudelleen nykyisen tilisi, mutta ei palauta seuraajia, jotka on siirretty kyseiselle tilille. cancelled_msg: Uudelleenohjaus peruttu onnistuneesti. @@ -1526,7 +1526,7 @@ fi: privacy_hint_html: Määritä, kuinka paljon muita avustavia tietoja haluat paljastaa. Käyttäjät löytävät kiinnostavia profiileja ja hienoja sovelluksia, kun he selaavat toisten seuraamia käyttäjiä ja kun he näkevät, millä sovelluksilla nämä julkaisevat. Saatat kuitenkin haluta piilottaa nämä tiedot. reach: Tavoittavuus reach_hint_html: Määritä, haluatko tulla uusien käyttäjien löytämäksi ja seuraamaksi. Haluatko julkaisujesi näkyvän Selaa-sivulla? Haluatko muiden käyttäjien näkevän sinut seuraamissuosituksissaan? Haluatko hyväksyä kaikki uudet seuraajat automaattisesti vai päättää jokaisesta erikseen? - search: Haku + search: Hae search_hint_html: Määritä, kuinka haluat tulla löydetyksi. Haluatko, että ihmiset löytävät sinut julkisten julkaisujesi perusteella? Haluatko, että ihmiset Mastodonin ulkopuolella löytävät profiilisi tehdessään hakuja verkossa? Otathan huomioon, ettei julkisten tietojen täyttä kaikista hakukoneista poisjäämistä voi taata. title: Yksityisyys ja tavoittavuus privacy_policy: diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index 1925ba77f..189f9f8eb 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -43,7 +43,7 @@ fi: bot: Tämä tili suorittaa enimmäkseen automaattisia toimintoja eikä sitä ehkä valvota context: Ainakin yksi konteksti, jossa suodattimen pitäisi olla voimassa current_password: Turvallisuussyistä kirjoita nykyisen tilin salasana - current_username: Vahvista kirjoittamalla nykyisen tilin käyttäjätunnus + current_username: Vahvista kirjoittamalla nykyisen tilin käyttäjänimi digest: Lähetetään vain pitkän poissaolon jälkeen ja vain, jos olet saanut suoria viestejä poissaolosi aikana email: Sinulle lähetetään vahvistussähköposti header: PNG, GIF tai JPG. Enintään %{size}. Skaalataan kokoon %{dimensions} px @@ -148,9 +148,9 @@ fi: show_collections: Näytä seuratut ja seuraajat profiilissa unlocked: Hyväksy uudet seuraajat automaattisesti account_alias: - acct: Vanhan tilin käyttäjätunnus + acct: Vanhan tilin käyttäjänimi account_migration: - acct: Uuden tilin käyttäjätunnus + acct: Uuden tilin käyttäjänimi account_warning_preset: text: Esiasetettu teksti title: Nimi @@ -227,7 +227,7 @@ fi: title: Nimi type: Tuontilaji username: Käyttäjänimi - username_or_email: Käyttäjänimi tai sähköposti + username_or_email: Käyttäjänimi tai sähköpostiosoite whole_word: Koko sana email_domain_block: with_dns_records: Sisällytä toimialueen MX tietueet ja IP-osoite @@ -253,7 +253,7 @@ fi: show_domain_blocks: Näytä domainestot show_domain_blocks_rationale: Näytä miksi verkkotunnukset on estetty site_contact_email: Ota yhteyttä sähköpostilla - site_contact_username: Kontaktin käyttäjänimi + site_contact_username: Yhteyshenkilön käyttäjänimi site_extended_description: Laajennettu kuvaus site_short_description: Palvelimen kuvaus site_terms: Tietosuojakäytäntö From d7a8f6b65823a1e52d4b4fd8a73e81b2e2ff1fa2 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 11 Oct 2023 11:06:26 -0400 Subject: [PATCH 09/69] Fix haml-lint `InstanceVariables` rule for relationships/account (#27342) --- .haml-lint_todo.yml | 7 +++---- app/views/relationships/_account.html.haml | 2 +- app/views/relationships/show.html.haml | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index cfa39a24e..c6bb38eb4 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -1,13 +1,13 @@ # This configuration was generated by # `haml-lint --auto-gen-config` -# on 2023-10-11 10:00:39 -0400 using Haml-Lint version 0.51.0. +# on 2023-10-11 10:49:10 -0400 using Haml-Lint version 0.51.0. # The point is for the user to remove these configuration records # one by one as the lints are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of Haml-Lint, may require this file to be generated again. linters: - # Offense count: 945 + # Offense count: 946 LineLength: enabled: false @@ -26,11 +26,10 @@ linters: - 'app/views/admin/reports/show.html.haml' - 'app/views/disputes/strikes/show.html.haml' - # Offense count: 2 + # Offense count: 1 InstanceVariables: exclude: - 'app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml' - - 'app/views/relationships/_account.html.haml' # Offense count: 2 IdNames: diff --git a/app/views/relationships/_account.html.haml b/app/views/relationships/_account.html.haml index 0fa3cffb5..43a3d64bc 100644 --- a/app/views/relationships/_account.html.haml +++ b/app/views/relationships/_account.html.haml @@ -6,7 +6,7 @@ %tbody %tr %td.accounts-table__interrelationships - = interrelationships_icon(@relationships, account.id) + = interrelationships_icon(relationships, account.id) %td= account_link_to account %td.accounts-table__count.optional = friendly_number_to_human account.statuses_count diff --git a/app/views/relationships/show.html.haml b/app/views/relationships/show.html.haml index f08e9c1df..97ba49eb5 100644 --- a/app/views/relationships/show.html.haml +++ b/app/views/relationships/show.html.haml @@ -53,6 +53,6 @@ - if @accounts.empty? = nothing_here 'nothing-here--under-tabs' - else - = render partial: 'account', collection: @accounts, locals: { f: f } + = render partial: 'account', collection: @accounts, locals: { f: f, relationships: @relationships } = paginate @accounts From 85b36fbbd3f13ea23bee2b94b6b4a0a7aeeeea44 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Oct 2023 09:35:25 +0200 Subject: [PATCH 10/69] Update dependency devise to v4.9.3 (#27373) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index fa84bdc5e..dfa184624 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -146,7 +146,7 @@ GEM net-http-persistent (~> 4.0) nokogiri (~> 1, >= 1.10.8) base64 (0.1.1) - bcrypt (3.1.18) + bcrypt (3.1.19) better_errors (2.10.1) erubi (>= 1.0.0) rack (>= 0.9.0) @@ -210,7 +210,7 @@ GEM database_cleaner-core (2.0.1) date (3.3.3) debug_inspector (1.1.0) - devise (4.9.2) + devise (4.9.3) bcrypt (~> 3.0) orm_adapter (~> 0.1) railties (>= 4.1.0) @@ -586,7 +586,7 @@ GEM regexp_parser (2.8.2) request_store (1.5.1) rack (>= 1.4) - responders (3.1.0) + responders (3.1.1) actionpack (>= 5.2) railties (>= 5.2) rexml (3.2.6) From d4c2dca8740236d99541420231874b258381b237 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Thu, 12 Oct 2023 03:44:20 -0400 Subject: [PATCH 11/69] =?UTF-8?q?Fix=20haml-lint=20`InstanceVariables`=20r?= =?UTF-8?q?ule=20for=20auth/sessions/two=5Ffactor/o=E2=80=A6=20(#27372)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .haml-lint_todo.yml | 7 +------ .../concerns/two_factor_authentication_concern.rb | 7 +++++++ app/views/auth/sessions/two_factor.html.haml | 2 +- .../sessions/two_factor/_otp_authentication_form.html.haml | 2 +- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index c6bb38eb4..8a4dbaeff 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -1,6 +1,6 @@ # This configuration was generated by # `haml-lint --auto-gen-config` -# on 2023-10-11 10:49:10 -0400 using Haml-Lint version 0.51.0. +# on 2023-10-11 11:31:24 -0400 using Haml-Lint version 0.51.0. # The point is for the user to remove these configuration records # one by one as the lints are removed from the code base. # Note that changes in the inspected code, or installation of new @@ -26,11 +26,6 @@ linters: - 'app/views/admin/reports/show.html.haml' - 'app/views/disputes/strikes/show.html.haml' - # Offense count: 1 - InstanceVariables: - exclude: - - 'app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml' - # Offense count: 2 IdNames: exclude: diff --git a/app/controllers/concerns/two_factor_authentication_concern.rb b/app/controllers/concerns/two_factor_authentication_concern.rb index 9eb45b90d..bc2d194c3 100644 --- a/app/controllers/concerns/two_factor_authentication_concern.rb +++ b/app/controllers/concerns/two_factor_authentication_concern.rb @@ -5,6 +5,7 @@ module TwoFactorAuthenticationConcern included do prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create] + helper_method :webauthn_enabled? end def two_factor_enabled? @@ -87,4 +88,10 @@ module TwoFactorAuthenticationConcern set_locale { render :two_factor } end + + protected + + def webauthn_enabled? + @webauthn_enabled + end end diff --git a/app/views/auth/sessions/two_factor.html.haml b/app/views/auth/sessions/two_factor.html.haml index 20232d8dc..653f15580 100644 --- a/app/views/auth/sessions/two_factor.html.haml +++ b/app/views/auth/sessions/two_factor.html.haml @@ -3,7 +3,7 @@ = javascript_pack_tag 'two_factor_authentication', crossorigin: 'anonymous' -- if @webauthn_enabled +- if webauthn_enabled? = render partial: 'auth/sessions/two_factor/webauthn_form', locals: { hidden: @scheme_type != 'webauthn' } = render partial: 'auth/sessions/two_factor/otp_authentication_form', locals: { hidden: @scheme_type != 'totp' } diff --git a/app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml b/app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml index 094b502b1..8cc2c8561 100644 --- a/app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml +++ b/app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml @@ -13,6 +13,6 @@ - if Setting.site_contact_email.present? %p.hint.subtle-hint= t('users.otp_lost_help_html', email: mail_to(Setting.site_contact_email, nil)) - - if @webauthn_enabled + - if webauthn_enabled? .form-footer = link_to(t('auth.link_to_webauth'), '#', id: 'link-to-webauthn') From c348b904a589697dfd29bcecb911fd979736ce42 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:07:40 +0200 Subject: [PATCH 12/69] New Crowdin Translations (automated) (#27376) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/si.json | 16 ++++++++++++++++ config/locales/ja.yml | 2 +- config/locales/ko.yml | 14 +++++++------- config/locales/si.yml | 2 +- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/app/javascript/mastodon/locales/si.json b/app/javascript/mastodon/locales/si.json index 43bd5a1bb..7b26a9b48 100644 --- a/app/javascript/mastodon/locales/si.json +++ b/app/javascript/mastodon/locales/si.json @@ -218,7 +218,10 @@ "home.hide_announcements": "නිවේදන සඟවන්න", "home.pending_critical_update.link": "යාවත්කාල බලන්න", "home.show_announcements": "නිවේදන පෙන්වන්න", + "interaction_modal.login.action": "මුලට ගෙනයන්න", "interaction_modal.on_this_server": "මෙම සේවාදායකයෙහි", + "interaction_modal.title.favourite": "{name}ගේ ලිපිය ප්‍රිය කරන්න", + "interaction_modal.title.follow": "{name} අනුගමනය", "intervals.full.days": "{number, plural, one {දවස් #} other {දවස් #}}", "intervals.full.hours": "{number, plural, one {පැය #} other {පැය #}}", "intervals.full.minutes": "{number, plural, one {විනාඩි #} other {විනාඩි #}}", @@ -319,6 +322,7 @@ "notifications.mark_as_read": "සියළු දැනුම්දීම් කියවූ බව යොදන්න", "notifications_permission_banner.enable": "වැඩතල දැනුම්දීම් සබල කරන්න", "notifications_permission_banner.title": "කිසිවක් අතපසු නොකරන්න", + "onboarding.actions.go_to_explore": "නැගී එන දෑ වෙත ගෙනයන්න", "onboarding.compose.template": "ආයුබෝ #මාස්ටඩන්!", "onboarding.share.title": "ඔබගේ පැතිකඩ බෙදාගන්න", "onboarding.steps.publish_status.title": "පළමු ලිපිය පළ කරන්න", @@ -358,6 +362,7 @@ "report.categories.other": "වෙනත්", "report.categories.spam": "ආයාචිත", "report.categories.violation": "අන්තර්ගතය නිසා සේවාදායකයේ නීතියක් හෝ කිහිපයක් කඩ වේ", + "report.category.subtitle": "හොඳම ගැලපීම තෝරන්න", "report.category.title": "මෙම {type}සමඟ සිදුවන්නේ කුමක්දැයි අපට කියන්න", "report.category.title_account": "පැතිකඩ", "report.category.title_status": "ලිපිය", @@ -394,14 +399,25 @@ "report_notification.categories.spam": "ආයාචිත", "report_notification.categories.violation": "නීතිය කඩ කිරීම", "report_notification.open": "විවෘත වාර්තාව", + "search.no_recent_searches": "මෑත සෙවීම් නැත", "search.placeholder": "සොයන්න", + "search.quick_action.account_search": "ගැළපෙන පැතිකඩ {x}", + "search.quick_action.go_to_account": "{x} පැතිකඩ වෙත යන්න", "search.quick_action.open_url": "ලිපිනය මාස්ටඩන්හි අරින්න", + "search.quick_action.status_search": "ගැළපෙන ලිපි {x}", "search.search_or_paste": "සොයන්න හෝ ඒ.ස.නි. අලවන්න", + "search_popout.options": "සෙවුම් විකල්ප", + "search_popout.quick_actions": "ඉක්මන් ක්‍රියාමාර්ග", + "search_popout.recent": "මෑත සෙවීම්", + "search_popout.specific_date": "නිශ්චිත දිනයකට", + "search_popout.user": "පරිශ්‍රීලකයා", + "search_results.accounts": "පැතිකඩ", "search_results.all": "සියල්ල", "search_results.nothing_found": "මෙම සෙවුම් පද සඳහා කිසිවක් සොයාගත නොහැකි විය", "search_results.see_all": "සියල්ල බලන්න", "search_results.statuses": "ලිපි", "search_results.title": "{q} සොයන්න", + "server_banner.active_users": "සක්‍රිය පරිශ්‍රීලකයින්", "server_banner.learn_more": "තව දැනගන්න", "sign_in_banner.create_account": "ගිණුමක් සාදන්න", "sign_in_banner.sign_in": "පිවිසෙන්න", diff --git a/config/locales/ja.yml b/config/locales/ja.yml index 6a72c1ca1..10bc59812 100644 --- a/config/locales/ja.yml +++ b/config/locales/ja.yml @@ -1653,7 +1653,7 @@ ja: private_long: フォロワーにのみ表示されます public: 公開 public_long: 誰でも見ることができ、かつ公開タイムラインに表示されます - unlisted: 未収載 + unlisted: 非収載 unlisted_long: 誰でも見ることができますが、公開タイムラインには表示されません statuses_cleanup: enabled: 古い投稿を自動的に削除する diff --git a/config/locales/ko.yml b/config/locales/ko.yml index ff337cc6c..720d65731 100644 --- a/config/locales/ko.yml +++ b/config/locales/ko.yml @@ -881,9 +881,9 @@ ko: only_allowed: 허용된 것만 pending_review: 심사 대기 preview_card_providers: - allowed: 이 발행처의 링크는 유행록에 실릴 수 있음 + allowed: 이 발행처의 링크는 유행 목록에 실릴 수 있음 description_html: 당신의 서버에서 많은 링크가 공유되고 있는 도메인들입니다. 링크의 도메인이 승인되기 전까지는 링크들은 공개적으로 트렌드에 게시되지 않습니다. 당신의 승인(또는 거절)은 서브도메인까지 확장됩니다. - rejected: 이 발행처의 링크는 유행록에 실리지 않음 + rejected: 이 발행처의 링크는 유행 목록에 실리지 않음 title: 발행처 rejected: 거부됨 statuses: @@ -906,14 +906,14 @@ ko: tag_servers_measure: 다른 서버들 tag_uses_measure: 총 사용 description_html: 현재 서버에서 볼 수 있는 게시물에서 많이 공유되고 있는 해시태그들입니다. 현재 사람들이 무슨 이야기를 하고 있는지 사용자들이 파악할 수 있도록 도움이 됩니다. 승인하지 않는 한 해시태그는 공개적으로 게시되지 않습니다. - listable: 추천될 수 있습니다 + listable: 추천될 수 있음 no_tag_selected: 아무 것도 선택 되지 않아 어떤 태그도 바뀌지 않았습니다 - not_listable: 추천될 수 없습니다 - not_trendable: 유행 목록에 나타나지 않습니다 - not_usable: 사용불가 + not_listable: 추천하지 않음 + not_trendable: 유행 목록에 나타내지 않음 + not_usable: 이용할 수 없음 peaked_on_and_decaying: "%{date}에 고점을 찍고, 떨어지고 있습니다" title: 유행하는 해시태그 - trendable: 유행 목록에 나타날 수 있습니다 + trendable: 유행 목록에 나타날 수 있음 trending_rank: "#%{rank}위로 유행 중" usable: 사용 가능 usage_comparison: 오늘은 %{today}회 쓰였고, 어제는 %{yesterday}회 쓰임 diff --git a/config/locales/si.yml b/config/locales/si.yml index 8f0453351..39368b3f8 100644 --- a/config/locales/si.yml +++ b/config/locales/si.yml @@ -299,7 +299,7 @@ si: updated_msg: ඉමොජි සාර්ථකව යාවත්කාලීන කරන ලදී! upload: උඩුගත කරන්න dashboard: - active_users: ක්රියාකාරී පරිශීලකයන් + active_users: සක්‍රිය පරිශ්‍රීලකයින් interactions: අන්තර්ක්රියා media_storage: මාධ්‍ය ආචයනය new_users: නව පරිශ්‍රීලකයින් From c86ad4566003f15f2f4f2499efd36f20b62737ab Mon Sep 17 00:00:00 2001 From: Claire Date: Thu, 12 Oct 2023 16:47:18 +0200 Subject: [PATCH 13/69] Change PWA start URL from `/home` to `/` (#27377) --- app/serializers/manifest_serializer.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/serializers/manifest_serializer.rb b/app/serializers/manifest_serializer.rb index aa7f7ce5d..501bb788e 100644 --- a/app/serializers/manifest_serializer.rb +++ b/app/serializers/manifest_serializer.rb @@ -16,11 +16,18 @@ class ManifestSerializer < ActiveModel::Serializer 512 ).freeze - attributes :name, :short_name, + attributes :id, :name, :short_name, :icons, :theme_color, :background_color, :display, :start_url, :scope, :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 object.title end @@ -53,7 +60,7 @@ class ManifestSerializer < ActiveModel::Serializer end def start_url - '/home' + '/' end def scope From 133f218a4da3d895ae99879acb3afaa25c12f80a Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 13 Oct 2023 04:42:59 -0400 Subject: [PATCH 14/69] Add system specs for admin area `batch` actions (#25027) --- spec/features/admin/accounts_spec.rb | 81 +++++++++++++++++++ spec/features/admin/custom_emojis_spec.rb | 33 ++++++++ .../admin/email_domain_blocks_spec.rb | 33 ++++++++ spec/features/admin/ip_blocks_spec.rb | 33 ++++++++ spec/features/admin/statuses_spec.rb | 34 ++++++++ .../links/preview_card_providers_spec.rb | 33 ++++++++ spec/features/admin/trends/links_spec.rb | 33 ++++++++ spec/features/admin/trends/statuses_spec.rb | 33 ++++++++ spec/features/admin/trends/tags_spec.rb | 33 ++++++++ 9 files changed, 346 insertions(+) create mode 100644 spec/features/admin/accounts_spec.rb create mode 100644 spec/features/admin/custom_emojis_spec.rb create mode 100644 spec/features/admin/email_domain_blocks_spec.rb create mode 100644 spec/features/admin/ip_blocks_spec.rb create mode 100644 spec/features/admin/statuses_spec.rb create mode 100644 spec/features/admin/trends/links/preview_card_providers_spec.rb create mode 100644 spec/features/admin/trends/links_spec.rb create mode 100644 spec/features/admin/trends/statuses_spec.rb create mode 100644 spec/features/admin/trends/tags_spec.rb diff --git a/spec/features/admin/accounts_spec.rb b/spec/features/admin/accounts_spec.rb new file mode 100644 index 000000000..6d7bab184 --- /dev/null +++ b/spec/features/admin/accounts_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Admin::Accounts' do + let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + + before do + sign_in current_user + end + + describe 'Performing batch updates' do + let(:unapproved_user_account) { Fabricate(:account) } + let(:approved_user_account) { Fabricate(:account) } + + before do + unapproved_user_account.user.update(approved: false) + approved_user_account.user.update(approved: true) + + visit admin_accounts_path + end + + context 'without selecting any accounts' do + it 'displays a notice about account selection' do + click_on button_for_suspend + + expect(page).to have_content(selection_error_text) + end + end + + context 'with action of `suspend`' do + it 'suspends the account' do + batch_checkbox_for(approved_user_account).check + + click_on button_for_suspend + + expect(approved_user_account.reload).to be_suspended + end + end + + context 'with action of `approve`' do + it 'approves the account user' do + batch_checkbox_for(unapproved_user_account).check + + click_on button_for_approve + + expect(unapproved_user_account.reload.user).to be_approved + end + end + + context 'with action of `reject`' do + it 'rejects and removes the account' do + batch_checkbox_for(unapproved_user_account).check + + click_on button_for_reject + + expect { unapproved_user_account.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + def button_for_suspend + I18n.t('admin.accounts.perform_full_suspension') + end + + def button_for_approve + I18n.t('admin.accounts.approve') + end + + def button_for_reject + I18n.t('admin.accounts.reject') + end + + def selection_error_text + I18n.t('admin.accounts.no_account_selected') + end + + def batch_checkbox_for(account) + find("#form_account_batch_account_ids_#{account.id}") + end + end +end diff --git a/spec/features/admin/custom_emojis_spec.rb b/spec/features/admin/custom_emojis_spec.rb new file mode 100644 index 000000000..8a8b6efcd --- /dev/null +++ b/spec/features/admin/custom_emojis_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Admin::CustomEmojis' do + let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + + before do + sign_in current_user + end + + describe 'Performing batch updates' do + before do + visit admin_custom_emojis_path + end + + context 'without selecting any records' do + it 'displays a notice about selection' do + click_on button_for_enable + + expect(page).to have_content(selection_error_text) + end + end + + def button_for_enable + I18n.t('admin.custom_emojis.enable') + end + + def selection_error_text + I18n.t('admin.custom_emojis.no_emoji_selected') + end + end +end diff --git a/spec/features/admin/email_domain_blocks_spec.rb b/spec/features/admin/email_domain_blocks_spec.rb new file mode 100644 index 000000000..14959cbe7 --- /dev/null +++ b/spec/features/admin/email_domain_blocks_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Admin::EmailDomainBlocks' do + let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + + before do + sign_in current_user + end + + describe 'Performing batch updates' do + before do + visit admin_email_domain_blocks_path + end + + context 'without selecting any records' do + it 'displays a notice about selection' do + click_on button_for_delete + + expect(page).to have_content(selection_error_text) + end + end + + def button_for_delete + I18n.t('admin.email_domain_blocks.delete') + end + + def selection_error_text + I18n.t('admin.email_domain_blocks.no_email_domain_block_selected') + end + end +end diff --git a/spec/features/admin/ip_blocks_spec.rb b/spec/features/admin/ip_blocks_spec.rb new file mode 100644 index 000000000..c9b16f6f7 --- /dev/null +++ b/spec/features/admin/ip_blocks_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Admin::IpBlocks' do + let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + + before do + sign_in current_user + end + + describe 'Performing batch updates' do + before do + visit admin_ip_blocks_path + end + + context 'without selecting any records' do + it 'displays a notice about selection' do + click_on button_for_delete + + expect(page).to have_content(selection_error_text) + end + end + + def button_for_delete + I18n.t('admin.ip_blocks.delete') + end + + def selection_error_text + I18n.t('admin.ip_blocks.no_ip_block_selected') + end + end +end diff --git a/spec/features/admin/statuses_spec.rb b/spec/features/admin/statuses_spec.rb new file mode 100644 index 000000000..531d0de95 --- /dev/null +++ b/spec/features/admin/statuses_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Admin::Statuses' do + let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + + before do + sign_in current_user + end + + describe 'Performing batch updates' do + before do + _status = Fabricate(:status, account: current_user.account) + visit admin_account_statuses_path(account_id: current_user.account_id) + end + + context 'without selecting any records' do + it 'displays a notice about selection' do + click_on button_for_report + + expect(page).to have_content(selection_error_text) + end + end + + def button_for_report + I18n.t('admin.statuses.batch.report') + end + + def selection_error_text + I18n.t('admin.statuses.no_status_selected') + end + end +end diff --git a/spec/features/admin/trends/links/preview_card_providers_spec.rb b/spec/features/admin/trends/links/preview_card_providers_spec.rb new file mode 100644 index 000000000..dca89117b --- /dev/null +++ b/spec/features/admin/trends/links/preview_card_providers_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Admin::Trends::Links::PreviewCardProviders' do + let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + + before do + sign_in current_user + end + + describe 'Performing batch updates' do + before do + visit admin_trends_links_preview_card_providers_path + end + + context 'without selecting any records' do + it 'displays a notice about selection' do + click_on button_for_allow + + expect(page).to have_content(selection_error_text) + end + end + + def button_for_allow + I18n.t('admin.trends.allow') + end + + def selection_error_text + I18n.t('admin.trends.links.publishers.no_publisher_selected') + end + end +end diff --git a/spec/features/admin/trends/links_spec.rb b/spec/features/admin/trends/links_spec.rb new file mode 100644 index 000000000..99638bc06 --- /dev/null +++ b/spec/features/admin/trends/links_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Admin::Trends::Links' do + let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + + before do + sign_in current_user + end + + describe 'Performing batch updates' do + before do + visit admin_trends_links_path + end + + context 'without selecting any records' do + it 'displays a notice about selection' do + click_on button_for_allow + + expect(page).to have_content(selection_error_text) + end + end + + def button_for_allow + I18n.t('admin.trends.links.allow') + end + + def selection_error_text + I18n.t('admin.trends.links.no_link_selected') + end + end +end diff --git a/spec/features/admin/trends/statuses_spec.rb b/spec/features/admin/trends/statuses_spec.rb new file mode 100644 index 000000000..779a15d38 --- /dev/null +++ b/spec/features/admin/trends/statuses_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Admin::Trends::Statuses' do + let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + + before do + sign_in current_user + end + + describe 'Performing batch updates' do + before do + visit admin_trends_statuses_path + end + + context 'without selecting any records' do + it 'displays a notice about selection' do + click_on button_for_allow + + expect(page).to have_content(selection_error_text) + end + end + + def button_for_allow + I18n.t('admin.trends.statuses.allow') + end + + def selection_error_text + I18n.t('admin.trends.statuses.no_status_selected') + end + end +end diff --git a/spec/features/admin/trends/tags_spec.rb b/spec/features/admin/trends/tags_spec.rb new file mode 100644 index 000000000..52e49c3a5 --- /dev/null +++ b/spec/features/admin/trends/tags_spec.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Admin::Trends::Tags' do + let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + + before do + sign_in current_user + end + + describe 'Performing batch updates' do + before do + visit admin_trends_tags_path + end + + context 'without selecting any records' do + it 'displays a notice about selection' do + click_on button_for_allow + + expect(page).to have_content(selection_error_text) + end + end + + def button_for_allow + I18n.t('admin.trends.allow') + end + + def selection_error_text + I18n.t('admin.trends.tags.no_tag_selected') + end + end +end From 6a9aab38d7294cd3c0f62fb8acf703ea58e8f48f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Oct 2023 10:52:55 +0200 Subject: [PATCH 15/69] Update dependency devise-two-factor to v4.1.1 (#27382) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index dfa184624..a47a9d046 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -216,11 +216,11 @@ GEM railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-two-factor (4.1.0) - activesupport (< 7.1) + devise-two-factor (4.1.1) + activesupport (~> 7.0) attr_encrypted (>= 1.3, < 5, != 2) devise (~> 4.0) - railties (< 7.1) + railties (~> 7.0) rotp (~> 6.0) devise_pam_authenticatable2 (9.2.0) devise (>= 4.0.0) From 773d25e8df5f32973df343a4dffdb730ef490d3d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:01:40 +0200 Subject: [PATCH 16/69] New Crowdin Translations (automated) (#27388) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/fi.json | 24 ++--- config/locales/doorkeeper.fi.yml | 6 +- config/locales/fi.yml | 120 ++++++++++++------------ config/locales/simple_form.fi.yml | 18 ++-- 4 files changed, 84 insertions(+), 84 deletions(-) diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index cef225033..91be7bfc0 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -26,10 +26,10 @@ "account.domain_blocked": "Verkkotunnus estetty", "account.edit_profile": "Muokkaa profiilia", "account.enable_notifications": "Ilmoita minulle, kun @{name} julkaisee", - "account.endorse": "Suosittele profiilissasi", + "account.endorse": "Pidä esillä profiilissa", "account.featured_tags.last_status_at": "Viimeisin julkaisu {date}", "account.featured_tags.last_status_never": "Ei julkaisuja", - "account.featured_tags.title": "Käyttäjän {name} esille nostetut aihetunnisteet", + "account.featured_tags.title": "Käyttäjän {name} esillä pidettävät aihetunnisteet", "account.follow": "Seuraa", "account.followers": "Seuraajat", "account.followers.empty": "Kukaan ei seuraa tätä käyttäjää vielä.", @@ -62,10 +62,10 @@ "account.share": "Jaa käyttäjän @{name} profiili", "account.show_reblogs": "Näytä käyttäjän @{name} tehostukset", "account.statuses_counter": "{count, plural, one {{counter} julkaisu} other {{counter} julkaisua}}", - "account.unblock": "Poista esto: @{name}", - "account.unblock_domain": "Salli palvelu {domain}", + "account.unblock": "Poista käyttäjän @{name} esto", + "account.unblock_domain": "Poista verkkotunnuksen {domain} esto", "account.unblock_short": "Poista esto", - "account.unendorse": "Poista suosittelu profiilistasi", + "account.unendorse": "Älä pidä esillä profiilissa", "account.unfollow": "Lopeta seuraaminen", "account.unmute": "Poista käyttäjän @{name} mykistys", "account.unmute_notifications_short": "Poista ilmoitusten mykistys", @@ -225,12 +225,12 @@ "empty_column.account_suspended": "Tili jäädytetty", "empty_column.account_timeline": "Ei viestejä täällä.", "empty_column.account_unavailable": "Profiilia ei löydy", - "empty_column.blocks": "Et ole estänyt käyttäjiä.", + "empty_column.blocks": "Et ole vielä estänyt käyttäjiä.", "empty_column.bookmarked_statuses": "Et ole vielä lisännyt julkaisuja kirjanmerkkeihisi. Kun lisäät yhden, se näkyy tässä.", "empty_column.community": "Paikallinen aikajana on tyhjä. Kirjoita jotain julkista, niin homma lähtee käyntiin!", "empty_column.direct": "Yksityisiä mainintoja ei vielä ole. Jos lähetät tai sinulle lähetetään sellaisia, näet ne täällä.", - "empty_column.domain_blocks": "Palveluita ei ole vielä estetty.", - "empty_column.explore_statuses": "Mikään ei trendaa nyt. Tarkista myöhemmin uudelleen!", + "empty_column.domain_blocks": "Verkkotunnuksia ei ole vielä estetty.", + "empty_column.explore_statuses": "Mikään ei ole nyt suosittua. Tarkista myöhemmin uudelleen!", "empty_column.favourited_statuses": "Sinulla ei ole vielä yhtään suosikkijulkaisua. Kun lisäät sellaisen, näkyy se tässä.", "empty_column.favourites": "Kukaan ei ole vielä lisännyt tätä julkaisua suosikkeihinsa. Kun joku tekee niin, tulee hän tähän näkyviin.", "empty_column.follow_requests": "Et ole vielä vastaanottanut seuraamispyyntöjä. Saamasi pyynnöt näkyvät täällä.", @@ -414,7 +414,7 @@ "navigation_bar.lists": "Listat", "navigation_bar.logout": "Kirjaudu ulos", "navigation_bar.mutes": "Mykistetyt käyttäjät", - "navigation_bar.opened_in_classic_interface": "Julkaisut, profiilit ja tietyt muut sivut avautuvat oletuksena perinteiseen web-käyttöliittymään.", + "navigation_bar.opened_in_classic_interface": "Julkaisut, profiilit ja tietyt muut sivut avautuvat oletuksena perinteiseen selainkäyttöliittymään.", "navigation_bar.personal": "Henkilökohtainen", "navigation_bar.pins": "Kiinnitetyt julkaisut", "navigation_bar.preferences": "Asetukset", @@ -472,7 +472,7 @@ "notifications_permission_banner.title": "Älä anna minkään mennä ohi", "onboarding.action.back": "Palaa takaisin", "onboarding.actions.back": "Palaa takaisin", - "onboarding.actions.go_to_explore": "Siirry suosituimpien aiheiden syötteeseen", + "onboarding.actions.go_to_explore": "Siirry suosittujen aiheiden syötteeseen", "onboarding.actions.go_to_home": "Siirry kotisyötteeseeni", "onboarding.compose.template": "Tervehdys #Mastodon!", "onboarding.follows.empty": "Valitettavasti tuloksia ei voida näyttää juuri nyt. Voit kokeilla hakua tai selata tutustumissivua löytääksesi seurattavaa tai yrittää myöhemmin uudelleen.", @@ -537,7 +537,7 @@ "relative_time.today": "tänään", "reply_indicator.cancel": "Peruuta", "report.block": "Estä", - "report.block_explanation": "Et näe hänen viestejään, eikä hän voi nähdä viestejäsi tai seurata sinua. Hän näkevät, että olet estänyt hänet.", + "report.block_explanation": "Et näe hänen viestejään, eikä hän voi nähdä viestejäsi tai seurata sinua. Hän näkee, että olet estänyt hänet.", "report.categories.legal": "Lakiasiat", "report.categories.other": "Muu", "report.categories.spam": "Roskaposti", @@ -694,7 +694,7 @@ "units.short.thousand": "{count} t.", "upload_area.title": "Lataa raahaamalla ja pudottamalla tähän", "upload_button.label": "Lisää kuvia, video tai äänitiedosto", - "upload_error.limit": "Tiedostolatauksien raja ylitetty.", + "upload_error.limit": "Tiedostolatauksien rajoitus ylitetty.", "upload_error.poll": "Tiedoston lataaminen ei ole sallittua äänestyksissä.", "upload_form.audio_description": "Kuvaile sisältöä kuuroille ja kuulorajoitteisille", "upload_form.description": "Kuvaile sisältöä sokeille ja näkörajoitteisille", diff --git a/config/locales/doorkeeper.fi.yml b/config/locales/doorkeeper.fi.yml index 0f263b00d..fea01d107 100644 --- a/config/locales/doorkeeper.fi.yml +++ b/config/locales/doorkeeper.fi.yml @@ -119,11 +119,11 @@ fi: write: Vain kirjoitusoikeus title: accounts: Tilit - admin/accounts: Tilien hallinta + admin/accounts: Tilien hallinnointi admin/all: Kaikki hallinnolliset toiminnot - admin/reports: Raporttien hallinta + admin/reports: Raporttien hallinnointi all: Täysi pääsy Mastodon-tiliisi - blocks: Torjutut + blocks: Estot bookmarks: Kirjanmerkit conversations: Keskustelut crypto: Päästä päähän -salaus diff --git a/config/locales/fi.yml b/config/locales/fi.yml index d1f345abd..0c36b795c 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -28,10 +28,10 @@ fi: title: Suorita valvontatoimi käyttäjälle %{acct} account_moderation_notes: create: Jätä muistiinpano - created_msg: Valvontamerkinnän luonti onnistui! - destroyed_msg: Valvontamerkinnän poisto onnistui! + created_msg: Valvontamuistiinpanon luonti onnistui! + destroyed_msg: Valvontamuistiinpanon poisto onnistui! accounts: - add_email_domain_block: Estä sähköpostidomain + add_email_domain_block: Estä sähköpostiverkkotunnus approve: Hyväksy approved_msg: Käyttäjän %{username} liittymishakemus hyväksyttiin are_you_sure: Oletko varma? @@ -96,10 +96,10 @@ fi: silenced: Rajoitettu suspended: Jäädytetty title: Valvonta - moderation_notes: Valvontamerkinnät + moderation_notes: Valvontamuistiinpanot most_recent_activity: Viimeisin toiminta most_recent_ip: Viimeisin IP - no_account_selected: Yhtään tiliä ei muutettu, koska mitään ei valittu + no_account_selected: Tilejä ei muutettu, koska yhtään ei ollut valittuna no_limits_imposed: Rajoituksia ei ole asetettu no_role_assigned: Roolia ei ole määritetty not_subscribed: Ei tilaaja @@ -118,7 +118,7 @@ fi: reject: Hylkää rejected_msg: Käyttäjän %{username} rekisteröitymishakemus hylättiin remote_suspension_irreversible: Tämän tilin tiedot on poistettu peruuttamattomasti. - remote_suspension_reversible_hint_html: Tili on jäädytetty heidän palvelimellaan, ja kaikki tiedot poistetaan %{date}. Sitä ennen etäpalvelin voi palauttaa tilin ongelmitta. Jos haluat poistaa kaikki tilin tiedot heti, onnistuu se alta. + remote_suspension_reversible_hint_html: Tili on jäädytetty omalla palvelimellaan, ja kaikki tiedot poistetaan %{date}. Sitä ennen etäpalvelin voi palauttaa tilin ongelmitta. Jos haluat poistaa kaikki tilin tiedot heti, onnistuu se alta. remove_avatar: Poista profiilikuva remove_header: Poista otsakekuva removed_avatar_msg: Käyttäjän %{username} avatar-kuva poistettu onnistuneesti @@ -182,7 +182,7 @@ fi: create_custom_emoji: Luo mukautettu emoji create_domain_allow: Luo verkkotunnuksen salliminen create_domain_block: Luo verkkotunnuksen esto - create_email_domain_block: Luo sähköpostin verkkotunnuksen esto + create_email_domain_block: Luo sähköpostiverkkotunnuksen esto create_ip_block: Luo IP-sääntö create_unavailable_domain: Luo ei-saatavilla oleva verkkotunnus create_user_role: Luo rooli @@ -192,7 +192,7 @@ fi: destroy_custom_emoji: Poista mukautettu emoji destroy_domain_allow: Poista verkkotunnuksen salliminen destroy_domain_block: Poista verkkotunnuksen esto - destroy_email_domain_block: Poista sähköpostin verkkotunnuksen esto + destroy_email_domain_block: Poista sähköpostiverkkotunnuksen esto destroy_instance: Tyhjennä verkkotunnus destroy_ip_block: Poista IP-sääntö destroy_status: Poista julkaisu @@ -237,21 +237,21 @@ fi: confirm_user_html: "%{name} vahvisti käyttäjän %{target} sähköpostiosoitteen" create_account_warning_html: "%{name} lähetti varoituksen käyttäjälle %{target}" create_announcement_html: "%{name} loi uuden tiedotteen %{target}" - create_canonical_email_block_html: "%{name} esti sähköpostin hashilla %{target}" + create_canonical_email_block_html: "%{name} esti sähköpostin tiivisteellä %{target}" create_custom_emoji_html: "%{name} lähetti uuden emojin %{target}" create_domain_allow_html: "%{name} salli federoinnin verkkotunnuksen %{target} kanssa" create_domain_block_html: "%{name} esti verkkotunnuksen %{target}" - create_email_domain_block_html: "%{name} esti sähköpostin %{target}" + create_email_domain_block_html: "%{name} esti sähköpostiverkkotunnuksen %{target}" create_ip_block_html: "%{name} loi IP-säännön %{target}" create_unavailable_domain_html: "%{name} pysäytti toimituksen verkkotunnukseen %{target}" create_user_role_html: "%{name} loi roolin %{target}" demote_user_html: "%{name} alensi käyttäjän %{target}" destroy_announcement_html: "%{name} poisti tiedotteen %{target}" - destroy_canonical_email_block_html: "%{name} poisti sähköpostieston hashilla %{target}" + destroy_canonical_email_block_html: "%{name} poisti sähköpostin eston tiivisteellä %{target}" destroy_custom_emoji_html: "%{name} poisti emojin %{target}" destroy_domain_allow_html: "%{name} kielsi federoinnin verkkotunnuksen %{target} kanssa" destroy_domain_block_html: "%{name} poisti verkkotunnuksen %{target} eston" - destroy_email_domain_block_html: "%{name} poisti sähköpostin verkkotunnuksen %{target} eston" + destroy_email_domain_block_html: "%{name} poisti sähköpostiverkkotunnuksen %{target} eston" destroy_instance_html: "%{name} tyhjensi verkkotunnuksen %{target}" destroy_ip_block_html: "%{name} poisti IP-säännön %{target}" destroy_status_html: "%{name} poisti käyttäjän %{target} julkaisun" @@ -332,7 +332,7 @@ fi: listed: Listalla new: title: Lisää uusi mukautettu emoji - no_emoji_selected: Emojeita ei muutettu, koska yhtään ei valittu + no_emoji_selected: Emojeita ei muutettu, koska yhtään ei ollut valittuna not_permitted: Sinulla ei ole oikeutta suorittaa tätä toimintoa overwrite: Korvaa shortcode: Lyhennekoodi @@ -382,7 +382,7 @@ fi: import: Tuo undo: Estä liitto verkkotunnukselle domain_blocks: - add_new: Lisää uusi + add_new: Lisää uusi verkkotunnuksen esto confirm_suspension: cancel: Peruuta confirm: Jäädytä @@ -409,7 +409,7 @@ fi: silence: Rajoita suspend: Jäädytä title: Uusi verkkotunnuksen esto - no_domain_block_selected: Verkkoalue-estoihin ei tehty muutoksia, koska valintoja ei tehty + no_domain_block_selected: Verkkotunnusten estoja ei muutettu, koska yhtään ei ollut valittuna not_permitted: Nykyiset käyttöoikeutesi eivät kata tätä toimintoa obfuscate: Peitä verkkotunnuksen nimi obfuscate_hint: Peitä verkkotunnus osittain luettelossa, jos julkinen verkkotunnusten rajoitusluettelo on käytössä @@ -421,14 +421,14 @@ fi: reject_media_hint: Poistaa paikallisesti tallennetut mediatiedostot eikä lataa niitä enää jatkossa. Ei merkitystä jäähyn kohdalla reject_reports: Hylkää raportit reject_reports_hint: Ohita kaikki tästä verkkotunnuksesta tulevat raportit. Erottamisen kannalta ei merkitystä - undo: Peru + undo: Peru verkkotunnuksen esto view: Näytä verkkotunnuksen esto email_domain_blocks: add_new: Lisää uusi attempts_over_week: one: "%{count} yritystä viimeisen viikon aikana" other: "%{count} rekisteröitymisyritystä viimeisen viikon aikana" - created_msg: Sähköpostiverkkotunnuksen lisäys estolistalle onnistui + created_msg: Sähköpostiverkkotunnus estetty onnistuneesti delete: Poista dns: types: @@ -437,26 +437,26 @@ fi: new: create: Lisää verkkotunnus resolve: Ratkaise verkkotunnus - title: Uusi sähköpostiestolistan merkintä - no_email_domain_block_selected: Sähköpostin verkkotunnuksia ei muutettu, koska yhtään ei valittu + title: Estä uusi sähköpostiverkkotunnus + no_email_domain_block_selected: Sähköpostin verkkotunnuksia ei muutettu, koska yhtään ei ollut valittuna not_permitted: Ei sallittu resolved_dns_records_hint_html: Verkkotunnuksen nimi määräytyy seuraaviin MX-verkkotunnuksiin, jotka ovat viime kädessä vastuussa sähköpostin vastaanottamisesta. MX-verkkotunnuksen estäminen estää kirjautumisen mistä tahansa sähköpostiosoitteesta, joka käyttää samaa MX-verkkotunnusta, vaikka näkyvä verkkotunnuksen nimi olisikin erilainen. Varo estämästä suuria sähköpostin palveluntarjoajia. resolved_through_html: Ratkaistu %{domain} kautta - title: Sähköpostiestolista + title: Estetyt sähköpostiverkkotunnukset export_domain_allows: new: - title: Tuo sallitut verkkoalueet + title: Tuo sallittuja verkkotunnuksia no_file: Yhtäkään tiedostoa ei ole valittu export_domain_blocks: import: description_html: Olet tuomassa verkkotunnusten estoluetteloa. Tarkista luettelo huolella – etenkin, jos et ole laatinut sitä itse. existing_relationships_warning: Olemassa olevat seuraussuhteet - private_comment_description_html: 'Tuodun estolistan alkuperän selvillä pitämiseksi, lisätään tietojen yhteyteen seuraava yksityinen kommentti: %{comment}' - private_comment_template: Tuotu lähteestä %{source}, pvm %{date} - title: Tuo luettelo verkkoalue-estoista - invalid_domain_block: 'Yksi tai useampi verkkotunnuksen lohko ohitettiin seuraavien virheiden vuoksi: %{error}' + private_comment_description_html: 'Seurataksesi tuotujen estojen alkuperää lisätään estojen yhteyteen seuraava yksityinen kommentti: %{comment}' + private_comment_template: Tuotu lähteestä %{source} %{date} + title: Tuo verkkotunnusten estoja + invalid_domain_block: 'Yksi tai useampi verkkotunnuksen esto ohitettiin seuraavien virheiden vuoksi: %{error}' new: - title: Tuo luettelo verkkoalue-estoista + title: Tuo verkkotunnusten estoja no_file: Yhtäkään tiedostoa ei ole valittu follow_recommendations: description_html: "Seuraamissuositukset auttavat uusia käyttäjiä löytämään nopeasti kiinnostavaa sisältöä. Kun käyttäjä ei ole ollut tarpeeksi vuorovaikutuksessa muiden kanssa, jotta hänelle olisi muodostunut henkilökohtaisia seuraamissuosituksia, suositellaan niiden sijaan näitä tilejä. Ne lasketaan päivittäin uudelleen yhdistelmästä tilejä, jotka ovat viime aikoina olleet aktiivisimmin sitoutuneita ja joilla on suurimmat paikalliset seuraajamäärät tietyllä kielellä." @@ -482,9 +482,9 @@ fi: back_to_limited: Rajoitettu back_to_warning: Varoitus by_domain: Verkkotunnus - confirm_purge: Oletko varma, että haluat pysyvästi poistaa tiedot tältä verkkotunnukselta? + confirm_purge: Haluatko varmasti poistaa pysyvästi tämän verkkotunnuksen tiedot? content_policies: - comment: Sisäinen huomautus + comment: Sisäinen muistiinpano description_html: Voit määrittää sisältökäytännöt, joita sovelletaan kaikkiin tämän verkkotunnuksen ja sen aliverkkotunnuksien tileihin. limited_federation_mode_description_html: Voit valita sallitaanko federointi tällä verkkotunnuksella. policies: @@ -528,7 +528,7 @@ fi: purge: Tyhjennä purge_description_html: Jos uskot, että tämä verkkotunnus on offline-tilassa tarkoituksella, voit poistaa kaikki verkkotunnuksen tilitietueet ja niihin liittyvät tiedot tallennustilastasi. Tämä voi kestää jonkin aikaa. title: Federointi - total_blocked_by_us: Estetty meidän toimesta + total_blocked_by_us: Estämämme total_followed_by_them: Heidän seuraama total_followed_by_us: Meidän seuraama total_reported: Niitä koskevat raportit @@ -555,7 +555,7 @@ fi: '94670856': 3 vuotta new: title: Luo uusi IP-sääntö - no_ip_block_selected: IP-sääntöjä ei muutettu, koska yhtään ei ole valittuna + no_ip_block_selected: IP-sääntöjä ei muutettu, koska yhtään ei ollut valittuna title: IP-säännöt relationships: title: "%{acct}n suhteet" @@ -576,13 +576,13 @@ fi: status: Tila title: Välittäjät report_notes: - created_msg: Muistiinpano onnistuneesti lisätty raporttiin! - destroyed_msg: Muistiinpano onnistuneesti poistettu raportista! + created_msg: Muistiinpano lisätty raporttiin onnistuneesti! + destroyed_msg: Muistiinpano poistettu raportista onnistuneesti! reports: account: notes: - one: "%{count} ilmoitus" - other: "%{count} ilmoitusta" + one: "%{count} muistiinpano" + other: "%{count} muistiinpanoa" action_log: Tarkastusloki action_taken_by: Toimen tehnyt actions: @@ -621,8 +621,8 @@ fi: create_and_unresolve: Avaa uudelleen ja lisää muistiinpano delete: Poista placeholder: Kuvaile mitä toimia on tehty tai muita päivityksiä tähän raporttiin… - title: Merkinnät - notes_description_html: Tarkastele ja jätä merkintöjä muille valvojille ja itsellesi tulevaisuuteen + title: Muistiinpanot + notes_description_html: Tarkastele ja jätä muistiinpanoja muille valvojille ja itsellesi tulevaisuuteen processed_msg: 'Raportti #%{id} käsitelty' quick_actions_description_html: 'Suorita nopea toiminto tai vieritä alas nähdäksesi raportoitu sisältö:' remote_user_placeholder: etäkäyttäjä instanssista %{instance} @@ -708,7 +708,7 @@ fi: manage_settings: Hallitse asetuksia manage_settings_description: Sallii käyttäjien muuttaa sivuston asetuksia manage_taxonomies: Hallitse luokittelua - manage_taxonomies_description: Sallii käyttäjien tarkistaa nousussa olevan sisällön ja päivittää aihetunnisteiden asetuksia + manage_taxonomies_description: Sallii käyttäjien tarkistaa suositun sisällön ja päivittää aihetunnisteiden asetuksia manage_user_access: Hallitse käyttäjäoikeuksia manage_user_access_description: Sallii käyttäjien poistaa muiden käyttäjien kaksivaiheinen todennus käytöstä, vaihtaa heidän sähköpostiosoitteensa ja nollata heidän salasanansa manage_users: Hallitse käyttäjiä @@ -736,10 +736,10 @@ fi: rules_hint: On olemassa erityinen alue sääntöjä, joita käyttäjien odotetaan noudattavan. title: Tietoja appearance: - preamble: Mukauta Mastodonin verkkokäyttöliittymää. + preamble: Mukauta Mastodonin selainkäyttöliittymää. title: Ulkoasu branding: - preamble: Palvelimesi brändäys erottaa sen muista verkon palvelimista. Nämä tiedot voivat näkyä monissa eri ympäristöissä, kuten Mastodonin verkkokäyttöliittymässä, natiivisovelluksissa, linkkien esikatseluissa muilla sivustoilla, viestintäsovelluksissa ja niin edelleen. Siksi nämä tiedot kannattaa pitää selkeinä, lyhyinä ja ytimekkäinä. + preamble: Palvelimesi brändäys erottaa sen muista verkon palvelimista. Nämä tiedot voivat näkyä monissa eri ympäristöissä, kuten Mastodonin selainkäyttöliittymässä, natiivisovelluksissa, linkkien esikatseluissa muilla sivustoilla, viestintäsovelluksissa ja niin edelleen. Siksi nämä tiedot kannattaa pitää selkeinä, lyhyinä ja ytimekkäinä. title: Brändäys captcha_enabled: desc_html: Tämä perustuu ulkoisiin skripteihin hCaptchasta, mikä voi olla turvallisuus- ja yksityisyysongelma. Lisäksi tämä voi tehdä rekisteröinnin ihmisille huomattavasti (erityisesti vammaisten) helpommaksi. Harkitse vaihtoehtoisia toimenpiteitä, kuten hyväksymisperusteista tai kutsupohjaista rekisteröintiä. @@ -808,13 +808,13 @@ fi: media: title: Media metadata: Metadata - no_status_selected: Julkaisuja ei muutettu, koska yhtään ei ole valittuna + no_status_selected: Julkaisuja ei muutettu, koska yhtään ei ollut valittuna open: Avaa julkaisu original_status: Alkuperäinen julkaisu reblogs: Edelleen jako status_changed: Julkaisua muutettu title: Tilin tilat - trending: Nousussa + trending: Suosituttua visibility: Näkyvyys with_media: Sisältää mediaa strikes: @@ -882,9 +882,9 @@ fi: description_html: Nämä ovat linkkejä, joita jaetaan tällä hetkellä paljon tileillä, joilta palvelimesi näkee viestejä. Se voi auttaa käyttäjiäsi saamaan selville, mitä maailmassa tapahtuu. Linkkejä ei näytetä julkisesti, ennen kuin hyväksyt julkaisijan. Voit myös sallia tai hylätä yksittäiset linkit. disallow: Hylkää linkki disallow_provider: Estä julkaisija - no_link_selected: Yhtään linkkiä ei muutettu, koska yhtään ei valittu + no_link_selected: Linkkejä ei muutettu, koska yhtään ei ollut valittuna publishers: - no_publisher_selected: Julkaisijoita ei muutettu, koska yhtään ei valittu + no_publisher_selected: Julkaisijoita ei muutettu, koska yhtään ei ollut valittuna shared_by_over_week: one: Yksi henkilö jakanut viimeisen viikon aikana other: Jakanut %{count} henkilöä viimeisen viikon aikana @@ -905,7 +905,7 @@ fi: description_html: Nämä ovat julkaisuja, joita palvelimesi tietää jaettavan ja lisättävän suosikkeihin paljon tällä hetkellä. Listaus voi auttaa uusia ja palaavia käyttäjiäsi löytämään lisää seurattavia. Julkaisut eivät näy julkisesti ennen kuin hyväksyt niiden julkaisijan ja julkaisija sallii tilinsä ehdottamisen. Voit myös sallia tai hylätä yksittäisiä julkaisuja. disallow: Kiellä julkaisu disallow_account: Estä tekijä - no_status_selected: Suosittuja julkaisuja ei muutettu, koska yhtään ei ole valittuna + no_status_selected: Suosittuja julkaisuja ei muutettu, koska yhtään ei ollut valittuna not_discoverable: Tekijä ei ole ilmoittanut olevansa löydettävissä shared_by: one: Jaettu tai lisätty suosikkeihin kerran @@ -921,21 +921,21 @@ fi: tag_uses_measure: käyttökerrat description_html: Nämä ovat aihetunnisteita, jotka näkyvät tällä hetkellä monissa julkaisuissa, jotka palvelimesi näkee. Tämä voi auttaa käyttäjiäsi selvittämään, mistä ihmiset puhuvat eniten tällä hetkellä. Mitään aihetunnisteita ei näytetä julkisesti, ennen kuin hyväksyt ne. listable: Voidaan ehdottaa - no_tag_selected: Yhtään tagia ei muutettu, koska yhtään ei valittu + no_tag_selected: Tunnisteita ei muutettu, koska yhtään ei ollut valittuna not_listable: Ei tulla ehdottamaan not_trendable: Ei näy trendien alla not_usable: Ei voida käyttää peaked_on_and_decaying: Saavutti huipun %{date}, nyt hiipuu title: Suositut aihetunnisteet trendable: Voi näkyä trendien alla - trending_rank: 'Nousussa #%{rank}' + trending_rank: 'Suosittua #%{rank}' usable: Voidaan käyttää usage_comparison: Käytetty %{today} kertaa tänään, verrattuna %{yesterday} eiliseen used_by_over_week: one: Yhden henkilön käyttämä viime viikon aikana other: Käyttänyt %{count} henkilöä viimeisen viikon aikana title: Trendit - trending: Nousussa + trending: Suosittua warning_presets: add_new: Lisää uusi delete: Poista @@ -995,8 +995,8 @@ fi: new_trending_statuses: title: Suositut julkaisut new_trending_tags: - no_approved_tags: Tällä hetkellä ei ole hyväksyttyjä trendikkäitä aihetunnisteita. - requirements: 'Mikä tahansa näistä ehdokkaista voisi ylittää #%{rank} hyväksytyn trendikkään aihetunnisteen, joka on tällä hetkellä #%{lowest_tag_name} arvosanalla %{lowest_tag_score}.' + no_approved_tags: Tällä hetkellä ei ole hyväksyttyjä suosittuja aihetunnisteita. + requirements: 'Mikä tahansa näistä ehdokkaista voisi ylittää #%{rank} hyväksytyn suositun aihetunnisteen, joka on tällä hetkellä #%{lowest_tag_name} %{lowest_tag_score} pisteellä.' title: Suositut aihetunnisteet subject: Uusia trendejä tarkistettavaksi instanssissa %{instance} aliases: @@ -1008,7 +1008,7 @@ fi: remove: Poista aliaksen linkitys appearance: advanced_web_interface: Edistynyt selainkäyttöliittymä - advanced_web_interface_hint: 'Jos haluat hyödyntää näytön koko leveyttä, edistyneen webkäyttöliittymän avulla voit määrittää useita erilaisia sarakkeita, niin näet kerralla niin paljon tietoa kuin haluat: kotisyöte, ilmoitukset, yleinen aikajana, mikä tahansa määrä listoja ja aihetunnisteita.' + advanced_web_interface_hint: 'Jos haluat hyödyntää näytön koko leveyttä, edistyneen selainkäyttöliittymän avulla voit määrittää useita erilaisia sarakkeita, niin näet kerralla niin paljon tietoa kuin haluat: kotisyöte, ilmoitukset, yleinen aikajana, mikä tahansa määrä listoja ja aihetunnisteita.' animations_and_accessibility: Animaatiot ja saavutettavuus confirmation_dialogs: Vahvistusvalinnat discovery: Löytäminen @@ -1220,8 +1220,8 @@ fi: featured_tags: add_new: Lisää uusi errors: - limit: Olet jo nostanut esille enimmäismäärän aihetunnisteita - hint_html: "Mitä ovat näkyvillä olevat hashtagit eli aihetunnisteet? Ne ovat näkyvissä julkisessa profiilissasi ja niiden avulla ihmiset voivat selata julkisia viestejäsi nimenomaan näiden aihetunnisteiden alla. Ne auttavat esimerkiksi luovan työn tai pitkäaikaisten projektien seurannassa." + limit: Pidät jo esillä aihetunnisteiden enimmäismäärää + hint_html: "Pidä tärkeimpiä aihetunnisteitasi esillä profiilissasi. Erinomainen työkalu, jolla pidät kirjaa luovista teoksistasi ja pitkäaikaisista projekteistasi. Esillä pitämäsi aihetunnisteet ovat näyttävällä paikalla profiilissasi ja mahdollistavat nopean pääsyn omiin julkaisuihisi." filters: contexts: account: Profiilit @@ -1236,7 +1236,7 @@ fi: statuses_hint_html: Tämä suodatin koskee yksittäisten julkaisujen valintaa riippumatta siitä, vastaavatko ne alla olevia avainsanoja. Tarkista tai poista julkaisut suodattimesta. title: Muokkaa suodatinta errors: - deprecated_api_multiple_keywords: Näitä parametreja ei voi muuttaa tästä sovelluksesta, koska ne koskevat useampaa kuin yhtä suodattimen avainsanaa. Käytä uudempaa sovellusta tai verkkokäyttöliittymää. + deprecated_api_multiple_keywords: Näitä parametreja ei voi muuttaa tästä sovelluksesta, koska ne koskevat useampaa kuin yhtä suodattimen avainsanaa. Käytä uudempaa sovellusta tai selainkäyttöliittymää. invalid_context: Ei sisältöä tai se on virheellinen index: contexts: Suodattimet %{contexts} @@ -1262,7 +1262,7 @@ fi: batch: remove: Poista suodattimista index: - hint: Tämä suodatin koskee yksittäisten julkaisujen valintaa muista kriteereistä riippumatta. Voit lisätä lisää julkaisuja tähän suodattimeen verkkokäyttöliittymästä. + hint: Tämä suodatin koskee yksittäisten julkaisujen valintaa muista kriteereistä riippumatta. Voit lisätä lisää julkaisuja tähän suodattimeen selainkäyttöliittymästä. title: Suodatetut julkaisut generic: all: Kaikki @@ -1291,7 +1291,7 @@ fi: imports: errors: empty: Tyhjä CSV-tiedosto - incompatible_type: Yhteensopimaton valitun tuontilajin kanssa + incompatible_type: Yhteensopimaton valitun tuontityypin kanssa invalid_csv_file: 'Epäkelpo CSV-tiedosto. Virhe: %{error}' over_rows_processing_limit: sisältää yli %{count} riviä too_large: Tiedosto on liian suuri @@ -1332,8 +1332,8 @@ fi: bookmarks: Tuodaan kirjanmerkkejä domain_blocking: Tuodaan estettyjä verkkotunnuksia following: Tuodaan seurattuja tilejä - lists: Listojen tuonti - muting: Tuodaan hiljennettyjä tilejä + lists: Tuodaan listoja + muting: Tuodaan mykistettyjä tilejä type: Tuonnin tyyppi type_groups: constructive: Seuratut ja kirjanmerkit @@ -1341,7 +1341,7 @@ fi: types: blocking: Estoluettelo bookmarks: Kirjanmerkit - domain_blocking: Verkkotunnuksen estoluettelo + domain_blocking: Verkkotunnusten estoluettelo following: Seurattujen luettelo lists: Listat muting: Mykistettyjen luettelo @@ -1437,7 +1437,7 @@ fi: title: Valvonta move_handler: carry_blocks_over_text: Tämä käyttäjä siirtyi paikasta %{acct}, jonka olit estänyt. - carry_mutes_over_text: Tämä käyttäjä siirtyi paikasta %{acct}, jonka mykistit. + carry_mutes_over_text: Tämä käyttäjä siirtyi tililtä %{acct}, jonka olet mykistänyt. copy_account_note_text: 'Tämä käyttäjä siirtyi paikasta %{acct}, tässä olivat aiemmat muistiinpanosi niistä:' navigation: toggle_menu: Avaa/sulje valikko @@ -1626,7 +1626,7 @@ fi: development: Kehitys edit_profile: Muokkaa profiilia export: Vie tietoja - featured_tags: Esiteltävät aihetunnisteet + featured_tags: Esillä pidettävät aihetunnisteet import: Tuo import_and_export: Tuonti ja vienti migrate: Tilin muutto muualle diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index 189f9f8eb..f73667f5c 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -78,11 +78,11 @@ fi: form_admin_settings: activity_api_enabled: Paikallisesti julkaistujen julkaisujen, aktiivisten käyttäjien ja rekisteröitymisten viikoittainen määrä backups_retention_period: Säilytä luodut arkistot määritetyn määrän päiviä. - bootstrap_timeline_accounts: Nämä tilit kiinnitetään uusien käyttäjien suositusten yläpuolelle. + bootstrap_timeline_accounts: Nämä tilit kiinnitetään uusien käyttäjien seuraamissuositusten yläpuolelle. closed_registrations_message: Näkyy, kun ilmoittautuminen on suljettu content_cache_retention_period: Viestit muilta palvelimilta poistetaan määritetyn määrän päiviä jälkeen, kun arvo on asetettu positiiviseksi. Tämä voi olla peruuttamatonta. custom_css: Voit käyttää mukautettuja tyylejä Mastodonin verkkoversiossa. - mascot: Ohittaa kuvituksen edistyneessä käyttöliittymässä. + mascot: Ohittaa kuvituksen edistyneessä selainkäyttöliittymässä. media_cache_retention_period: Ladatut mediatiedostot poistetaan määritetyn määrän päiviä jälkeen, kun arvo on positiivinen ja ladataan uudelleen pyynnöstä. peers_api_enabled: Luettelo verkkotunnuksista, jotka tämä palvelin on kohdannut fediversumissa. Se ei kerro, oletko liitossa tietyn palvelimen kanssa, vaan että palvelimesi on ylipäätään tietoinen siitä. Tätä tietoa käytetään palveluissa, jotka keräävät tilastoja liittoutumisesta yleisellä tasolla. profile_directory: Profiilihakemisto lueteloi kaikki käyttäjät, jotka ovat ilmoittaneet olevansa löydettävissä. @@ -109,7 +109,7 @@ fi: ip_block: comment: Valinnainen. Muista miksi lisäsit tämän säännön. expires_in: IP-osoitteet ovat rajallinen resurssi, joskus niitä jaetaan ja vaihtavat usein omistajaa. Tästä syystä epämääräisiä IP-lohkoja ei suositella. - ip: Kirjoita IPv4- tai IPv6-osoite. Voit estää kokonaisia alueita käyttämällä CIDR-syntaksia. Varo, että et lukitse itseäsi! + ip: Kirjoita IPv4- tai IPv6-osoite. Voit estää kokonaisia alueita käyttämällä CIDR-syntaksia. Varo, että et lukitse itseäsi ulos! severities: no_access: Estä pääsy kaikkiin resursseihin sign_up_block: Uudet kirjautumiset eivät ole mahdollisia @@ -140,7 +140,7 @@ fi: url: Mihin tapahtumat lähetetään labels: account: - discoverable: Nosta profiili ja julkaisut esille löytämisalgoritmeissa + discoverable: Pidä profiiliasi ja julkaisujasi esillä löytämisalgoritmeissa fields: name: Nimike value: Sisältö @@ -163,8 +163,8 @@ fi: disable: Poista kirjautuminen käytöstä none: Älä tee mitään sensitive: Arkaluonteinen - silence: Hiljennä - suspend: Poista käytöstä ja tuhoa käyttäjätunnuksen tiedot peruuttamattomasti + silence: Rajoita + suspend: Jäädytä warning_preset_id: Käytä varoitusmallia announcement: all_day: Koko päivän kestävä tapahtuma @@ -225,7 +225,7 @@ fi: severity: Vakavuus sign_in_token_attempt: Turvakoodi title: Nimi - type: Tuontilaji + type: Tuontityyppi username: Käyttäjänimi username_or_email: Käyttäjänimi tai sähköpostiosoite whole_word: Koko sana @@ -250,7 +250,7 @@ fi: profile_directory: Ota profiilihakemisto käyttöön registrations_mode: Kuka voi rekisteröityä require_invite_text: Vaadi syy liittyä - show_domain_blocks: Näytä domainestot + show_domain_blocks: Näytä verkkotunnusten estot show_domain_blocks_rationale: Näytä miksi verkkotunnukset on estetty site_contact_email: Ota yhteyttä sähköpostilla site_contact_username: Yhteyshenkilön käyttäjänimi @@ -278,7 +278,7 @@ fi: ip: IP-osoite severities: no_access: Estä pääsy - sign_up_block: Estä kirjautumiset + sign_up_block: Estä rekisteröitymiset sign_up_requires_approval: Rajoita rekisteröitymisiä severity: Sääntö notification_emails: From 45bdd60f3c268363c28da6e20144eed6c450a29b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KMY=EF=BC=88=E9=9B=AA=E3=81=82=E3=81=99=E3=81=8B=EF=BC=89?= Date: Fri, 13 Oct 2023 18:15:47 +0900 Subject: [PATCH 17/69] Fix when unfollow a tag, my post also disappears from the home timeline (#27391) --- app/lib/feed_manager.rb | 1 + spec/lib/feed_manager_spec.rb | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index 26e5b78e8..8b7f20811 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -192,6 +192,7 @@ class FeedManager # also tagged with another followed hashtag or from a followed user scope = from_tag.statuses .where(id: timeline_status_ids) + .where.not(account: into_account) .where.not(account: into_account.following) .tagged_with_none(TagFollow.where(account: into_account).pluck(:tag_id)) diff --git a/spec/lib/feed_manager_spec.rb b/spec/lib/feed_manager_spec.rb index 25edaada6..f4dd42f84 100644 --- a/spec/lib/feed_manager_spec.rb +++ b/spec/lib/feed_manager_spec.rb @@ -525,6 +525,44 @@ RSpec.describe FeedManager do end end + describe '#unmerge_tag_from_home' do + let(:receiver) { Fabricate(:account) } + let(:tag) { Fabricate(:tag) } + + it 'leaves a tagged status' do + status = Fabricate(:status) + status.tags << tag + described_class.instance.push_to_home(receiver, status) + + described_class.instance.unmerge_tag_from_home(tag, receiver) + + expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to_not include(status.id.to_s) + end + + it 'remains a tagged status written by receiver\'s followee' do + followee = Fabricate(:account) + receiver.follow!(followee) + + status = Fabricate(:status, account: followee) + status.tags << tag + described_class.instance.push_to_home(receiver, status) + + described_class.instance.unmerge_tag_from_home(tag, receiver) + + expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s) + end + + it 'remains a tagged status written by receiver' do + status = Fabricate(:status, account: receiver) + status.tags << tag + described_class.instance.push_to_home(receiver, status) + + described_class.instance.unmerge_tag_from_home(tag, receiver) + + expect(redis.zrange("feed:home:#{receiver.id}", 0, -1)).to include(status.id.to_s) + end + end + describe '#clear_from_home' do let(:account) { Fabricate(:account) } let(:followed_account) { Fabricate(:account) } From 5c287c4958a2bf87db79064aa0e8a70d71e58b99 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 13 Oct 2023 11:46:25 +0200 Subject: [PATCH 18/69] Update dependency rubocop to v1.57.1 (#27389) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index a47a9d046..0068df461 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -620,7 +620,7 @@ GEM sidekiq (>= 5, < 8) rspec-support (3.12.1) rspec_chunked (0.6) - rubocop (1.57.0) + rubocop (1.57.1) base64 (~> 0.1.1) json (~> 2.3) language_server-protocol (>= 3.17.0) From 77098c6f1c25958960df98a1510b28352a39704f Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Fri, 13 Oct 2023 12:01:33 +0200 Subject: [PATCH 19/69] Delete .github/FUNDING.yml (#27390) --- .github/FUNDING.yml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index be750a5e4..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -patreon: mastodon -open_collective: mastodon -custom: https://sponsor.joinmastodon.org From 4230d1ee0a0752e0259e96e1590f7eb9fb1ebb9b Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Fri, 13 Oct 2023 09:04:37 -0300 Subject: [PATCH 20/69] Migrate to request specs in `/api/v1/timelines/tag/:hashtag` (#25797) --- .../api/v1/timelines/tag_controller_spec.rb | 71 ----------- spec/requests/api/v1/timelines/tag_spec.rb | 112 ++++++++++++++++++ 2 files changed, 112 insertions(+), 71 deletions(-) delete mode 100644 spec/controllers/api/v1/timelines/tag_controller_spec.rb create mode 100644 spec/requests/api/v1/timelines/tag_spec.rb diff --git a/spec/controllers/api/v1/timelines/tag_controller_spec.rb b/spec/controllers/api/v1/timelines/tag_controller_spec.rb deleted file mode 100644 index 1c60798fc..000000000 --- a/spec/controllers/api/v1/timelines/tag_controller_spec.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::Timelines::TagController do - render_views - - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:statuses') } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'GET #show' do - subject do - get :show, params: { id: 'test' } - end - - before do - PostStatusService.new.call(user.account, text: 'It is a #test') - end - - context 'when the instance allows public preview' do - context 'when the user is not authenticated' do - let(:token) { nil } - - it 'returns http success', :aggregate_failures do - subject - - expect(response).to have_http_status(200) - expect(response.headers['Link'].links.size).to eq(2) - end - end - - context 'when the user is authenticated' do - it 'returns http success', :aggregate_failures do - subject - - expect(response).to have_http_status(200) - expect(response.headers['Link'].links.size).to eq(2) - end - end - end - - context 'when the instance does not allow public preview' do - before do - Form::AdminSettings.new(timeline_preview: false).save - end - - context 'when the user is not authenticated' do - let(:token) { nil } - - it 'returns http unauthorized' do - subject - - expect(response).to have_http_status(401) - end - end - - context 'when the user is authenticated' do - it 'returns http success', :aggregate_failures do - subject - - expect(response).to have_http_status(200) - expect(response.headers['Link'].links.size).to eq(2) - end - end - end - end -end diff --git a/spec/requests/api/v1/timelines/tag_spec.rb b/spec/requests/api/v1/timelines/tag_spec.rb new file mode 100644 index 000000000..a118af13e --- /dev/null +++ b/spec/requests/api/v1/timelines/tag_spec.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Tag' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'read:statuses' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + shared_examples 'a successful request to the tag timeline' do + it 'returns the expected statuses', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json.pluck(:id)).to match_array(expected_statuses.map { |status| status.id.to_s }) + end + end + + describe 'GET /api/v1/timelines/tag/:hashtag' do + subject do + get "/api/v1/timelines/tag/#{hashtag}", headers: headers, params: params + end + + let(:account) { Fabricate(:account) } + let!(:private_status) { PostStatusService.new.call(account, visibility: :private, text: '#life could be a dream') } # rubocop:disable RSpec/LetSetup + let!(:life_status) { PostStatusService.new.call(account, text: 'tell me what is my #life without your #love') } + let!(:war_status) { PostStatusService.new.call(user.account, text: '#war, war never changes') } + let!(:love_status) { PostStatusService.new.call(account, text: 'what is #love?') } + let(:params) { {} } + let(:hashtag) { 'life' } + + context 'when given only one hashtag' do + let(:expected_statuses) { [life_status] } + + it_behaves_like 'a successful request to the tag timeline' + end + + context 'with any param' do + let(:expected_statuses) { [life_status, love_status] } + let(:params) { { any: %(love) } } + + it_behaves_like 'a successful request to the tag timeline' + end + + context 'with all param' do + let(:expected_statuses) { [life_status] } + let(:params) { { all: %w(love) } } + + it_behaves_like 'a successful request to the tag timeline' + end + + context 'with none param' do + let(:expected_statuses) { [war_status] } + let(:hashtag) { 'war' } + let(:params) { { none: %w(life love) } } + + it_behaves_like 'a successful request to the tag timeline' + end + + context 'with limit param' do + let(:hashtag) { 'love' } + let(:params) { { limit: 1 } } + + it 'returns only the requested number of statuses' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + + it 'sets the correct pagination headers', :aggregate_failures do + subject + + headers = response.headers['Link'] + + expect(headers.find_link(%w(rel prev)).href).to eq(api_v1_timelines_tag_url(limit: 1, min_id: love_status.id.to_s)) + expect(headers.find_link(%w(rel next)).href).to eq(api_v1_timelines_tag_url(limit: 1, max_id: love_status.id.to_s)) + end + end + + context 'when the instance allows public preview' do + context 'when the user is not authenticated' do + let(:headers) { {} } + let(:expected_statuses) { [life_status] } + + it_behaves_like 'a successful request to the tag timeline' + end + end + + context 'when the instance does not allow public preview' do + before do + Form::AdminSettings.new(timeline_preview: false).save + end + + context 'when the user is not authenticated' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + + context 'when the user is authenticated' do + let(:expected_statuses) { [life_status] } + + it_behaves_like 'a successful request to the tag timeline' + end + end + end +end From 7664e3b69207a221b036e3afb84b897ab9078e6a Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Fri, 13 Oct 2023 09:08:21 -0300 Subject: [PATCH 21/69] Migrate to request specs in `/api/v1/statuses/:status_id/source` (#25634) --- .../v1/statuses/sources_controller_spec.rb | 29 -------- spec/requests/api/v1/statuses/sources_spec.rb | 72 +++++++++++++++++++ 2 files changed, 72 insertions(+), 29 deletions(-) delete mode 100644 spec/controllers/api/v1/statuses/sources_controller_spec.rb create mode 100644 spec/requests/api/v1/statuses/sources_spec.rb diff --git a/spec/controllers/api/v1/statuses/sources_controller_spec.rb b/spec/controllers/api/v1/statuses/sources_controller_spec.rb deleted file mode 100644 index fbe6fa0be..000000000 --- a/spec/controllers/api/v1/statuses/sources_controller_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::Statuses::SourcesController do - render_views - - let(:user) { Fabricate(:user) } - 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) } - - context 'with an oauth token' do - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'GET #show' do - let(:status) { Fabricate(:status, account: user.account) } - - before do - get :show, params: { status_id: status.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - end - end -end diff --git a/spec/requests/api/v1/statuses/sources_spec.rb b/spec/requests/api/v1/statuses/sources_spec.rb new file mode 100644 index 000000000..723b81905 --- /dev/null +++ b/spec/requests/api/v1/statuses/sources_spec.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Sources' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'read:statuses' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/statuses/:status_id/source' do + subject do + get "/api/v1/statuses/#{status.id}/source", headers: headers + end + + let(:status) { Fabricate(:status) } + + it_behaves_like 'forbidden for wrong scope', 'write write:statuses' + + context 'with public status' do + it 'returns the source properties of the status', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to eq({ + id: status.id.to_s, + text: status.text, + spoiler_text: status.spoiler_text, + }) + end + end + + context 'with private status of non-followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'with private status of followed account' do + let(:status) { Fabricate(:status, visibility: :private) } + + before do + user.account.follow!(status.account) + end + + it 'returns the source properties of the status', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to eq({ + id: status.id.to_s, + text: status.text, + spoiler_text: status.spoiler_text, + }) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end +end From ecdb31d4799422245f73d74b280bc8733081a6d7 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Fri, 13 Oct 2023 09:19:12 -0300 Subject: [PATCH 22/69] Migrate to request specs in `/api/v1/favourites` (#25518) --- .../api/v1/favourites_controller_spec.rb | 80 ------------------- spec/requests/api/v1/favourites_spec.rb | 71 ++++++++++++++++ 2 files changed, 71 insertions(+), 80 deletions(-) delete mode 100644 spec/controllers/api/v1/favourites_controller_spec.rb create mode 100644 spec/requests/api/v1/favourites_spec.rb diff --git a/spec/controllers/api/v1/favourites_controller_spec.rb b/spec/controllers/api/v1/favourites_controller_spec.rb deleted file mode 100644 index c9ca046be..000000000 --- a/spec/controllers/api/v1/favourites_controller_spec.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Api::V1::FavouritesController do - render_views - - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') } - - describe 'GET #index' do - context 'without token' do - it 'returns http unauthorized' do - get :index - expect(response).to have_http_status 401 - end - end - - context 'with token' do - context 'without read scope' do - before do - allow(controller).to receive(:doorkeeper_token) do - Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: '') - end - end - - it 'returns http forbidden' do - get :index - expect(response).to have_http_status 403 - end - end - - context 'without valid resource owner' do - before do - token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read') - user.destroy! - - allow(controller).to receive(:doorkeeper_token) { token } - end - - it 'returns http unprocessable entity' do - get :index - expect(response).to have_http_status 422 - end - end - - context 'with read scope and valid resource owner' do - before do - allow(controller).to receive(:doorkeeper_token) do - Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:favourites') - end - end - - it 'shows favourites owned by the user' do - favourite_by_user = Fabricate(:favourite, account: user.account) - favourite_by_others = Fabricate(:favourite) - - get :index - - expect(assigns(:statuses)).to contain_exactly(favourite_by_user.status) - end - - it 'adds pagination headers if necessary' do - favourite = Fabricate(:favourite, account: user.account) - - get :index, params: { limit: 1 } - - expect(response.headers['Link'].find_link(%w(rel next)).href).to eq "http://test.host/api/v1/favourites?limit=1&max_id=#{favourite.id}" - expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq "http://test.host/api/v1/favourites?limit=1&min_id=#{favourite.id}" - end - - it 'does not add pagination headers if not necessary' do - get :index - - expect(response.headers['Link']).to be_nil - end - end - end - end -end diff --git a/spec/requests/api/v1/favourites_spec.rb b/spec/requests/api/v1/favourites_spec.rb new file mode 100644 index 000000000..713990592 --- /dev/null +++ b/spec/requests/api/v1/favourites_spec.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Favourites' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:favourites' } + let(:headers) { { Authorization: "Bearer #{token.token}" } } + + describe 'GET /api/v1/favourites' do + subject do + get '/api/v1/favourites', headers: headers, params: params + end + + let(:params) { {} } + let!(:favourites) { Fabricate.times(3, :favourite, account: user.account) } + + let(:expected_response) do + favourites.map do |favourite| + a_hash_including(id: favourite.status.id.to_s, account: a_hash_including(id: favourite.status.account.id.to_s)) + end + end + + it_behaves_like 'forbidden for wrong scope', 'write' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the favourites' do + subject + + expect(body_as_json).to match_array(expected_response) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of favourites' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + + it 'sets the correct pagination header for the prev path' do + subject + + expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_favourites_url(limit: params[:limit], min_id: favourites.last.id)) + end + + it 'sets the correct pagination header for the next path' do + subject + + expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_favourites_url(limit: params[:limit], max_id: favourites[1].id)) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http unauthorized' do + subject + + expect(response).to have_http_status(401) + end + end + end +end From fd9dea21d0da9d5c4184efca8f66cc2ffc5435f6 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 13 Oct 2023 08:42:09 -0400 Subject: [PATCH 23/69] DB speedup in `API::` controller/request specs (#25516) --- .../controllers/api/oembed_controller_spec.rb | 5 +- .../accounts/credentials_controller_spec.rb | 6 +- .../follower_accounts_controller_spec.rb | 10 +-- .../following_accounts_controller_spec.rb | 10 +-- .../api/v1/accounts/notes_controller_spec.rb | 18 ++--- .../api/v1/accounts/pins_controller_spec.rb | 14 +--- .../accounts/relationships_controller_spec.rb | 16 ++-- .../v1/accounts/statuses_controller_spec.rb | 14 +--- .../api/v1/accounts_controller_spec.rb | 80 +++---------------- .../api/v1/admin/accounts_controller_spec.rb | 38 ++------- .../reactions_controller_spec.rb | 10 +-- .../api/v1/announcements_controller_spec.rb | 5 +- .../api/v1/blocks_controller_spec.rb | 21 ++--- .../api/v1/conversations_controller_spec.rb | 11 +-- .../api/v1/filters_controller_spec.rb | 26 ++---- .../translation_languages_controller_spec.rb | 4 +- .../api/v1/lists/accounts_controller_spec.rb | 20 +---- .../api/v1/markers_controller_spec.rb | 17 +--- .../api/v1/media_controller_spec.rb | 31 +------ .../api/v1/polls/votes_controller_spec.rb | 7 +- .../api/v1/reports_controller_spec.rb | 16 +--- .../api/v1/statuses/mutes_controller_spec.rb | 10 +-- .../reblogged_by_accounts_controller_spec.rb | 6 +- .../v1/statuses/reblogs_controller_spec.rb | 24 +----- .../api/v1/statuses_controller_spec.rb | 57 ++++--------- .../api/v2/admin/accounts_controller_spec.rb | 10 +-- .../v2/filters/keywords_controller_spec.rb | 18 +---- .../v2/filters/statuses_controller_spec.rb | 14 +--- .../api/v1/admin/account_actions_spec.rb | 28 +------ .../v1/admin/canonical_email_blocks_spec.rb | 49 ++---------- .../api/v1/admin/domain_allows_spec.rb | 26 +----- .../api/v1/admin/domain_blocks_spec.rb | 41 ++-------- .../api/v1/admin/email_domain_blocks_spec.rb | 26 +----- spec/requests/api/v1/admin/ip_blocks_spec.rb | 33 +------- spec/requests/api/v1/admin/reports_spec.rb | 55 +++---------- spec/requests/api/v1/apps/credentials_spec.rb | 6 +- spec/requests/api/v1/apps_spec.rb | 18 +---- spec/requests/api/v1/domain_blocks_spec.rb | 21 +---- spec/requests/api/v1/follow_requests_spec.rb | 33 +------- spec/requests/api/v1/lists_spec.rb | 40 ++-------- spec/requests/api/v1/tags_spec.rb | 33 +------- 41 files changed, 181 insertions(+), 746 deletions(-) diff --git a/spec/controllers/api/oembed_controller_spec.rb b/spec/controllers/api/oembed_controller_spec.rb index 70248c398..5f0ca560d 100644 --- a/spec/controllers/api/oembed_controller_spec.rb +++ b/spec/controllers/api/oembed_controller_spec.rb @@ -14,11 +14,8 @@ RSpec.describe Api::OEmbedController do get :show, params: { url: short_account_status_url(alice, status) }, format: :json end - it 'returns http success' do + it 'returns private cache control headers', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'returns private cache control headers' do expect(response.headers['Cache-Control']).to include('private, no-store') end end diff --git a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb index b5d5c37a9..a62fa54e6 100644 --- a/spec/controllers/api/v1/accounts/credentials_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/credentials_controller_spec.rb @@ -41,11 +41,9 @@ describe Api::V1::Accounts::CredentialsController do } end - it 'returns http success' do + it 'updates account info', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'updates account info' do user.reload user.account.reload @@ -55,9 +53,7 @@ describe Api::V1::Accounts::CredentialsController do expect(user.account.header).to exist expect(user.setting_default_privacy).to eq('unlisted') expect(user.setting_default_sensitive).to be(true) - end - it 'queues up an account update distribution' do expect(ActivityPub::UpdateDistributionWorker).to have_received(:perform_async).with(user.account_id) end end diff --git a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb index 7a387f326..510a47566 100644 --- a/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/follower_accounts_controller_spec.rb @@ -18,23 +18,19 @@ describe Api::V1::Accounts::FollowerAccountsController do end describe 'GET #index' do - it 'returns http success' do + it 'returns accounts following the given account', :aggregate_failures do get :index, params: { account_id: account.id, limit: 2 } expect(response).to have_http_status(200) - end - - it 'returns accounts following the given account' do - get :index, params: { account_id: account.id, limit: 2 } - expect(body_as_json.size).to eq 2 expect([body_as_json[0][:id], body_as_json[1][:id]]).to contain_exactly(alice.id.to_s, bob.id.to_s) end - it 'does not return blocked users' do + it 'does not return blocked users', :aggregate_failures do user.account.block!(bob) get :index, params: { account_id: account.id, limit: 2 } + expect(response).to have_http_status(200) expect(body_as_json.size).to eq 1 expect(body_as_json[0][:id]).to eq alice.id.to_s end diff --git a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb index b69b0bd39..a7d07a6be 100644 --- a/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/following_accounts_controller_spec.rb @@ -18,23 +18,19 @@ describe Api::V1::Accounts::FollowingAccountsController do end describe 'GET #index' do - it 'returns http success' do + it 'returns accounts followed by the given account', :aggregate_failures do get :index, params: { account_id: account.id, limit: 2 } expect(response).to have_http_status(200) - end - - it 'returns accounts followed by the given account' do - get :index, params: { account_id: account.id, limit: 2 } - expect(body_as_json.size).to eq 2 expect([body_as_json[0][:id], body_as_json[1][:id]]).to contain_exactly(alice.id.to_s, bob.id.to_s) end - it 'does not return blocked users' do + it 'does not return blocked users', :aggregate_failures do user.account.block!(bob) get :index, params: { account_id: account.id, limit: 2 } + expect(response).to have_http_status(200) expect(body_as_json.size).to eq 1 expect(body_as_json[0][:id]).to eq alice.id.to_s end diff --git a/spec/controllers/api/v1/accounts/notes_controller_spec.rb b/spec/controllers/api/v1/accounts/notes_controller_spec.rb index 4107105af..75599b32b 100644 --- a/spec/controllers/api/v1/accounts/notes_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/notes_controller_spec.rb @@ -19,30 +19,24 @@ describe Api::V1::Accounts::NotesController do post :create, params: { account_id: account.id, comment: comment } end - context 'when account note has reasonable length' do + context 'when account note has reasonable length', :aggregate_failures do let(:comment) { 'foo' } - it 'returns http success' do - subject - expect(response).to have_http_status(200) - end - it 'updates account note' do subject + + expect(response).to have_http_status(200) expect(AccountNote.find_by(account_id: user.account.id, target_account_id: account.id).comment).to eq comment end end - context 'when account note exceeds allowed length' do + context 'when account note exceeds allowed length', :aggregate_failures do let(:comment) { 'a' * 2_001 } - it 'returns 422' do - subject - expect(response).to have_http_status(422) - end - it 'does not create account note' do subject + + expect(response).to have_http_status(422) expect(AccountNote.where(account_id: user.account.id, target_account_id: account.id)).to_not exist end end diff --git a/spec/controllers/api/v1/accounts/pins_controller_spec.rb b/spec/controllers/api/v1/accounts/pins_controller_spec.rb index b4aa9b711..36f525e75 100644 --- a/spec/controllers/api/v1/accounts/pins_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/pins_controller_spec.rb @@ -15,14 +15,11 @@ RSpec.describe Api::V1::Accounts::PinsController do describe 'POST #create' do subject { post :create, params: { account_id: kevin.account.id } } - it 'returns 200' do - expect(response).to have_http_status(200) - end - - it 'creates account_pin' do + it 'creates account_pin', :aggregate_failures do expect do subject end.to change { AccountPin.where(account: john.account, target_account: kevin.account).count }.by(1) + expect(response).to have_http_status(200) end end @@ -33,14 +30,11 @@ RSpec.describe Api::V1::Accounts::PinsController do Fabricate(:account_pin, account: john.account, target_account: kevin.account) end - it 'returns 200' do - expect(response).to have_http_status(200) - end - - it 'destroys account_pin' do + it 'destroys account_pin', :aggregate_failures do expect do subject end.to change { AccountPin.where(account: john.account, target_account: kevin.account).count }.by(-1) + expect(response).to have_http_status(200) end end end diff --git a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb index 993ead636..5ba6f2a1f 100644 --- a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb @@ -26,13 +26,10 @@ describe Api::V1::Accounts::RelationshipsController do get :index, params: { id: simon.id } end - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns JSON with correct data' do + it 'returns JSON with correct data', :aggregate_failures do json = body_as_json + expect(response).to have_http_status(200) expect(json).to be_a Enumerable expect(json.first[:following]).to be true expect(json.first[:followed_by]).to be false @@ -51,11 +48,14 @@ describe Api::V1::Accounts::RelationshipsController do context 'when there is returned JSON data' do let(:json) { body_as_json } - it 'returns an enumerable json' do + it 'returns an enumerable json with correct elements', :aggregate_failures do expect(json).to be_a Enumerable + + expect_simon_item_one + expect_lewis_item_two end - it 'returns a correct first element' do + def expect_simon_item_one expect(json.first[:id]).to eq simon.id.to_s expect(json.first[:following]).to be true expect(json.first[:showing_reblogs]).to be true @@ -65,7 +65,7 @@ describe Api::V1::Accounts::RelationshipsController do expect(json.first[:domain_blocking]).to be false end - it 'returns a correct second element' do + def expect_lewis_item_two expect(json.second[:id]).to eq lewis.id.to_s expect(json.second[:following]).to be false expect(json.second[:showing_reblogs]).to be false diff --git a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb index cb62afcf9..0e4fa9301 100644 --- a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb @@ -14,15 +14,10 @@ describe Api::V1::Accounts::StatusesController do end describe 'GET #index' do - it 'returns http success' do + it 'returns expected headers', :aggregate_failures do get :index, params: { account_id: user.account.id, limit: 1 } expect(response).to have_http_status(200) - end - - it 'returns expected headers' do - get :index, params: { account_id: user.account.id, limit: 1 } - expect(response.headers['Link'].links.size).to eq(2) end @@ -44,14 +39,11 @@ describe Api::V1::Accounts::StatusesController do get :index, params: { account_id: user.account.id, exclude_replies: true } end - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns posts along with self replies' do + it 'returns posts along with self replies', :aggregate_failures do json = body_as_json post_ids = json.map { |item| item[:id].to_i }.sort + expect(response).to have_http_status(200) expect(post_ids).to eq [status.id, status_self_reply.id] end end diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb index 0daec691a..9d0bb73c7 100644 --- a/spec/controllers/api/v1/accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts_controller_spec.rb @@ -25,15 +25,10 @@ RSpec.describe Api::V1::AccountsController do context 'when given truthy agreement' do let(:agreement) { 'true' } - it 'returns http success' do + it 'creates a user', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'returns a new access token as JSON' do expect(body_as_json[:access_token]).to_not be_blank - end - it 'creates a user' do user = User.find_by(email: 'hello@world.tld') expect(user).to_not be_nil expect(user.created_by_application_id).to eq app.id @@ -59,18 +54,14 @@ RSpec.describe Api::V1::AccountsController do context 'with unlocked account' do let(:locked) { false } - it 'returns http success' do + it 'creates a following relation between user and target user', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'returns JSON with following=true and requested=false' do json = body_as_json expect(json[:following]).to be true expect(json[:requested]).to be false - end - it 'creates a following relation between user and target user' do expect(user.account.following?(other_account)).to be true end @@ -80,18 +71,14 @@ RSpec.describe Api::V1::AccountsController do context 'with locked account' do let(:locked) { true } - it 'returns http success' do + it 'creates a follow request relation between user and target user', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'returns JSON with following=false and requested=true' do json = body_as_json expect(json[:following]).to be false expect(json[:requested]).to be true - end - it 'creates a follow request relation between user and target user' do expect(user.account.requested?(other_account)).to be true end @@ -148,11 +135,8 @@ RSpec.describe Api::V1::AccountsController do post :unfollow, params: { id: other_account.id } end - it 'returns http success' do + it 'removes the following relation between user and target user', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'removes the following relation between user and target user' do expect(user.account.following?(other_account)).to be false end @@ -168,11 +152,8 @@ RSpec.describe Api::V1::AccountsController do post :remove_from_followers, params: { id: other_account.id } end - it 'returns http success' do + it 'removes the followed relation between user and target user', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'removes the followed relation between user and target user' do expect(user.account.followed_by?(other_account)).to be false end @@ -188,15 +169,9 @@ RSpec.describe Api::V1::AccountsController do post :block, params: { id: other_account.id } end - it 'returns http success' do + it 'creates a blocking relation', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'removes the following relation between user and target user' do expect(user.account.following?(other_account)).to be false - end - - it 'creates a blocking relation' do expect(user.account.blocking?(other_account)).to be true end @@ -212,11 +187,8 @@ RSpec.describe Api::V1::AccountsController do post :unblock, params: { id: other_account.id } end - it 'returns http success' do + it 'removes the blocking relation between user and target user', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'removes the blocking relation between user and target user' do expect(user.account.blocking?(other_account)).to be false end @@ -232,19 +204,10 @@ RSpec.describe Api::V1::AccountsController do post :mute, params: { id: other_account.id } end - it 'returns http success' do + it 'mutes notifications', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'does not remove the following relation between user and target user' do expect(user.account.following?(other_account)).to be true - end - - it 'creates a muting relation' do expect(user.account.muting?(other_account)).to be true - end - - it 'mutes notifications' do expect(user.account.muting_notifications?(other_account)).to be true end @@ -260,19 +223,10 @@ RSpec.describe Api::V1::AccountsController do post :mute, params: { id: other_account.id, notifications: false } end - it 'returns http success' do + it 'does not mute notifications', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'does not remove the following relation between user and target user' do expect(user.account.following?(other_account)).to be true - end - - it 'creates a muting relation' do expect(user.account.muting?(other_account)).to be true - end - - it 'does not mute notifications' do expect(user.account.muting_notifications?(other_account)).to be false end @@ -288,19 +242,10 @@ RSpec.describe Api::V1::AccountsController do post :mute, params: { id: other_account.id, duration: 300 } end - it 'returns http success' do + it 'mutes notifications', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'does not remove the following relation between user and target user' do expect(user.account.following?(other_account)).to be true - end - - it 'creates a muting relation' do expect(user.account.muting?(other_account)).to be true - end - - it 'mutes notifications' do expect(user.account.muting_notifications?(other_account)).to be true end @@ -316,11 +261,8 @@ RSpec.describe Api::V1::AccountsController do post :unmute, params: { id: other_account.id } end - it 'returns http success' do + it 'removes the muting relation between user and target user', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'removes the muting relation between user and target user' do expect(user.account.muting?(other_account)).to be false end diff --git a/spec/controllers/api/v1/admin/accounts_controller_spec.rb b/spec/controllers/api/v1/admin/accounts_controller_spec.rb index 36f6e398c..4b56b2547 100644 --- a/spec/controllers/api/v1/admin/accounts_controller_spec.rb +++ b/spec/controllers/api/v1/admin/accounts_controller_spec.rb @@ -44,11 +44,9 @@ RSpec.describe Api::V1::Admin::AccountsController do context "when called with #{params.inspect}" do let(:params) { params } - it 'returns http success' do + it "returns the correct accounts (#{expected_results.inspect})", :aggregate_failures do expect(response).to have_http_status(200) - end - it "returns the correct accounts (#{expected_results.inspect})" do json = body_as_json expect(json.map { |a| a[:id].to_i }).to eq(expected_results.map { |symbol| send(symbol).id }) @@ -79,15 +77,10 @@ RSpec.describe Api::V1::Admin::AccountsController do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do + it 'approves user', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'approves user' do expect(account.reload.user_approved?).to be true - end - it 'logs action' do log_item = Admin::ActionLog.last expect(log_item).to_not be_nil @@ -106,15 +99,10 @@ RSpec.describe Api::V1::Admin::AccountsController do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do + it 'removes user', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'removes user' do expect(User.where(id: account.user.id).count).to eq 0 - end - it 'logs action' do log_item = Admin::ActionLog.last expect(log_item).to_not be_nil @@ -133,11 +121,8 @@ RSpec.describe Api::V1::Admin::AccountsController do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do + it 'enables user', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'enables user' do expect(account.reload.user_disabled?).to be false end end @@ -151,11 +136,8 @@ RSpec.describe Api::V1::Admin::AccountsController do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do + it 'unsuspends account', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'unsuspends account' do expect(account.reload.suspended?).to be false end end @@ -169,11 +151,8 @@ RSpec.describe Api::V1::Admin::AccountsController do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do + it 'unsensitizes account', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'unsensitizes account' do expect(account.reload.sensitized?).to be false end end @@ -187,11 +166,8 @@ RSpec.describe Api::V1::Admin::AccountsController do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do + it 'unsilences account', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'unsilences account' do expect(account.reload.silenced?).to be false end end diff --git a/spec/controllers/api/v1/announcements/reactions_controller_spec.rb b/spec/controllers/api/v1/announcements/reactions_controller_spec.rb index 10aaa553f..c1debc33f 100644 --- a/spec/controllers/api/v1/announcements/reactions_controller_spec.rb +++ b/spec/controllers/api/v1/announcements/reactions_controller_spec.rb @@ -25,11 +25,8 @@ RSpec.describe Api::V1::Announcements::ReactionsController do put :update, params: { announcement_id: announcement.id, id: '😂' } end - it 'returns http success' do + it 'creates reaction', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'creates reaction' do expect(announcement.announcement_reactions.find_by(name: '😂', account: user.account)).to_not be_nil end end @@ -53,11 +50,8 @@ RSpec.describe Api::V1::Announcements::ReactionsController do delete :destroy, params: { announcement_id: announcement.id, id: '😂' } end - it 'returns http success' do + it 'creates reaction', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'creates reaction' do expect(announcement.announcement_reactions.find_by(name: '😂', account: user.account)).to be_nil end end diff --git a/spec/controllers/api/v1/announcements_controller_spec.rb b/spec/controllers/api/v1/announcements_controller_spec.rb index 15d94b451..95ce8fd9f 100644 --- a/spec/controllers/api/v1/announcements_controller_spec.rb +++ b/spec/controllers/api/v1/announcements_controller_spec.rb @@ -47,11 +47,8 @@ RSpec.describe Api::V1::AnnouncementsController do post :dismiss, params: { id: announcement.id } end - it 'returns http success' do + it 'dismisses announcement', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'dismisses announcement' do expect(announcement.announcement_mutes.find_by(account: user.account)).to_not be_nil end end diff --git a/spec/controllers/api/v1/blocks_controller_spec.rb b/spec/controllers/api/v1/blocks_controller_spec.rb index eaafc1b4f..ba63560a9 100644 --- a/spec/controllers/api/v1/blocks_controller_spec.rb +++ b/spec/controllers/api/v1/blocks_controller_spec.rb @@ -12,45 +12,48 @@ RSpec.describe Api::V1::BlocksController do before { allow(controller).to receive(:doorkeeper_token) { token } } describe 'GET #index' do - it 'limits according to limit parameter' do + it 'limits according to limit parameter', :aggregate_failures do Array.new(2) { Fabricate(:block, account: user.account) } get :index, params: { limit: 1 } + + expect(response).to have_http_status(200) expect(body_as_json.size).to eq 1 end - it 'queries blocks in range according to max_id' do + it 'queries blocks in range according to max_id', :aggregate_failures do blocks = Array.new(2) { Fabricate(:block, account: user.account) } get :index, params: { max_id: blocks[1] } + expect(response).to have_http_status(200) expect(body_as_json.size).to eq 1 expect(body_as_json[0][:id]).to eq blocks[0].target_account_id.to_s end - it 'queries blocks in range according to since_id' do + it 'queries blocks in range according to since_id', :aggregate_failures do blocks = Array.new(2) { Fabricate(:block, account: user.account) } get :index, params: { since_id: blocks[0] } + expect(response).to have_http_status(200) expect(body_as_json.size).to eq 1 expect(body_as_json[0][:id]).to eq blocks[1].target_account_id.to_s end - it 'sets pagination header for next path' do + it 'sets pagination header for next path', :aggregate_failures do blocks = Array.new(2) { Fabricate(:block, account: user.account) } get :index, params: { limit: 1, since_id: blocks[0] } + + expect(response).to have_http_status(200) expect(response.headers['Link'].find_link(%w(rel next)).href).to eq api_v1_blocks_url(limit: 1, max_id: blocks[1]) end - it 'sets pagination header for previous path' do + it 'sets pagination header for previous path', :aggregate_failures do block = Fabricate(:block, account: user.account) get :index - expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq api_v1_blocks_url(since_id: block) - end - it 'returns http success' do - get :index expect(response).to have_http_status(200) + expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq api_v1_blocks_url(since_id: block) end context 'with wrong scopes' do diff --git a/spec/controllers/api/v1/conversations_controller_spec.rb b/spec/controllers/api/v1/conversations_controller_spec.rb index 28d7c7f3a..50e2a62ef 100644 --- a/spec/controllers/api/v1/conversations_controller_spec.rb +++ b/spec/controllers/api/v1/conversations_controller_spec.rb @@ -21,17 +21,14 @@ RSpec.describe Api::V1::ConversationsController do PostStatusService.new.call(user.account, text: 'Hey, nobody here', visibility: 'direct') end - it 'returns http success' do - get :index - expect(response).to have_http_status(200) - end - - it 'returns pagination headers' do + it 'returns pagination headers', :aggregate_failures do get :index, params: { limit: 1 } + + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) end - it 'returns conversations' do + it 'returns conversations', :aggregate_failures do get :index json = body_as_json expect(json.size).to eq 2 diff --git a/spec/controllers/api/v1/filters_controller_spec.rb b/spec/controllers/api/v1/filters_controller_spec.rb index 8ccd2f4d6..8d5408cf5 100644 --- a/spec/controllers/api/v1/filters_controller_spec.rb +++ b/spec/controllers/api/v1/filters_controller_spec.rb @@ -31,12 +31,10 @@ RSpec.describe Api::V1::FiltersController do post :create, params: { phrase: 'magic', context: %w(home), irreversible: irreversible, whole_word: whole_word } end - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'creates a filter' do + it 'creates a filter', :aggregate_failures do filter = user.account.custom_filters.first + + expect(response).to have_http_status(200) expect(filter).to_not be_nil expect(filter.keywords.pluck(:keyword, :whole_word)).to eq [['magic', whole_word]] expect(filter.context).to eq %w(home) @@ -48,12 +46,10 @@ RSpec.describe Api::V1::FiltersController do let(:irreversible) { false } let(:whole_word) { true } - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'creates a filter' do + it 'creates a filter', :aggregate_failures do filter = user.account.custom_filters.first + + expect(response).to have_http_status(200) expect(filter).to_not be_nil expect(filter.keywords.pluck(:keyword, :whole_word)).to eq [['magic', whole_word]] expect(filter.context).to eq %w(home) @@ -83,11 +79,8 @@ RSpec.describe Api::V1::FiltersController do put :update, params: { id: keyword.id, phrase: 'updated' } end - it 'returns http success' do + it 'updates the filter', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'updates the filter' do expect(keyword.reload.phrase).to eq 'updated' end end @@ -101,11 +94,8 @@ RSpec.describe Api::V1::FiltersController do delete :destroy, params: { id: keyword.id } end - it 'returns http success' do + it 'removes the filter', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'removes the filter' do expect { keyword.reload }.to raise_error ActiveRecord::RecordNotFound end end diff --git a/spec/controllers/api/v1/instances/translation_languages_controller_spec.rb b/spec/controllers/api/v1/instances/translation_languages_controller_spec.rb index 88bcc4034..f79687df6 100644 --- a/spec/controllers/api/v1/instances/translation_languages_controller_spec.rb +++ b/spec/controllers/api/v1/instances/translation_languages_controller_spec.rb @@ -5,7 +5,7 @@ require 'rails_helper' describe Api::V1::Instances::TranslationLanguagesController do describe 'GET #show' do context 'when no translation service is configured' do - it 'returns empty language matrix' do + it 'returns empty language matrix', :aggregate_failures do get :show expect(response).to have_http_status(200) @@ -19,7 +19,7 @@ describe Api::V1::Instances::TranslationLanguagesController do allow(TranslationService).to receive_messages(configured?: true, configured: service) end - it 'returns language matrix' do + it 'returns language matrix', :aggregate_failures do get :show expect(response).to have_http_status(200) diff --git a/spec/controllers/api/v1/lists/accounts_controller_spec.rb b/spec/controllers/api/v1/lists/accounts_controller_spec.rb index d4550dd76..21e155a50 100644 --- a/spec/controllers/api/v1/lists/accounts_controller_spec.rb +++ b/spec/controllers/api/v1/lists/accounts_controller_spec.rb @@ -35,11 +35,8 @@ describe Api::V1::Lists::AccountsController do post :create, params: { list_id: list.id, account_ids: [bob.id] } end - it 'returns http success' do + it 'adds account to the list', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'adds account to the list' do expect(list.accounts.include?(bob)).to be true end end @@ -50,11 +47,8 @@ describe Api::V1::Lists::AccountsController do post :create, params: { list_id: list.id, account_ids: [bob.id] } end - it 'returns http success' do + it 'adds account to the list', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'adds account to the list' do expect(list.accounts.include?(bob)).to be true end end @@ -64,11 +58,8 @@ describe Api::V1::Lists::AccountsController do post :create, params: { list_id: list.id, account_ids: [bob.id] } end - it 'returns http not found' do + it 'does not add the account to the list', :aggregate_failures do expect(response).to have_http_status(404) - end - - it 'does not add the account to the list' do expect(list.accounts.include?(bob)).to be false end end @@ -81,11 +72,8 @@ describe Api::V1::Lists::AccountsController do delete :destroy, params: { list_id: list.id, account_ids: [list.accounts.first.id] } end - it 'returns http success' do + it 'removes account from the list', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'removes account from the list' do expect(list.accounts.count).to eq 0 end end diff --git a/spec/controllers/api/v1/markers_controller_spec.rb b/spec/controllers/api/v1/markers_controller_spec.rb index 64e9dcafb..e954bbd1b 100644 --- a/spec/controllers/api/v1/markers_controller_spec.rb +++ b/spec/controllers/api/v1/markers_controller_spec.rb @@ -18,13 +18,10 @@ RSpec.describe Api::V1::MarkersController do get :index, params: { timeline: %w(home notifications) } end - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns markers' do + it 'returns markers', :aggregate_failures do json = body_as_json + expect(response).to have_http_status(200) expect(json.key?(:home)).to be true expect(json[:home][:last_read_id]).to eq '123' expect(json.key?(:notifications)).to be true @@ -38,11 +35,8 @@ RSpec.describe Api::V1::MarkersController do post :create, params: { home: { last_read_id: '69420' } } end - it 'returns http success' do + it 'creates a marker', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'creates a marker' do expect(user.markers.first.timeline).to eq 'home' expect(user.markers.first.last_read_id).to eq 69_420 end @@ -54,11 +48,8 @@ RSpec.describe Api::V1::MarkersController do post :create, params: { home: { last_read_id: '70120' } } end - it 'returns http success' do + it 'updates a marker', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'updates a marker' do expect(user.markers.first.timeline).to eq 'home' expect(user.markers.first.last_read_id).to eq 70_120 end diff --git a/spec/controllers/api/v1/media_controller_spec.rb b/spec/controllers/api/v1/media_controller_spec.rb index 94b2a0a98..b574381f9 100644 --- a/spec/controllers/api/v1/media_controller_spec.rb +++ b/spec/controllers/api/v1/media_controller_spec.rb @@ -38,19 +38,10 @@ RSpec.describe Api::V1::MediaController do post :create, params: { file: fixture_file_upload('attachment.jpg', 'image/jpeg') } end - it 'returns http success' do + it 'creates a media attachment', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'creates a media attachment' do expect(MediaAttachment.first).to_not be_nil - end - - it 'uploads a file' do expect(MediaAttachment.first).to have_attached_file(:file) - end - - it 'returns media ID in JSON' do expect(body_as_json[:id]).to eq MediaAttachment.first.id.to_s end end @@ -60,19 +51,10 @@ RSpec.describe Api::V1::MediaController do post :create, params: { file: fixture_file_upload('attachment.gif', 'image/gif') } end - it 'returns http success' do + it 'creates a media attachment', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'creates a media attachment' do expect(MediaAttachment.first).to_not be_nil - end - - it 'uploads a file' do expect(MediaAttachment.first).to have_attached_file(:file) - end - - it 'returns media ID in JSON' do expect(body_as_json[:id]).to eq MediaAttachment.first.id.to_s end end @@ -82,17 +64,10 @@ RSpec.describe Api::V1::MediaController do post :create, params: { file: fixture_file_upload('attachment.webm', 'video/webm') } end - it do - # returns http success + it 'creates a media attachment', :aggregate_failures do expect(response).to have_http_status(200) - - # creates a media attachment expect(MediaAttachment.first).to_not be_nil - - # uploads a file expect(MediaAttachment.first).to have_attached_file(:file) - - # returns media ID in JSON expect(body_as_json[:id]).to eq MediaAttachment.first.id.to_s end end diff --git a/spec/controllers/api/v1/polls/votes_controller_spec.rb b/spec/controllers/api/v1/polls/votes_controller_spec.rb index 7abd2a1b1..5de225a48 100644 --- a/spec/controllers/api/v1/polls/votes_controller_spec.rb +++ b/spec/controllers/api/v1/polls/votes_controller_spec.rb @@ -18,18 +18,13 @@ RSpec.describe Api::V1::Polls::VotesController do post :create, params: { poll_id: poll.id, choices: %w(1) } end - it 'returns http success' do + it 'creates a vote', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'creates a vote' do vote = poll.votes.where(account: user.account).first expect(vote).to_not be_nil expect(vote.choice).to eq 1 - end - it 'updates poll tallies' do expect(poll.reload.cached_tallies).to eq [0, 1] end end diff --git a/spec/controllers/api/v1/reports_controller_spec.rb b/spec/controllers/api/v1/reports_controller_spec.rb index f923ff079..624ceb213 100644 --- a/spec/controllers/api/v1/reports_controller_spec.rb +++ b/spec/controllers/api/v1/reports_controller_spec.rb @@ -26,19 +26,10 @@ RSpec.describe Api::V1::ReportsController do post :create, params: { status_ids: [status.id], account_id: target_account.id, comment: 'reasons', category: category, rule_ids: rule_ids, forward: forward } end - it 'returns http success' do + it 'creates a report', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'creates a report' do expect(target_account.targeted_reports).to_not be_empty - end - - it 'saves comment' do expect(target_account.targeted_reports.first.comment).to eq 'reasons' - end - - it 'sends e-mails to admins' do expect(ActionMailer::Base.deliveries.first.to).to eq([admin.email]) end @@ -63,11 +54,8 @@ RSpec.describe Api::V1::ReportsController do let(:category) { 'violation' } let(:rule_ids) { [rule.id] } - it 'saves category' do + it 'saves category and rule_ids' do expect(target_account.targeted_reports.first.violation?).to be true - end - - it 'saves rule_ids' do expect(target_account.targeted_reports.first.rule_ids).to contain_exactly(rule.id) end end diff --git a/spec/controllers/api/v1/statuses/mutes_controller_spec.rb b/spec/controllers/api/v1/statuses/mutes_controller_spec.rb index bffa9fe0d..03274fe1c 100644 --- a/spec/controllers/api/v1/statuses/mutes_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/mutes_controller_spec.rb @@ -21,11 +21,8 @@ describe Api::V1::Statuses::MutesController do post :create, params: { status_id: status.id } end - it 'returns http success' do + it 'creates a conversation mute', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'creates a conversation mute' do expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to_not be_nil end end @@ -38,11 +35,8 @@ describe Api::V1::Statuses::MutesController do post :destroy, params: { status_id: status.id } end - it 'returns http success' do + it 'destroys the conversation mute', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'destroys the conversation mute' do expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to be_nil end end diff --git a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb index 756010af8..0d15cca75 100644 --- a/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/reblogged_by_accounts_controller_spec.rb @@ -24,14 +24,12 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController do Fabricate(:status, account: bob, reblog_of_id: status.id) end - it 'returns http success' do + it 'returns accounts who reblogged the status', :aggregate_failures do get :index, params: { status_id: status.id, limit: 2 } + expect(response).to have_http_status(200) expect(response.headers['Link'].links.size).to eq(2) - end - it 'returns accounts who reblogged the status' do - get :index, params: { status_id: status.id, limit: 2 } expect(body_as_json.size).to eq 2 expect([body_as_json[0][:id], body_as_json[1][:id]]).to contain_exactly(alice.id.to_s, bob.id.to_s) end diff --git a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb index 16ce95dc2..2f2b30b07 100644 --- a/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb +++ b/spec/controllers/api/v1/statuses/reblogs_controller_spec.rb @@ -28,19 +28,13 @@ describe Api::V1::Statuses::ReblogsController do end context 'with public status' do - it 'returns http success' do + it 'reblogs the status', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'updates the reblogs count' do expect(status.reblogs.count).to eq 1 - end - it 'updates the reblogged attribute' do expect(user.account.reblogged?(status)).to be true - end - it 'returns json with updated attributes' do hash_body = body_as_json expect(hash_body[:reblog][:id]).to eq status.id.to_s @@ -67,19 +61,13 @@ describe Api::V1::Statuses::ReblogsController do post :destroy, params: { status_id: status.id } end - it 'returns http success' do + it 'destroys the reblog', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'updates the reblogs count' do expect(status.reblogs.count).to eq 0 - end - it 'updates the reblogged attribute' do expect(user.account.reblogged?(status)).to be false - end - it 'returns json with updated attributes' do hash_body = body_as_json expect(hash_body[:id]).to eq status.id.to_s @@ -97,19 +85,13 @@ describe Api::V1::Statuses::ReblogsController do post :destroy, params: { status_id: status.id } end - it 'returns http success' do + it 'destroys the reblog', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'updates the reblogs count' do expect(status.reblogs.count).to eq 0 - end - it 'updates the reblogged attribute' do expect(user.account.reblogged?(status)).to be false - end - it 'returns json with updated attributes' do hash_body = body_as_json expect(hash_body[:id]).to eq status.id.to_s diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb index c2bdba9ac..30bafe19a 100644 --- a/spec/controllers/api/v1/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/statuses_controller_spec.rb @@ -30,14 +30,11 @@ RSpec.describe Api::V1::StatusesController do user.account.custom_filters.create!(phrase: 'filter1', context: %w(home), action: :hide, keywords_attributes: [{ keyword: 'banned' }, { keyword: 'irrelevant' }]) end - it 'returns http success' do - get :show, params: { id: status.id } - expect(response).to have_http_status(200) - end - - it 'returns filter information' do + it 'returns filter information', :aggregate_failures do get :show, params: { id: status.id } json = body_as_json + + expect(response).to have_http_status(200) expect(json[:filtered][0]).to include({ filter: a_hash_including({ id: user.account.custom_filters.first.id.to_s, @@ -57,14 +54,11 @@ RSpec.describe Api::V1::StatusesController do filter.statuses.create!(status_id: status.id) end - it 'returns http success' do - get :show, params: { id: status.id } - expect(response).to have_http_status(200) - end - - it 'returns filter information' do + it 'returns filter information', :aggregate_failures do get :show, params: { id: status.id } json = body_as_json + + expect(response).to have_http_status(200) expect(json[:filtered][0]).to include({ filter: a_hash_including({ id: user.account.custom_filters.first.id.to_s, @@ -83,14 +77,11 @@ RSpec.describe Api::V1::StatusesController do user.account.custom_filters.create!(phrase: 'filter1', context: %w(home), action: :hide, keywords_attributes: [{ keyword: 'banned' }, { keyword: 'irrelevant' }]) end - it 'returns http success' do - get :show, params: { id: status.id } - expect(response).to have_http_status(200) - end - - it 'returns filter information' do + it 'returns filter information', :aggregate_failures do get :show, params: { id: status.id } json = body_as_json + + expect(response).to have_http_status(200) expect(json[:reblog][:filtered][0]).to include({ filter: a_hash_including({ id: user.account.custom_filters.first.id.to_s, @@ -125,11 +116,8 @@ RSpec.describe Api::V1::StatusesController do post :create, params: { status: 'Hello world' } end - it 'returns http success' do + it 'returns rate limit headers', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'returns rate limit headers' do expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s expect(response.headers['X-RateLimit-Remaining']).to eq (RateLimiter::FAMILIES[:statuses][:limit] - 1).to_s end @@ -143,11 +131,8 @@ RSpec.describe Api::V1::StatusesController do post :create, params: { status: '@alice hm, @bob is really annoying lately', allowed_mentions: [alice.id] } end - it 'returns http unprocessable entity' do + it 'returns serialized extra accounts in body', :aggregate_failures do expect(response).to have_http_status(422) - end - - it 'returns serialized extra accounts in body' do expect(body_as_json[:unexpected_accounts].map { |a| a.slice(:id, :acct) }).to eq [{ id: bob.id.to_s, acct: bob.acct }] end end @@ -157,11 +142,8 @@ RSpec.describe Api::V1::StatusesController do post :create, params: {} end - it 'returns http unprocessable entity' do + it 'returns rate limit headers', :aggregate_failures do expect(response).to have_http_status(422) - end - - it 'returns rate limit headers' do expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s end end @@ -173,11 +155,8 @@ RSpec.describe Api::V1::StatusesController do post :create, params: { status: 'Hello world' } end - it 'returns http too many requests' do + it 'returns rate limit headers', :aggregate_failures do expect(response).to have_http_status(429) - end - - it 'returns rate limit headers' do expect(response.headers['X-RateLimit-Limit']).to eq RateLimiter::FAMILIES[:statuses][:limit].to_s expect(response.headers['X-RateLimit-Remaining']).to eq '0' end @@ -192,11 +171,8 @@ RSpec.describe Api::V1::StatusesController do post :destroy, params: { id: status.id } end - it 'returns http success' do + it 'removes the status', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'removes the status' do expect(Status.find_by(id: status.id)).to be_nil end end @@ -209,11 +185,8 @@ RSpec.describe Api::V1::StatusesController do put :update, params: { id: status.id, status: 'I am updated' } end - it 'returns http success' do + it 'updates the status', :aggregate_failures do expect(response).to have_http_status(200) - end - - it 'updates the status' do expect(status.reload.text).to eq 'I am updated' end end diff --git a/spec/controllers/api/v2/admin/accounts_controller_spec.rb b/spec/controllers/api/v2/admin/accounts_controller_spec.rb index 635f64591..18b395014 100644 --- a/spec/controllers/api/v2/admin/accounts_controller_spec.rb +++ b/spec/controllers/api/v2/admin/accounts_controller_spec.rb @@ -44,14 +44,14 @@ RSpec.describe Api::V2::Admin::AccountsController do context "when called with #{params.inspect}" do let(:params) { params } - it 'returns http success' do + it "returns the correct accounts (#{expected_results.inspect})" do expect(response).to have_http_status(200) - end - it "returns the correct accounts (#{expected_results.inspect})" do - json = body_as_json + expect(body_json_ids).to eq(expected_results.map { |symbol| send(symbol).id }) + end - expect(json.map { |a| a[:id].to_i }).to eq(expected_results.map { |symbol| send(symbol).id }) + def body_json_ids + body_as_json.map { |a| a[:id].to_i } end end end diff --git a/spec/controllers/api/v2/filters/keywords_controller_spec.rb b/spec/controllers/api/v2/filters/keywords_controller_spec.rb index 057a9c3d0..5321f787a 100644 --- a/spec/controllers/api/v2/filters/keywords_controller_spec.rb +++ b/spec/controllers/api/v2/filters/keywords_controller_spec.rb @@ -40,17 +40,13 @@ RSpec.describe Api::V2::Filters::KeywordsController do post :create, params: { filter_id: filter_id, keyword: 'magic', whole_word: false } end - it 'returns http success' do + it 'creates a filter', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'returns a keyword' do json = body_as_json expect(json[:keyword]).to eq 'magic' expect(json[:whole_word]).to be false - end - it 'creates a keyword' do filter = user.account.custom_filters.first expect(filter).to_not be_nil expect(filter.keywords.pluck(:keyword)).to eq ['magic'] @@ -73,11 +69,9 @@ RSpec.describe Api::V2::Filters::KeywordsController do get :show, params: { id: keyword.id } end - it 'returns http success' do + it 'responds with the keyword', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'returns expected data' do json = body_as_json expect(json[:keyword]).to eq 'foo' expect(json[:whole_word]).to be false @@ -100,11 +94,9 @@ RSpec.describe Api::V2::Filters::KeywordsController do get :update, params: { id: keyword.id, keyword: 'updated' } end - it 'returns http success' do + it 'updates the keyword', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'updates the keyword' do expect(keyword.reload.keyword).to eq 'updated' end @@ -125,11 +117,9 @@ RSpec.describe Api::V2::Filters::KeywordsController do delete :destroy, params: { id: keyword.id } end - it 'returns http success' do + it 'destroys the keyword', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'removes the filter' do expect { keyword.reload }.to raise_error ActiveRecord::RecordNotFound end diff --git a/spec/controllers/api/v2/filters/statuses_controller_spec.rb b/spec/controllers/api/v2/filters/statuses_controller_spec.rb index 588532ffd..5c2a62395 100644 --- a/spec/controllers/api/v2/filters/statuses_controller_spec.rb +++ b/spec/controllers/api/v2/filters/statuses_controller_spec.rb @@ -41,16 +41,12 @@ RSpec.describe Api::V2::Filters::StatusesController do post :create, params: { filter_id: filter_id, status_id: status.id } end - it 'returns http success' do + it 'creates a filter', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'returns a status filter' do json = body_as_json expect(json[:status_id]).to eq status.id.to_s - end - it 'creates a status filter' do filter = user.account.custom_filters.first expect(filter).to_not be_nil expect(filter.statuses.pluck(:status_id)).to eq [status.id] @@ -73,11 +69,9 @@ RSpec.describe Api::V2::Filters::StatusesController do get :show, params: { id: status_filter.id } end - it 'returns http success' do + it 'responds with the filter', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'returns expected data' do json = body_as_json expect(json[:status_id]).to eq status_filter.status_id.to_s end @@ -99,11 +93,9 @@ RSpec.describe Api::V2::Filters::StatusesController do delete :destroy, params: { id: status_filter.id } end - it 'returns http success' do + it 'destroys the filter', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'removes the filter' do expect { status_filter.reload }.to raise_error ActiveRecord::RecordNotFound end diff --git a/spec/requests/api/v1/admin/account_actions_spec.rb b/spec/requests/api/v1/admin/account_actions_spec.rb index 9295d262d..bdf1f08e4 100644 --- a/spec/requests/api/v1/admin/account_actions_spec.rb +++ b/spec/requests/api/v1/admin/account_actions_spec.rb @@ -51,14 +51,9 @@ RSpec.describe 'Account actions' do it_behaves_like 'a successful notification delivery' it_behaves_like 'a successful logged action', :disable, :user - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - it 'disables the target account' do expect { subject }.to change { target_account.reload.user_disabled? }.from(false).to(true) + expect(response).to have_http_status(200) end end @@ -70,14 +65,9 @@ RSpec.describe 'Account actions' do it_behaves_like 'a successful notification delivery' it_behaves_like 'a successful logged action', :sensitive, :account - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - it 'marks the target account as sensitive' do expect { subject }.to change { target_account.reload.sensitized? }.from(false).to(true) + expect(response).to have_http_status(200) end end @@ -89,14 +79,9 @@ RSpec.describe 'Account actions' do it_behaves_like 'a successful notification delivery' it_behaves_like 'a successful logged action', :silence, :account - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - it 'marks the target account as silenced' do expect { subject }.to change { target_account.reload.silenced? }.from(false).to(true) + expect(response).to have_http_status(200) end end @@ -108,14 +93,9 @@ RSpec.describe 'Account actions' do it_behaves_like 'a successful notification delivery' it_behaves_like 'a successful logged action', :suspend, :account - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - it 'marks the target account as suspended' do expect { subject }.to change { target_account.reload.suspended? }.from(false).to(true) + expect(response).to have_http_status(200) end end diff --git a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb index 4382cb84e..3f33b50f3 100644 --- a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb +++ b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb @@ -92,15 +92,10 @@ RSpec.describe 'Canonical Email Blocks' do it_behaves_like 'forbidden for wrong role', 'Moderator' context 'when the requested canonical email block exists' do - it 'returns http success' do + it 'returns the requested canonical email block data correctly', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the requested canonical email block data correctly' do - subject - json = body_as_json expect(json[:id]).to eq(canonical_email_block.id.to_s) @@ -142,29 +137,19 @@ RSpec.describe 'Canonical Email Blocks' do context 'when there is a matching canonical email block' do let!(:canonical_email_block) { CanonicalEmailBlock.create(params) } - it 'returns http success' do + it 'returns the expected canonical email hash', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the expected canonical email hash' do - subject - expect(body_as_json[0][:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) end end context 'when there is no matching canonical email block' do - it 'returns http success' do + it 'returns an empty list', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns an empty list' do - subject - expect(body_as_json).to be_empty end end @@ -183,15 +168,10 @@ RSpec.describe 'Canonical Email Blocks' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do + it 'returns the canonical_email_hash correctly', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the canonical_email_hash correctly' do - subject - expect(body_as_json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) end @@ -208,15 +188,10 @@ RSpec.describe 'Canonical Email Blocks' do context 'when the canonical_email_hash param is provided instead of email' do let(:params) { { canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } } - it 'returns http success' do + it 'returns the correct canonical_email_hash', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the correct canonical_email_hash' do - subject - expect(body_as_json[:canonical_email_hash]).to eq(params[:canonical_email_hash]) end end @@ -224,15 +199,10 @@ RSpec.describe 'Canonical Email Blocks' do 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 + it 'ignores the canonical_email_hash param', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'ignores the canonical_email_hash param' do - subject - expect(body_as_json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) end end @@ -262,15 +232,10 @@ RSpec.describe 'Canonical Email Blocks' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do + it 'deletes the canonical email block', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'deletes the canonical email block' do - subject - expect(CanonicalEmailBlock.find_by(id: canonical_email_block.id)).to be_nil end diff --git a/spec/requests/api/v1/admin/domain_allows_spec.rb b/spec/requests/api/v1/admin/domain_allows_spec.rb index 96000e3ef..6db1ab6e3 100644 --- a/spec/requests/api/v1/admin/domain_allows_spec.rb +++ b/spec/requests/api/v1/admin/domain_allows_spec.rb @@ -75,15 +75,10 @@ RSpec.describe 'Domain Allows' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do + it 'returns the expected allowed domain name', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the expected allowed domain name' do - subject - expect(body_as_json[:domain]).to eq domain_allow.domain end @@ -108,21 +103,11 @@ RSpec.describe 'Domain Allows' do it_behaves_like 'forbidden for wrong role', 'Moderator' context 'with a valid domain name' do - it 'returns http success' do + it 'returns the expected domain name', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the expected domain name' do - subject - expect(body_as_json[:domain]).to eq 'foo.bar.com' - end - - it 'creates a domain allow' do - subject - expect(DomainAllow.find_by(domain: 'foo.bar.com')).to be_present end end @@ -171,15 +156,10 @@ RSpec.describe 'Domain Allows' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do + it 'deletes the allowed domain', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'deletes the allowed domain' do - subject - expect(DomainAllow.find_by(id: domain_allow.id)).to be_nil end diff --git a/spec/requests/api/v1/admin/domain_blocks_spec.rb b/spec/requests/api/v1/admin/domain_blocks_spec.rb index 7a5ac28c5..1fb6fc822 100644 --- a/spec/requests/api/v1/admin/domain_blocks_spec.rb +++ b/spec/requests/api/v1/admin/domain_blocks_spec.rb @@ -89,15 +89,10 @@ RSpec.describe 'Domain Blocks' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do + it 'returns the expected domain block content', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the expected domain block content' do - subject - expect(body_as_json).to eq( { id: domain_block.id.to_s, @@ -133,27 +128,18 @@ RSpec.describe 'Domain Blocks' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - - it 'returns expected domain name and severity' do + it 'returns expected domain name and severity', :aggregate_failures do subject body = body_as_json + expect(response).to have_http_status(200) expect(body).to match a_hash_including( { domain: 'foo.bar.com', severity: 'silence', } ) - end - - it 'creates a domain block' do - subject expect(DomainBlock.find_by(domain: 'foo.bar.com')).to be_present end @@ -163,15 +149,10 @@ RSpec.describe 'Domain Blocks' do Fabricate(:domain_block, domain: 'bar.com', severity: :suspend) end - it 'returns http unprocessable entity' do + it 'returns existing domain block in error', :aggregate_failures do subject expect(response).to have_http_status(422) - end - - it 'returns existing domain block in error' do - subject - expect(body_as_json[:existing_domain_block][:domain]).to eq('bar.com') end end @@ -199,15 +180,10 @@ RSpec.describe 'Domain Blocks' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do + it 'returns the updated domain block', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the updated domain block' do - subject - expect(body_as_json).to match a_hash_including( { id: domain_block.id.to_s, @@ -241,15 +217,10 @@ RSpec.describe 'Domain Blocks' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do + it 'deletes the domain block', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'deletes the domain block' do - subject - expect(DomainBlock.find_by(id: domain_block.id)).to be_nil end diff --git a/spec/requests/api/v1/admin/email_domain_blocks_spec.rb b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb index d512def86..16656e020 100644 --- a/spec/requests/api/v1/admin/email_domain_blocks_spec.rb +++ b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb @@ -93,15 +93,10 @@ RSpec.describe 'Email Domain Blocks' do it_behaves_like 'forbidden for wrong role', 'Moderator' context 'when email domain block exists' do - it 'returns http success' do + it 'returns the correct blocked domain', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the correct blocked domain' do - subject - expect(body_as_json[:domain]).to eq(email_domain_block.domain) end end @@ -126,15 +121,10 @@ RSpec.describe 'Email Domain Blocks' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do + it 'returns the correct blocked email domain', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the correct blocked email domain' do - subject - expect(body_as_json[:domain]).to eq(params[:domain]) end @@ -182,21 +172,11 @@ RSpec.describe 'Email Domain Blocks' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do + it 'deletes email domain block', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns an empty body' do - subject - expect(body_as_json).to be_empty - end - - it 'deletes email domain block' do - subject - expect(EmailDomainBlock.find_by(id: email_domain_block.id)).to be_nil end diff --git a/spec/requests/api/v1/admin/ip_blocks_spec.rb b/spec/requests/api/v1/admin/ip_blocks_spec.rb index d03886c51..fbcb39e3b 100644 --- a/spec/requests/api/v1/admin/ip_blocks_spec.rb +++ b/spec/requests/api/v1/admin/ip_blocks_spec.rb @@ -84,15 +84,10 @@ RSpec.describe 'IP Blocks' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do + it 'returns the correct ip block', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the correct ip block' do - subject - json = body_as_json expect(json[:ip]).to eq("#{ip_block.ip}/#{ip_block.ip.prefix}") @@ -119,15 +114,10 @@ RSpec.describe 'IP Blocks' do it_behaves_like 'forbidden for wrong role', '' it_behaves_like 'forbidden for wrong role', 'Moderator' - it 'returns http success' do + it 'returns the correct ip block', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the correct ip block' do - subject - json = body_as_json expect(json[:ip]).to eq("#{params[:ip]}/32") @@ -186,15 +176,10 @@ RSpec.describe 'IP Blocks' do let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access', comment: 'Spam', expires_in: 48.hours) } let(:params) { { severity: 'sign_up_requires_approval', comment: 'Decreasing severity' } } - it 'returns http success' do + it 'returns the correct ip block', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the correct ip block' do - subject - expect(body_as_json).to match(hash_including({ ip: "#{ip_block.ip}/#{ip_block.ip.prefix}", severity: 'sign_up_requires_approval', @@ -226,21 +211,11 @@ RSpec.describe 'IP Blocks' do let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access') } - it 'returns http success' do + it 'deletes the ip block', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns an empty body' do - subject - expect(body_as_json).to be_empty - end - - it 'deletes the ip block' do - subject - expect(IpBlock.find_by(id: ip_block.id)).to be_nil end diff --git a/spec/requests/api/v1/admin/reports_spec.rb b/spec/requests/api/v1/admin/reports_spec.rb index 91c3c11f5..5403457db 100644 --- a/spec/requests/api/v1/admin/reports_spec.rb +++ b/spec/requests/api/v1/admin/reports_spec.rb @@ -122,15 +122,10 @@ RSpec.describe 'Reports' do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do + it 'returns the requested report content', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the requested report content' do - subject - expect(body_as_json).to include( { id: report.id.to_s, @@ -155,18 +150,10 @@ RSpec.describe 'Reports' do let!(:report) { Fabricate(:report, category: :other) } let(:params) { { category: 'spam' } } - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - - it 'updates the report category' do + it 'updates the report category', :aggregate_failures do expect { subject }.to change { report.reload.category }.from('other').to('spam') - end - it 'returns the updated report content' do - subject + expect(response).to have_http_status(200) report.reload @@ -196,14 +183,9 @@ RSpec.describe 'Reports' do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - - it 'marks report as resolved' do + it 'marks report as resolved', :aggregate_failures do expect { subject }.to change { report.reload.unresolved? }.from(true).to(false) + expect(response).to have_http_status(200) end end @@ -217,14 +199,9 @@ RSpec.describe 'Reports' do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - - it 'marks report as unresolved' do + it 'marks report as unresolved', :aggregate_failures do expect { subject }.to change { report.reload.unresolved? }.from(false).to(true) + expect(response).to have_http_status(200) end end @@ -238,14 +215,9 @@ RSpec.describe 'Reports' do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - - it 'assigns report to the requesting user' do + it 'assigns report to the requesting user', :aggregate_failures do expect { subject }.to change { report.reload.assigned_account_id }.from(nil).to(user.account.id) + expect(response).to have_http_status(200) end end @@ -259,14 +231,9 @@ RSpec.describe 'Reports' do it_behaves_like 'forbidden for wrong scope', 'write:statuses' it_behaves_like 'forbidden for wrong role', '' - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - - it 'unassigns report from assignee' do + it 'unassigns report from assignee', :aggregate_failures do expect { subject }.to change { report.reload.assigned_account_id }.from(user.account.id).to(nil) + expect(response).to have_http_status(200) end end end diff --git a/spec/requests/api/v1/apps/credentials_spec.rb b/spec/requests/api/v1/apps/credentials_spec.rb index dafe168c5..1268b36f8 100644 --- a/spec/requests/api/v1/apps/credentials_spec.rb +++ b/spec/requests/api/v1/apps/credentials_spec.rb @@ -12,14 +12,10 @@ describe 'Credentials' do let(:token) { Fabricate(:accessible_access_token, scopes: 'read', application: Fabricate(:application)) } let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } - it 'returns http success' do + it 'returns the app information correctly', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the app information correctly' do - subject expect(body_as_json).to match( a_hash_including( diff --git a/spec/requests/api/v1/apps_spec.rb b/spec/requests/api/v1/apps_spec.rb index 88f9eee36..acabbc93f 100644 --- a/spec/requests/api/v1/apps_spec.rb +++ b/spec/requests/api/v1/apps_spec.rb @@ -23,20 +23,11 @@ RSpec.describe 'Apps' do end context 'with valid params' do - it 'returns http success' do + it 'creates an OAuth app', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'creates an OAuth app' do - subject - expect(Doorkeeper::Application.find_by(name: client_name)).to be_present - end - - it 'returns client ID and client secret' do - subject body = body_as_json @@ -58,15 +49,10 @@ RSpec.describe 'Apps' do context 'with many duplicate scopes' do let(:scopes) { (%w(read) * 40).join(' ') } - it 'returns http success' do + it 'only saves the scope once', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'only saves the scope once' do - subject - expect(Doorkeeper::Application.find_by(name: client_name).scopes.to_s).to eq 'read' end end diff --git a/spec/requests/api/v1/domain_blocks_spec.rb b/spec/requests/api/v1/domain_blocks_spec.rb index 0f4fd4e90..954497ebe 100644 --- a/spec/requests/api/v1/domain_blocks_spec.rb +++ b/spec/requests/api/v1/domain_blocks_spec.rb @@ -22,15 +22,10 @@ RSpec.describe 'Domain blocks' do it_behaves_like 'forbidden for wrong scope', 'write:blocks' - it 'returns http success' do + it 'returns the domains blocked by the requesting user', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the domains blocked by the requesting user' do - subject - expect(body_as_json).to match_array(blocked_domains) end @@ -54,15 +49,10 @@ RSpec.describe 'Domain blocks' do it_behaves_like 'forbidden for wrong scope', 'read read:blocks' - it 'returns http success' do + it 'creates a domain block', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'creates a domain block' do - subject - expect(user.account.domain_blocking?(params[:domain])).to be(true) end @@ -100,15 +90,10 @@ RSpec.describe 'Domain blocks' do it_behaves_like 'forbidden for wrong scope', 'read read:blocks' - it 'returns http success' do + it 'deletes the specified domain block', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'deletes the specified domain block' do - subject - expect(user.account.domain_blocking?('example.com')).to be(false) end diff --git a/spec/requests/api/v1/follow_requests_spec.rb b/spec/requests/api/v1/follow_requests_spec.rb index 9d4ef8cd5..1d78c9be1 100644 --- a/spec/requests/api/v1/follow_requests_spec.rb +++ b/spec/requests/api/v1/follow_requests_spec.rb @@ -32,15 +32,10 @@ RSpec.describe 'Follow requests' do it_behaves_like 'forbidden for wrong scope', 'write write:follows' - it 'returns http success' do + it 'returns the expected content from accounts requesting to follow', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the expected content from accounts requesting to follow' do - subject - expect(body_as_json).to match_array(expected_response) end @@ -68,19 +63,9 @@ RSpec.describe 'Follow requests' do it_behaves_like 'forbidden for wrong scope', 'read read:follows' - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - - it 'allows the requesting follower to follow' do + it 'allows the requesting follower to follow', :aggregate_failures do expect { subject }.to change { follower.following?(user.account) }.from(false).to(true) - end - - it 'returns JSON with followed_by set to true' do - subject - + expect(response).to have_http_status(200) expect(body_as_json[:followed_by]).to be true end end @@ -98,21 +83,11 @@ RSpec.describe 'Follow requests' do it_behaves_like 'forbidden for wrong scope', 'read read:follows' - it 'returns http success' do + it 'removes the follow request', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'removes the follow request' do - subject - expect(FollowRequest.where(target_account: user.account, account: follower)).to_not exist - end - - it 'returns JSON with followed_by set to false' do - subject - expect(body_as_json[:followed_by]).to be false end end diff --git a/spec/requests/api/v1/lists_spec.rb b/spec/requests/api/v1/lists_spec.rb index 383e09d0c..22dde43a1 100644 --- a/spec/requests/api/v1/lists_spec.rb +++ b/spec/requests/api/v1/lists_spec.rb @@ -39,15 +39,10 @@ RSpec.describe 'Lists' do it_behaves_like 'forbidden for wrong scope', 'write write:lists' - it 'returns http success' do + it 'returns the expected lists', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the expected lists' do - subject - expect(body_as_json).to match_array(expected_response) end end @@ -61,15 +56,10 @@ RSpec.describe 'Lists' do it_behaves_like 'forbidden for wrong scope', 'write write:lists' - it 'returns http success' do + it 'returns the requested list correctly', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the requested list correctly' do - subject - expect(body_as_json).to eq({ id: list.id.to_s, title: list.title, @@ -106,21 +96,11 @@ RSpec.describe 'Lists' do it_behaves_like 'forbidden for wrong scope', 'read read:lists' - it 'returns http success' do + it 'returns the new list', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the new list' do - subject - expect(body_as_json).to match(a_hash_including(title: 'my list', replies_policy: 'none', exclusive: true)) - end - - it 'creates a list' do - subject - expect(List.where(account: user.account).count).to eq(1) end @@ -155,15 +135,10 @@ RSpec.describe 'Lists' do it_behaves_like 'forbidden for wrong scope', 'read read:lists' - it 'returns http success' do + it 'returns the updated list', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the updated list' do - subject - list.reload expect(body_as_json).to eq({ @@ -214,15 +189,10 @@ RSpec.describe 'Lists' do it_behaves_like 'forbidden for wrong scope', 'read read:lists' - it 'returns http success' do + it 'deletes the list', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'deletes the list' do - subject - expect(List.where(id: list.id)).to_not exist end diff --git a/spec/requests/api/v1/tags_spec.rb b/spec/requests/api/v1/tags_spec.rb index 300ddf805..db74a6f03 100644 --- a/spec/requests/api/v1/tags_spec.rb +++ b/spec/requests/api/v1/tags_spec.rb @@ -17,15 +17,10 @@ RSpec.describe 'Tags' do let!(:tag) { Fabricate(:tag) } let(:name) { tag.name } - it 'returns http success' do + it 'returns the tag', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'returns the tag' do - subject - expect(body_as_json[:name]).to eq(name) end end @@ -62,15 +57,10 @@ RSpec.describe 'Tags' do it_behaves_like 'forbidden for wrong scope', 'read read:follows' context 'when the tag exists' do - it 'returns http success' do + it 'creates follow', :aggregate_failures do subject expect(response).to have_http_status(:success) - end - - it 'creates follow' do - subject - expect(TagFollow.where(tag: tag, account: user.account)).to exist end end @@ -78,21 +68,11 @@ RSpec.describe 'Tags' do context 'when the tag does not exist' do let(:name) { 'hoge' } - it 'returns http success' do + it 'creates a new tag with the specified name', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'creates a new tag with the specified name' do - subject - expect(Tag.where(name: name)).to exist - end - - it 'creates follow' do - subject - expect(TagFollow.where(tag: Tag.find_by(name: name), account: user.account)).to exist end end @@ -133,15 +113,10 @@ RSpec.describe 'Tags' do it_behaves_like 'forbidden for wrong scope', 'read read:follows' - it 'returns http success' do + it 'removes the follow', :aggregate_failures do subject expect(response).to have_http_status(200) - end - - it 'removes the follow' do - subject - expect(TagFollow.where(tag: tag, account: user.account)).to_not exist end From bc6cd27d9edc473da3403c9af7970db8114b2135 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Fri, 13 Oct 2023 09:43:50 -0300 Subject: [PATCH 24/69] Migrate to request specs in `/api/v1/followed_tags` (#25472) --- .rubocop_todo.yml | 1 - .../api/v1/followed_tags_controller_spec.rb | 25 ------- spec/requests/api/v1/followed_tags_spec.rb | 65 +++++++++++++++++++ 3 files changed, 65 insertions(+), 26 deletions(-) delete mode 100644 spec/controllers/api/v1/followed_tags_controller_spec.rb create mode 100644 spec/requests/api/v1/followed_tags_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 19cfcff7f..d58bee4ba 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -180,7 +180,6 @@ RSpec/LetSetup: - 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb' - 'spec/controllers/api/v1/admin/accounts_controller_spec.rb' - 'spec/controllers/api/v1/filters_controller_spec.rb' - - 'spec/controllers/api/v1/followed_tags_controller_spec.rb' - 'spec/controllers/api/v2/admin/accounts_controller_spec.rb' - 'spec/controllers/api/v2/filters/keywords_controller_spec.rb' - 'spec/controllers/api/v2/filters/statuses_controller_spec.rb' diff --git a/spec/controllers/api/v1/followed_tags_controller_spec.rb b/spec/controllers/api/v1/followed_tags_controller_spec.rb deleted file mode 100644 index c1a366d4e..000000000 --- a/spec/controllers/api/v1/followed_tags_controller_spec.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Api::V1::FollowedTagsController do - render_views - - let(:user) { Fabricate(:user) } - let(:scopes) { 'read:follows' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - - before { allow(controller).to receive(:doorkeeper_token) { token } } - - describe 'GET #index' do - let!(:tag_follows) { Fabricate.times(5, :tag_follow, account: user.account) } - - before do - get :index, params: { limit: 1 } - end - - it 'returns http success' do - expect(response).to have_http_status(:success) - end - end -end diff --git a/spec/requests/api/v1/followed_tags_spec.rb b/spec/requests/api/v1/followed_tags_spec.rb new file mode 100644 index 000000000..9391c7bdc --- /dev/null +++ b/spec/requests/api/v1/followed_tags_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Followed tags' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'read:follows' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/followed_tags' do + subject do + get '/api/v1/followed_tags', headers: headers, params: params + end + + let!(:tag_follows) { Fabricate.times(5, :tag_follow, account: user.account) } + let(:params) { {} } + + let(:expected_response) do + tag_follows.map do |tag_follow| + a_hash_including(name: tag_follow.tag.name, following: true) + end + end + + before do + Fabricate(:tag_follow) + end + + it_behaves_like 'forbidden for wrong scope', 'write write:follows' + + it 'returns http success' do + subject + + expect(response).to have_http_status(:success) + end + + it 'returns the followed tags correctly' do + subject + + expect(body_as_json).to match_array(expected_response) + end + + context 'with limit param' do + let(:params) { { limit: 3 } } + + it 'returns only the requested number of follow tags' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + + it 'sets the correct pagination header for the prev path' do + subject + + expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_followed_tags_url(limit: params[:limit], since_id: tag_follows.last.id)) + end + + it 'sets the correct pagination header for the next path' do + subject + + expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_followed_tags_url(limit: params[:limit], max_id: tag_follows[2].id)) + end + end + end +end From abf0e1fa39f67baecab5c3893845e34a0f7be198 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 13 Oct 2023 09:19:40 -0400 Subject: [PATCH 25/69] Move `SignedReqestHelpers` rspec config to separate file (#25453) --- spec/rails_helper.rb | 20 -------------------- spec/support/signed_request_helpers.rb | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 spec/support/signed_request_helpers.rb diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 7b8dccb6a..06e8418a0 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -54,26 +54,6 @@ Devise::Test::ControllerHelpers.module_eval do end end -module SignedRequestHelpers - def get(path, headers: nil, sign_with: nil, **args) - return super path, headers: headers, **args if sign_with.nil? - - headers ||= {} - headers['Date'] = Time.now.utc.httpdate - headers['Host'] = ENV.fetch('LOCAL_DOMAIN') - signed_headers = headers.merge('(request-target)' => "get #{path}").slice('(request-target)', 'Host', 'Date') - - key_id = ActivityPub::TagManager.instance.key_uri_for(sign_with) - keypair = sign_with.keypair - signed_string = signed_headers.map { |key, value| "#{key.downcase}: #{value}" }.join("\n") - signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string)) - - headers['Signature'] = "keyId=\"#{key_id}\",algorithm=\"rsa-sha256\",headers=\"#{signed_headers.keys.join(' ').downcase}\",signature=\"#{signature}\"" - - super path, headers: headers, **args - end -end - RSpec.configure do |config| # This is set before running spec:system, see lib/tasks/tests.rake config.filter_run_excluding type: lambda { |type| diff --git a/spec/support/signed_request_helpers.rb b/spec/support/signed_request_helpers.rb new file mode 100644 index 000000000..33d7dba6b --- /dev/null +++ b/spec/support/signed_request_helpers.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module SignedRequestHelpers + def get(path, headers: nil, sign_with: nil, **args) + return super path, headers: headers, **args if sign_with.nil? + + headers ||= {} + headers['Date'] = Time.now.utc.httpdate + headers['Host'] = ENV.fetch('LOCAL_DOMAIN') + signed_headers = headers.merge('(request-target)' => "get #{path}").slice('(request-target)', 'Host', 'Date') + + key_id = ActivityPub::TagManager.instance.key_uri_for(sign_with) + keypair = sign_with.keypair + signed_string = signed_headers.map { |key, value| "#{key.downcase}: #{value}" }.join("\n") + signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string)) + + headers['Signature'] = "keyId=\"#{key_id}\",algorithm=\"rsa-sha256\",headers=\"#{signed_headers.keys.join(' ').downcase}\",signature=\"#{signature}\"" + + super path, headers: headers, **args + end +end From 058f73a4f5f17fc33bb842115d1f69a4b2e9f740 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Fri, 13 Oct 2023 10:30:40 -0300 Subject: [PATCH 26/69] Add request specs for `/api/v1/reports` (#25475) --- spec/requests/api/v1/reports_spec.rb | 106 +++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 spec/requests/api/v1/reports_spec.rb diff --git a/spec/requests/api/v1/reports_spec.rb b/spec/requests/api/v1/reports_spec.rb new file mode 100644 index 000000000..ce74f17c4 --- /dev/null +++ b/spec/requests/api/v1/reports_spec.rb @@ -0,0 +1,106 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Reports' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'write:reports' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'POST /api/v1/reports' do + subject do + post '/api/v1/reports', headers: headers, params: params + end + + let!(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + let(:status) { Fabricate(:status) } + let(:target_account) { status.account } + let(:category) { 'other' } + let(:forward) { nil } + let(:rule_ids) { nil } + + let(:params) do + { + status_ids: [status.id], + account_id: target_account.id, + comment: 'reasons', + category: category, + rule_ids: rule_ids, + forward: forward, + } + end + + it_behaves_like 'forbidden for wrong scope', 'read read:reports' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'returns the created report' do + subject + + expect(body_as_json).to match( + a_hash_including( + status_ids: [status.id.to_s], + category: category, + comment: 'reasons' + ) + ) + end + + it 'creates a report' do + subject + + expect(target_account.targeted_reports).to_not be_empty + end + + it 'sends e-mails to admins' do + perform_enqueued_jobs do + subject + + expect(ActionMailer::Base.deliveries.first.to).to eq([admin.email]) + end + end + + context 'when a status does not belong to the reported account' do + let(:target_account) { Fabricate(:account) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'when a category is chosen' do + let(:category) { 'spam' } + + it 'saves category' do + subject + + expect(target_account.targeted_reports.first.spam?).to be true + end + end + + context 'when violated rules are chosen' do + let(:rule) { Fabricate(:rule) } + let(:category) { 'violation' } + let(:rule_ids) { [rule.id] } + + it 'saves category' do + subject + + expect(target_account.targeted_reports.first.violation?).to be true + end + + it 'saves rule_ids' do + subject + + expect(target_account.targeted_reports.first.rule_ids).to contain_exactly(rule.id) + end + end + end +end From cad8cc90baf721fc4269b8ac36d4411ce8fe8539 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 13 Oct 2023 09:50:46 -0400 Subject: [PATCH 27/69] Speed-up on `MoveWorker` spec (#25528) --- spec/workers/move_worker_spec.rb | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/spec/workers/move_worker_spec.rb b/spec/workers/move_worker_spec.rb index 7577f6e89..efad92c04 100644 --- a/spec/workers/move_worker_spec.rb +++ b/spec/workers/move_worker_spec.rb @@ -67,39 +67,31 @@ describe MoveWorker do end shared_examples 'block and mute handling' do - it 'makes blocks carry over and add a note' do + it 'makes blocks and mutes carry over and adds a note' do subject.perform(source_account.id, target_account.id) + expect(block_service).to have_received(:call).with(blocking_account, target_account) expect(AccountNote.find_by(account: blocking_account, target_account: target_account).comment).to include(source_account.acct) - end - it 'makes mutes carry over and add a note' do - subject.perform(source_account.id, target_account.id) expect(muting_account.muting?(target_account)).to be true expect(AccountNote.find_by(account: muting_account, target_account: target_account).comment).to include(source_account.acct) end end shared_examples 'followers count handling' do - it 'updates the source account followers count' do + it 'updates the source and target account followers counts' do subject.perform(source_account.id, target_account.id) - expect(source_account.reload.followers_count).to eq(source_account.passive_relationships.count) - end - it 'updates the target account followers count' do - subject.perform(source_account.id, target_account.id) + expect(source_account.reload.followers_count).to eq(source_account.passive_relationships.count) expect(target_account.reload.followers_count).to eq(target_account.passive_relationships.count) end end shared_examples 'lists handling' do - it 'puts the new account on the list' do + it 'puts the new account on the list and makes valid lists', sidekiq: :inline do subject.perform(source_account.id, target_account.id) - expect(list.accounts.include?(target_account)).to be true - end - it 'does not create invalid list memberships' do - subject.perform(source_account.id, target_account.id) + expect(list.accounts.include?(target_account)).to be true expect(ListAccount.all).to all be_valid end end From 71cfdd78650b85251b0253acd11e55227f72f506 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 13 Oct 2023 16:10:43 +0200 Subject: [PATCH 28/69] Fix duplicate tests (#27395) --- .../api/v1/reports_controller_spec.rb | 63 ------------------- spec/requests/api/v1/reports_spec.rb | 43 ++++--------- 2 files changed, 13 insertions(+), 93 deletions(-) delete mode 100644 spec/controllers/api/v1/reports_controller_spec.rb diff --git a/spec/controllers/api/v1/reports_controller_spec.rb b/spec/controllers/api/v1/reports_controller_spec.rb deleted file mode 100644 index 624ceb213..000000000 --- a/spec/controllers/api/v1/reports_controller_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Api::V1::ReportsController do - render_views - - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'POST #create' do - let!(:admin) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - - let(:scopes) { 'write:reports' } - let(:status) { Fabricate(:status) } - let(:target_account) { status.account } - let(:category) { nil } - let(:forward) { nil } - let(:rule_ids) { nil } - - before do - post :create, params: { status_ids: [status.id], account_id: target_account.id, comment: 'reasons', category: category, rule_ids: rule_ids, forward: forward } - end - - it 'creates a report', :aggregate_failures do - expect(response).to have_http_status(200) - expect(target_account.targeted_reports).to_not be_empty - expect(target_account.targeted_reports.first.comment).to eq 'reasons' - expect(ActionMailer::Base.deliveries.first.to).to eq([admin.email]) - end - - context 'when a status does not belong to the reported account' do - let(:target_account) { Fabricate(:account) } - - it 'returns http not found' do - expect(response).to have_http_status(404) - end - end - - context 'when a category is chosen' do - let(:category) { 'spam' } - - it 'saves category' do - expect(target_account.targeted_reports.first.spam?).to be true - end - end - - context 'when violated rules are chosen' do - let(:rule) { Fabricate(:rule) } - let(:category) { 'violation' } - let(:rule_ids) { [rule.id] } - - it 'saves category and rule_ids' do - expect(target_account.targeted_reports.first.violation?).to be true - expect(target_account.targeted_reports.first.rule_ids).to contain_exactly(rule.id) - end - end - end -end diff --git a/spec/requests/api/v1/reports_spec.rb b/spec/requests/api/v1/reports_spec.rb index ce74f17c4..ba3d2b306 100644 --- a/spec/requests/api/v1/reports_spec.rb +++ b/spec/requests/api/v1/reports_spec.rb @@ -33,33 +33,21 @@ RSpec.describe 'Reports' do it_behaves_like 'forbidden for wrong scope', 'read read:reports' - it 'returns http success' do - subject - - expect(response).to have_http_status(200) - end - - it 'returns the created report' do - subject + it 'creates a report', :aggregate_failures do + perform_enqueued_jobs do + subject - expect(body_as_json).to match( - a_hash_including( - status_ids: [status.id.to_s], - category: category, - comment: 'reasons' + expect(response).to have_http_status(200) + expect(body_as_json).to match( + a_hash_including( + status_ids: [status.id.to_s], + category: category, + comment: 'reasons' + ) ) - ) - end - - it 'creates a report' do - subject - - expect(target_account.targeted_reports).to_not be_empty - end - it 'sends e-mails to admins' do - perform_enqueued_jobs do - subject + expect(target_account.targeted_reports).to_not be_empty + expect(target_account.targeted_reports.first.comment).to eq 'reasons' expect(ActionMailer::Base.deliveries.first.to).to eq([admin.email]) end @@ -90,15 +78,10 @@ RSpec.describe 'Reports' do let(:category) { 'violation' } let(:rule_ids) { [rule.id] } - it 'saves category' do + it 'saves category and rule_ids' do subject expect(target_account.targeted_reports.first.violation?).to be true - end - - it 'saves rule_ids' do - subject - expect(target_account.targeted_reports.first.rule_ids).to contain_exactly(rule.id) end end From 1b195ce115bd1572eb883722c55c1db90eb3182b Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 13 Oct 2023 10:14:23 -0400 Subject: [PATCH 29/69] Speed-up on `Rack::Attack` spec (#25542) --- spec/config/initializers/rack_attack_spec.rb | 48 ++++++++++++++++---- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/spec/config/initializers/rack_attack_spec.rb b/spec/config/initializers/rack_attack_spec.rb index 7cd4ac76b..c9ce9e27d 100644 --- a/spec/config/initializers/rack_attack_spec.rb +++ b/spec/config/initializers/rack_attack_spec.rb @@ -16,37 +16,63 @@ describe Rack::Attack, type: :request do # https://github.com/rack/rack-attack/blob/v6.6.1/lib/rack/attack/cache.rb#L64-L66 # So we want to minimize `Time.now.to_i % period` - travel_to Time.zone.at((Time.now.to_i / period.seconds).to_i * period.seconds) + travel_to Time.zone.at(counter_prefix * period.seconds) end context 'when the number of requests is lower than the limit' do + before do + below_limit.times { increment_counter } + end + it 'does not change the request status' do - limit.times do - request.call - expect(response).to_not have_http_status(429) - end + expect { request.call }.to change { throttle_count }.by(1) + + expect(response).to_not have_http_status(429) end end context 'when the number of requests is higher than the limit' do + before do + above_limit.times { increment_counter } + end + it 'returns http too many requests after limit and returns to normal status after period' do - (limit * 2).times do |i| - request.call - expect(response).to have_http_status(429) if i > limit - end + expect { request.call }.to change { throttle_count }.by(1) + expect(response).to have_http_status(429) travel period - request.call + expect { request.call }.to change { throttle_count }.by(1) expect(response).to_not have_http_status(429) end end + + def below_limit + limit - 1 + end + + def above_limit + limit * 2 + end + + def throttle_count + described_class.cache.read("#{counter_prefix}:#{throttle}:#{remote_ip}") || 0 + end + + def counter_prefix + (Time.now.to_i / period.seconds).to_i + end + + def increment_counter + described_class.cache.count("#{throttle}:#{remote_ip}", period) + end end let(:remote_ip) { '1.2.3.5' } describe 'throttle excessive sign-up requests by IP address' do context 'when accessed through the website' do + let(:throttle) { 'throttle_sign_up_attempts/ip' } let(:limit) { 25 } let(:period) { 5.minutes } let(:request) { -> { post path, headers: { 'REMOTE_ADDR' => remote_ip } } } @@ -65,6 +91,7 @@ describe Rack::Attack, type: :request do end context 'when accessed through the API' do + let(:throttle) { 'throttle_api_sign_up' } let(:limit) { 5 } let(:period) { 30.minutes } let(:request) { -> { post path, headers: { 'REMOTE_ADDR' => remote_ip } } } @@ -87,6 +114,7 @@ describe Rack::Attack, type: :request do end describe 'throttle excessive sign-in requests by IP address' do + let(:throttle) { 'throttle_login_attempts/ip' } let(:limit) { 25 } let(:period) { 5.minutes } let(:request) { -> { post path, headers: { 'REMOTE_ADDR' => remote_ip } } } From 82beeb2f7ec0eb07a1bdc688d827ef1db5a3427d Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Fri, 13 Oct 2023 11:23:16 -0300 Subject: [PATCH 30/69] Migrate to request specs in `/api/v1/admin/trends/links` (#25739) --- .../v1/admin/trends/links_controller_spec.rb | 52 ------- .../api/v1/admin/trends/links/links_spec.rb | 129 ++++++++++++++++++ 2 files changed, 129 insertions(+), 52 deletions(-) delete mode 100644 spec/controllers/api/v1/admin/trends/links_controller_spec.rb create mode 100644 spec/requests/api/v1/admin/trends/links/links_spec.rb diff --git a/spec/controllers/api/v1/admin/trends/links_controller_spec.rb b/spec/controllers/api/v1/admin/trends/links_controller_spec.rb deleted file mode 100644 index d9aa06824..000000000 --- a/spec/controllers/api/v1/admin/trends/links_controller_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::Admin::Trends::LinksController do - render_views - - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:read admin:write' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:account) { Fabricate(:account) } - let(:preview_card) { Fabricate(:preview_card) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'GET #index' do - it 'returns http success' do - get :index, params: { account_id: account.id, limit: 2 } - - expect(response).to have_http_status(200) - end - end - - describe 'POST #approve' do - before do - post :approve, params: { id: preview_card.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - - it 'returns http success' do - expect(response).to have_http_status(200) - end - end - - describe 'POST #reject' do - before do - post :reject, params: { id: preview_card.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - - it 'returns http success' do - expect(response).to have_http_status(200) - end - end -end diff --git a/spec/requests/api/v1/admin/trends/links/links_spec.rb b/spec/requests/api/v1/admin/trends/links/links_spec.rb new file mode 100644 index 000000000..05020b0fd --- /dev/null +++ b/spec/requests/api/v1/admin/trends/links/links_spec.rb @@ -0,0 +1,129 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'Links' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/admin/trends/links' do + subject do + get '/api/v1/admin/trends/links', headers: headers + end + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + end + + describe 'POST /api/v1/admin/trends/links/:id/approve' do + subject do + post "/api/v1/admin/trends/links/#{preview_card.id}/approve", headers: headers + end + + let(:preview_card) { Fabricate(:preview_card, trendable: false) } + + it_behaves_like 'forbidden for wrong scope', 'read write' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'sets the link as trendable' do + expect { subject }.to change { preview_card.reload.trendable }.from(false).to(true) + end + + it 'returns the link data' do + subject + + expect(body_as_json).to match( + a_hash_including( + url: preview_card.url, + title: preview_card.title, + description: preview_card.description, + type: 'link', + requires_review: false + ) + ) + end + + context 'when the link does not exist' do + it 'returns http not found' do + post '/api/v1/admin/trends/links/-1/approve', headers: headers + + expect(response).to have_http_status(404) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + end + + describe 'POST /api/v1/admin/trends/links/:id/reject' do + subject do + post "/api/v1/admin/trends/links/#{preview_card.id}/reject", headers: headers + end + + let(:preview_card) { Fabricate(:preview_card, trendable: false) } + + it_behaves_like 'forbidden for wrong scope', 'read write' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + it 'does not set the link as trendable' do + expect { subject }.to_not(change { preview_card.reload.trendable }) + end + + it 'returns the link data' do + subject + + expect(body_as_json).to match( + a_hash_including( + url: preview_card.url, + title: preview_card.title, + description: preview_card.description, + type: 'link', + requires_review: false + ) + ) + end + + context 'when the link does not exist' do + it 'returns http not found' do + post '/api/v1/admin/trends/links/-1/reject', headers: headers + + expect(response).to have_http_status(404) + end + end + + context 'without an authorization header' do + let(:headers) { {} } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + end +end From e0da64bb4e0ae84fb9380b6e2bff228c997eea62 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Fri, 13 Oct 2023 19:00:53 +0200 Subject: [PATCH 31/69] Fix empty ENV variables not using default nil value (#27400) --- config/initializers/chewy.rb | 6 +++--- config/initializers/devise.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/initializers/chewy.rb b/config/initializers/chewy.rb index 66008a000..076f38332 100644 --- a/config/initializers/chewy.rb +++ b/config/initializers/chewy.rb @@ -3,9 +3,9 @@ enabled = ENV['ES_ENABLED'] == 'true' host = ENV.fetch('ES_HOST') { 'localhost' } port = ENV.fetch('ES_PORT') { 9200 } -user = ENV.fetch('ES_USER') { nil } -password = ENV.fetch('ES_PASS') { nil } -fallback_prefix = ENV.fetch('REDIS_NAMESPACE') { nil } +user = ENV.fetch('ES_USER', nil).presence +password = ENV.fetch('ES_PASS', nil).presence +fallback_prefix = ENV.fetch('REDIS_NAMESPACE', nil).presence prefix = ENV.fetch('ES_PREFIX') { fallback_prefix } Chewy.settings = { diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 7bbaff71f..41d0ee25b 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -394,7 +394,7 @@ Devise.setup do |config| config.check_at_sign = true config.pam_default_suffix = ENV.fetch('PAM_EMAIL_DOMAIN') { ENV['LOCAL_DOMAIN'] } config.pam_default_service = ENV.fetch('PAM_DEFAULT_SERVICE') { 'rpam' } - config.pam_controlled_service = ENV.fetch('PAM_CONTROLLED_SERVICE') { nil } + config.pam_controlled_service = ENV.fetch('PAM_CONTROLLED_SERVICE', nil).presence end if ENV['LDAP_ENABLED'] == 'true' From f0ef87b84d7d56a9d59ce721062277eb58e6b3e9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:08:13 +0200 Subject: [PATCH 32/69] Update dependency i18n-tasks to v1.0.13 (#27403) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0068df461..ce21bc0a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -151,7 +151,7 @@ GEM erubi (>= 1.0.0) rack (>= 0.9.0) rouge (>= 1.0.0) - better_html (2.0.1) + better_html (2.0.2) actionview (>= 6.0) activesupport (>= 6.0) ast (~> 2.0) @@ -345,14 +345,14 @@ GEM rainbow (>= 2.0.0) i18n (1.14.1) concurrent-ruby (~> 1.0) - i18n-tasks (1.0.12) + i18n-tasks (1.0.13) activesupport (>= 4.0.2) ast (>= 2.1.0) better_html (>= 1.0, < 3.0) erubi highline (>= 2.0.0) i18n - parser (>= 2.2.3.0) + parser (>= 3.2.2.1) rails-i18n rainbow (>= 2.2.2, < 4.0) terminal-table (>= 1.5.1) @@ -561,7 +561,7 @@ GEM rails-html-sanitizer (1.6.0) loofah (~> 2.21) nokogiri (~> 1.14) - rails-i18n (7.0.7) + rails-i18n (7.0.8) i18n (>= 0.7, < 2) railties (>= 6.0.0, < 8) railties (7.0.8) From a00fc837014da27465695da1c3fd3a86e476937f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:09:20 +0200 Subject: [PATCH 33/69] Update dependency bufferutil to v4.0.8 (#27412) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index b69e7b65f..5684a034d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3820,9 +3820,9 @@ buffer@^6.0.3: ieee754 "^1.2.1" bufferutil@^4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.7.tgz#60c0d19ba2c992dd8273d3f73772ffc894c153ad" - integrity sha512-kukuqc39WOHtdxtw4UScxF/WVnMFVSQVKhtx3AjZJzhd0RGZZldcrfSEbVsWWe6KNH253574cq5F+wpv0G9pJw== + version "4.0.8" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.8.tgz#1de6a71092d65d7766c4d8a522b261a6e787e8ea" + integrity sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw== dependencies: node-gyp-build "^4.3.0" @@ -8766,9 +8766,9 @@ node-forge@^0.10.0: integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== node-gyp-build@^4.3.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.0.tgz#0c52e4cbf54bbd28b709820ef7b6a3c2d6209055" - integrity sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ== + version "4.6.1" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.6.1.tgz#24b6d075e5e391b8d5539d98c7fc5c210cac8a3e" + integrity sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ== node-int64@^0.4.0: version "0.4.0" @@ -11473,6 +11473,7 @@ stringz@^2.1.0: char-regex "^1.0.2" "strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: + name strip-ansi-cjs version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== From 13456efd7b3f95e5312eac350644679ae90d63db Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:31:52 +0200 Subject: [PATCH 34/69] New Crowdin Translations (automated) (#27410) Co-authored-by: GitHub Actions --- app/javascript/mastodon/locales/eu.json | 2 +- app/javascript/mastodon/locales/fi.json | 16 ++-- app/javascript/mastodon/locales/ja.json | 2 +- app/javascript/mastodon/locales/sk.json | 2 +- app/javascript/mastodon/locales/zh-TW.json | 6 +- config/locales/devise.zh-TW.yml | 4 +- config/locales/fi.yml | 100 ++++++++++----------- config/locales/si.yml | 14 +-- config/locales/simple_form.fi.yml | 32 +++---- config/locales/simple_form.zh-TW.yml | 10 +-- config/locales/sk.yml | 1 + config/locales/zh-TW.yml | 28 +++--- 12 files changed, 107 insertions(+), 110 deletions(-) diff --git a/app/javascript/mastodon/locales/eu.json b/app/javascript/mastodon/locales/eu.json index b674f90c8..5b4fef59c 100644 --- a/app/javascript/mastodon/locales/eu.json +++ b/app/javascript/mastodon/locales/eu.json @@ -613,7 +613,7 @@ "sign_in_banner.create_account": "Sortu kontua", "sign_in_banner.sign_in": "Hasi saioa", "sign_in_banner.sso_redirect": "Hasi saioa edo izena eman", - "sign_in_banner.text": "Hasi saioa profilak edo traolak jarraitzeko, bidalketak gogokoetara gehitzeko, partekatzeko edo erantzuteko. Zure kontutik ere komunika zaitezke beste zerbitzari ezberdin vatean.", + "sign_in_banner.text": "Hasi saioa profilak edo traolak jarraitzeko, bidalketak gogokoetara gehitzeko, partekatzeko edo erantzuteko. Zure kontutik ere komunika zaitezke beste zerbitzari ezberdin batean.", "status.admin_account": "Ireki @{name} erabiltzailearen moderazio interfazea", "status.admin_domain": "{domain}-(r)en moderazio-interfazea ireki", "status.admin_status": "Ireki bidalketa hau moderazio interfazean", diff --git a/app/javascript/mastodon/locales/fi.json b/app/javascript/mastodon/locales/fi.json index 91be7bfc0..a7a68533d 100644 --- a/app/javascript/mastodon/locales/fi.json +++ b/app/javascript/mastodon/locales/fi.json @@ -71,8 +71,8 @@ "account.unmute_notifications_short": "Poista ilmoitusten mykistys", "account.unmute_short": "Poista mykistys", "account_note.placeholder": "Lisää muistiinpano napsauttamalla", - "admin.dashboard.daily_retention": "Käyttäjän säilyminen rekisteröitymisen jälkeiseen päivään mennessä", - "admin.dashboard.monthly_retention": "Käyttäjän säilyminen rekisteröitymisen jälkeiseen kuukauteen mennessä", + "admin.dashboard.daily_retention": "Käyttäjän pysyminen rekisteröitymisen jälkeiseen päivään mennessä", + "admin.dashboard.monthly_retention": "Käyttäjän pysyminen rekisteröitymisen jälkeiseen kuukauteen mennessä", "admin.dashboard.retention.average": "Keskimäärin", "admin.dashboard.retention.cohort": "Kirjautumiset", "admin.dashboard.retention.cohort_size": "Uudet käyttäjät", @@ -101,7 +101,7 @@ "bundle_modal_error.close": "Sulje", "bundle_modal_error.message": "Jotain meni pieleen komponenttia ladattaessa.", "bundle_modal_error.retry": "Yritä uudelleen", - "closed_registrations.other_server_instructions": "Koska Mastodon on hajautettu, voit luoda tilin toiselle palvelimelle ja silti olla vuorovaikutuksessa tämän kanssa.", + "closed_registrations.other_server_instructions": "Koska Mastodon on hajautettu, voit luoda tilin toiselle palvelimelle ja olla silti vuorovaikutuksessa tämän kanssa.", "closed_registrations_modal.description": "Tilin luonti palveluun {domain} ei tällä hetkellä ole mahdollista, mutta huomioi, ettei Mastodonin käyttö edellytä juuri kyseisen palvelun tiliä.", "closed_registrations_modal.find_another_server": "Etsi toinen palvelin", "closed_registrations_modal.preamble": "Mastodon on hajautettu, joten riippumatta siitä, missä luot tilisi, voit seurata ja olla vuorovaikutuksessa kenen tahansa kanssa tällä palvelimella. Voit jopa isännöidä palvelinta!", @@ -154,9 +154,9 @@ "compose_form.publish_form": "Uusi julkaisu", "compose_form.publish_loud": "{publish}!", "compose_form.save_changes": "Tallenna muutokset", - "compose_form.sensitive.hide": "{count, plural, one {Merkitse media arkaluontoiseksi} other {Merkitse mediat arkaluontoiseksi}}", - "compose_form.sensitive.marked": "{count, plural, one {Media on merkitty arkaluontoiseksi} other {Mediat on merkitty arkaluontoiseksi}}", - "compose_form.sensitive.unmarked": "{count, plural, one {Mediaa ei ole merkitty arkaluontoiseksi} other {Medioja ei ole merkitty arkaluontoiseksi}}", + "compose_form.sensitive.hide": "{count, plural, one {Merkitse media arkaluonteiseksi} other {Merkitse mediat arkaluonteisiksi}}", + "compose_form.sensitive.marked": "{count, plural, one {Media on merkitty arkaluonteiseksi} other {Mediat on merkitty arkaluonteisiksi}}", + "compose_form.sensitive.unmarked": "{count, plural, one {Mediaa ei ole merkitty arkaluonteiseksi} other {Medioita ei ole merkitty arkaluonteisiksi}}", "compose_form.spoiler.marked": "Poista sisältövaroitus", "compose_form.spoiler.unmarked": "Lisää sisältövaroitus", "compose_form.spoiler_placeholder": "Kirjoita varoituksesi tähän", @@ -592,7 +592,7 @@ "search.search_or_paste": "Hae tai kirjoita URL-osoite", "search_popout.full_text_search_disabled_message": "Ei saatavilla palvelimella {domain}.", "search_popout.language_code": "ISO-kielikoodi", - "search_popout.options": "Haun asetukset", + "search_popout.options": "Hakuvalinnat", "search_popout.quick_actions": "Pikatoiminnot", "search_popout.recent": "Viimeaikaiset haut", "search_popout.specific_date": "tietty päivämäärä", @@ -686,7 +686,7 @@ "timeline_hint.resources.followers": "Seuraajat", "timeline_hint.resources.follows": "seurattua", "timeline_hint.resources.statuses": "Vanhemmat julkaisut", - "trends.counter_by_accounts": "{count, plural, one {{counter} henkilö} other {{counter} henkilöä}} {days, plural, one {viimeisen päivän} other {viimeisten {days} päivän}} aikana", + "trends.counter_by_accounts": "{count, plural, one {{counter} henkilö} other {{counter} henkilöä}} {days, plural, one {viime päivänä} other {viimeisenä {days} päivänä}}", "trends.trending_now": "Suosittua nyt", "ui.beforeunload": "Luonnos häviää, jos poistut Mastodonista.", "units.short.billion": "{count} mrd.", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 0c8e417b9..c5817c199 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -47,7 +47,7 @@ "account.locked_info": "このアカウントは承認制アカウントです。相手が承認するまでフォローは完了しません。", "account.media": "メディア", "account.mention": "@{name}さんにメンション", - "account.moved_to": "{name}さんがアカウントを引っ越しました:", + "account.moved_to": "{name}さんはこちらのアカウントに引っ越しました:", "account.mute": "@{name}さんをミュート", "account.mute_notifications_short": "通知をオフにする", "account.mute_short": "ミュート", diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json index da12eaa1b..60760607f 100644 --- a/app/javascript/mastodon/locales/sk.json +++ b/app/javascript/mastodon/locales/sk.json @@ -61,7 +61,7 @@ "account.requested_follow": "{name} ti poslal žiadosť na sledovanie", "account.share": "Zdieľaj @{name} profil", "account.show_reblogs": "Ukáž vyzdvihnutia od @{name}", - "account.statuses_counter": "{count, plural, one {{counter} Toot} other {{counter} Toots}}", + "account.statuses_counter": "{count, plural, one {{counter} príspevok} other {{counter} príspevkov}}", "account.unblock": "Odblokuj @{name}", "account.unblock_domain": "Prestaň skrývať {domain}", "account.unblock_short": "Odblokuj", diff --git a/app/javascript/mastodon/locales/zh-TW.json b/app/javascript/mastodon/locales/zh-TW.json index 27fd0ad35..b1cd0ca75 100644 --- a/app/javascript/mastodon/locales/zh-TW.json +++ b/app/javascript/mastodon/locales/zh-TW.json @@ -204,7 +204,7 @@ "dismissable_banner.explore_links": "這些新聞故事正在被此伺服器以及去中心化網路上的人們熱烈討論著。越多不同人所嘟出的新聞排名更高。", "dismissable_banner.explore_statuses": "這些於此伺服器以及去中心化網路中其他伺服器發出的嘟文正在被此伺服器上的人們熱烈討論著。越多不同人轉嘟及最愛排名更高。", "dismissable_banner.explore_tags": "這些主題標籤正在被此伺服器以及去中心化網路上的人們熱烈討論著。越多不同人所嘟出的主題標籤排名更高。", - "dismissable_banner.public_timeline": "這些是來自 {domain} 上人們於社群網站中跟隨者所發表之最近公開嘟文。", + "dismissable_banner.public_timeline": "這些是來自 {domain} 使用者們跟隨中帳號所發表之最新公開嘟文。", "embed.instructions": "要在您的網站嵌入此嘟文,請複製以下程式碼。", "embed.preview": "它將顯示成這樣:", "emoji_button.activity": "活動", @@ -271,8 +271,8 @@ "filter_modal.select_filter.title": "過濾此嘟文", "filter_modal.title.status": "過濾一則嘟文", "firehose.all": "全部", - "firehose.local": "此伺服器", - "firehose.remote": "其他伺服器", + "firehose.local": "本站", + "firehose.remote": "聯邦宇宙", "follow_request.authorize": "授權", "follow_request.reject": "拒絕", "follow_requests.unlocked_explanation": "即便您的帳號未被鎖定,{domain} 的管理員認為您可能想要自己審核這些帳號的跟隨請求。", diff --git a/config/locales/devise.zh-TW.yml b/config/locales/devise.zh-TW.yml index ea1813e41..c01beb796 100644 --- a/config/locales/devise.zh-TW.yml +++ b/config/locales/devise.zh-TW.yml @@ -4,7 +4,7 @@ zh-TW: confirmations: confirmed: 您的電子郵件地址已確認成功。 send_instructions: 幾分鐘後您將收到確認信件。若未收到此信件,請檢查垃圾郵件資料夾。 - send_paranoid_instructions: 如果您的電子郵件存在於我們的資料庫,您將會在幾分鐘內收到確認信。若未收到請檢查垃圾郵件資料夾。 + send_paranoid_instructions: 如果您的電子郵件存在於我們的資料庫,您將於幾分鐘內收到確認信。若未收到請檢查垃圾郵件資料夾。 failure: already_authenticated: 您已登入。 inactive: 您的帳號尚未啟用。 @@ -43,7 +43,7 @@ zh-TW: reset_password_instructions: action: 變更密碼 explanation: 您已請求帳號的新密碼。 - extra: 若您並未請求,請忽略此信件。您的密碼在存取上方連結並建立新密碼前不會變更。 + extra: 若您並未請求,請忽略此信件。您的密碼於存取上方連結並建立新密碼前不會變更。 subject: Mastodon:重設密碼指引 title: 重設密碼 two_factor_disabled: diff --git a/config/locales/fi.yml b/config/locales/fi.yml index 0c36b795c..cdc5d1267 100644 --- a/config/locales/fi.yml +++ b/config/locales/fi.yml @@ -12,7 +12,7 @@ fi: one: seuraaja other: seuraajaa following: seurattu(a) - instance_actor_flash: Tämä tili on virtuaalinen toimija, jota käytetään edustamaan itse palvelinta eikä yksittäistä käyttäjää. Sitä käytetään federointitarkoituksiin, eikä sitä tule jäädyttää. + instance_actor_flash: Tämä tili on virtuaalinen toimija, jota käytetään edustamaan itse palvelinta eikä yksittäistä käyttäjää. Sitä käytetään liittoutumistarkoituksiin, eikä sitä tule jäädyttää. last_active: viimeksi aktiivinen link_verified_on: Tämän linkin omistus on tarkastettu %{date} nothing_here: Täällä ei ole mitään! @@ -138,7 +138,7 @@ fi: security_measures: only_password: Vain salasana password_and_2fa: Salasana ja kaksivaiheinen tunnistautuminen - sensitive: Pakotus arkaluonteiseksi + sensitive: Pakota arkaluonteiseksi sensitized: Merkitty arkaluonteiseksi shared_inbox_url: Jaetun saapuvan postilaatikon osoite show: @@ -157,7 +157,7 @@ fi: unblock_email: Poista sähköpostiosoitteen esto unblocked_email_msg: Käyttäjän %{username} sähköpostiosoitteen esto kumottiin unconfirmed_email: Sähköpostia ei vahvistettu - undo_sensitized: Kumoa pakotus arkaluonteiseksi tiliksi + undo_sensitized: Kumoa pakotus arkaluonteiseksi undo_silenced: Kumoa rajoitus undo_suspension: Peru jäähy unsilenced_msg: Tilin %{username} rajoituksen kumoaminen onnistui @@ -167,7 +167,7 @@ fi: view_domain: Näytä verkkotunnuksen yhteenveto warn: Varoita web: Verkko - whitelisted: Sallittu federoimaan + whitelisted: Sallittu liittoutua action_logs: action_types: approve_appeal: Hyväksy valitus @@ -214,12 +214,12 @@ fi: resend_user: Lähetä vahvistusviesti uudelleen reset_password_user: Nollaa salasana resolve_report: Selvitä raportti - sensitive_account: Pakotus arkaluontoiseksi tiliksi + sensitive_account: Pakotus arkaluonteiseksi tiliksi silence_account: Rajoita tiliä suspend_account: Jäädytä tili unassigned_report: Peruuta raportin määritys unblock_email_account: Poista sähköpostiosoitteen esto - unsensitive_account: Kumoa pakotus arkaluontoiseksi tiliksi + unsensitive_account: Kumoa pakotus arkaluonteiseksi tiliksi unsilence_account: Kumoa tilin rajoitus unsuspend_account: Kumoa tilin jäädytys update_announcement: Päivitä tiedote @@ -239,7 +239,7 @@ fi: create_announcement_html: "%{name} loi uuden tiedotteen %{target}" create_canonical_email_block_html: "%{name} esti sähköpostin tiivisteellä %{target}" create_custom_emoji_html: "%{name} lähetti uuden emojin %{target}" - create_domain_allow_html: "%{name} salli federoinnin verkkotunnuksen %{target} kanssa" + create_domain_allow_html: "%{name} salli liittoutumisen verkkotunnuksen %{target} kanssa" create_domain_block_html: "%{name} esti verkkotunnuksen %{target}" create_email_domain_block_html: "%{name} esti sähköpostiverkkotunnuksen %{target}" create_ip_block_html: "%{name} loi IP-säännön %{target}" @@ -249,7 +249,7 @@ fi: destroy_announcement_html: "%{name} poisti tiedotteen %{target}" destroy_canonical_email_block_html: "%{name} poisti sähköpostin eston tiivisteellä %{target}" destroy_custom_emoji_html: "%{name} poisti emojin %{target}" - destroy_domain_allow_html: "%{name} kielsi federoinnin verkkotunnuksen %{target} kanssa" + destroy_domain_allow_html: "%{name} kielsi liittoutumisen verkkotunnuksen %{target} kanssa" destroy_domain_block_html: "%{name} poisti verkkotunnuksen %{target} eston" destroy_email_domain_block_html: "%{name} poisti sähköpostiverkkotunnuksen %{target} eston" destroy_instance_html: "%{name} tyhjensi verkkotunnuksen %{target}" @@ -278,7 +278,7 @@ fi: suspend_account_html: "%{name} jäädytti käyttäjän %{target} tilin" unassigned_report_html: "%{name} peruutti raportin määrityksen %{target}" unblock_email_account_html: "%{name} poisti käyttäjän %{target} sähköpostiosoitteen eston" - unsensitive_account_html: "%{name} poisti käyttäjän %{target} median arkaluonteisen merkinnän" + unsensitive_account_html: "%{name} kumosi käyttäjän %{target} median arkaluonteisuusmerkinnän" unsilence_account_html: "%{name} kumosi käyttäjän %{target} rajoituksen" unsuspend_account_html: "%{name} kumosi käyttäjän %{target} tilin jäädytyksen" update_announcement_html: "%{name} päivitti tiedotteen %{target}" @@ -375,12 +375,12 @@ fi: empty: Valituksia ei löytynyt. title: Valitukset domain_allows: - add_new: Salli liitto verkkotunnuksella - created_msg: Verkkotunnus on onnistuneesti sallittu federoinnille - destroyed_msg: Verkkotunnusta on kielletty federoimasta + add_new: Salli liittoutuminen tämän verkkotunnuksen kanssa + created_msg: Verkkotunnuksen on onnistuneesti sallittu liittoutua + destroyed_msg: Verkkotunnusta on kielletty liittoutumasta export: Vie import: Tuo - undo: Estä liitto verkkotunnukselle + undo: Kiellä liittoutuminen tämän verkkotunnuksen kanssa domain_blocks: add_new: Lisää uusi verkkotunnuksen esto confirm_suspension: @@ -527,7 +527,7 @@ fi: public_comment: Julkinen kommentti purge: Tyhjennä purge_description_html: Jos uskot, että tämä verkkotunnus on offline-tilassa tarkoituksella, voit poistaa kaikki verkkotunnuksen tilitietueet ja niihin liittyvät tiedot tallennustilastasi. Tämä voi kestää jonkin aikaa. - title: Federointi + title: Liittoutuminen total_blocked_by_us: Estämämme total_followed_by_them: Heidän seuraama total_followed_by_us: Meidän seuraama @@ -562,7 +562,7 @@ fi: relays: add_new: Lisää uusi välittäjä delete: Poista - description_html: "Federointivälittäjä on välityspalvelin, joka siirtää suuria määriä julkisia julkaisuja siihen liittyneiden palvelinten välillä. Se voi auttaa pieniä ja keskisuuria palvelimia löytämään fediversumin sisältöä, mikä muutoin vaatisi paikallisia käyttäjiä seuraamaan etäpalvalinten käyttäjiä manuaalisesti." + description_html: "Liittoutumisvälittäjä on välityspalvelin, joka siirtää suuria määriä julkisia julkaisuja siihen liittyneiden palvelinten välillä. Se voi auttaa pieniä ja keskisuuria palvelimia löytämään fediversumin sisältöä, mikä muutoin vaatisi paikallisia käyttäjiä seuraamaan etäpalvalinten käyttäjiä manuaalisesti." disable: Poista käytöstä disabled: Poissa käytöstä enable: Ota käyttöön @@ -671,56 +671,56 @@ fi: devops: DevOps invites: Kutsut moderation: Valvonta - special: Erikois + special: Erityistä delete: Poista description_html: "Käyttäjärooleilla voit muokata, mihin toimintoihin ja alueisiin käyttäjäsi pääsevät käsiksi." - edit: Muokkaa "%{name}" roolia - everyone: Oletus käyttöoikeudet - everyone_full_description_html: Tämä on perusrooli joka vaikuttaa kaikkiin käyttäjiin, jopa ilman määrättyä roolia. Kaikki muut roolit perivät sen käyttöoikeudet. + edit: Muokkaa roolia ”%{name}” + everyone: Oletuskäyttöoikeudet + everyone_full_description_html: Tämä on perusrooli, joka vaikuttaa kaikkiin käyttäjiin, jopa ilman määrättyä roolia. Kaikki muut roolit perivät sen käyttöoikeudet. permissions_count: one: "%{count} käyttöoikeus" other: "%{count} käyttöoikeutta" privileges: administrator: Ylläpitäjä administrator_description: Käyttäjät, joilla on tämä käyttöoikeus, ohittavat jokaisen käyttöoikeuden - delete_user_data: Poista käyttäjän tiedot + delete_user_data: Poistaa käyttäjän tiedot delete_user_data_description: Salli käyttäjien poistaa muiden käyttäjien tiedot viipymättä - invite_users: Kutsu käyttäjiä + invite_users: Kutsua käyttäjiä invite_users_description: Sallii käyttäjien kutsua uusia ihmisiä palvelimelle - manage_announcements: Hallitse tiedotteita + manage_announcements: Hallita tiedotteita manage_announcements_description: Sallii käyttäjien hallita tiedotteita palvelimella - manage_appeals: Hallitse valituksia + manage_appeals: Hallita valituksia manage_appeals_description: Sallii käyttäjien tarkistaa valvontatoimia koskevia valituksia - manage_blocks: Hallitse estoja + manage_blocks: Hallita estoja manage_blocks_description: Sallii käyttäjien estää sähköpostipalveluntarjoajia ja IP-osoitteita - manage_custom_emojis: Hallitse mukautettuja emojeita + manage_custom_emojis: Hallita mukautettuja emojeita manage_custom_emojis_description: Sallii käyttäjien hallita mukautettuja emojeita palvelimella - manage_federation: Hallitse federointia - manage_federation_description: Sallii käyttäjien estää tai sallia federointi muiden verkkotunnusten kanssa ja hallita toimitusta - manage_invites: Hallitse kutsuja + manage_federation: Hallita liittoutumista + manage_federation_description: Sallii käyttäjien estää tai sallia liittoutuminen muiden verkkotunnusten kanssa ja hallita toimitusta + manage_invites: Hallita kutsuja manage_invites_description: Sallii käyttäjien selata ja poistaa kutsulinkkejä käytöstä - manage_reports: Hallitse raportteja + manage_reports: Hallita raportteja manage_reports_description: Sallii käyttäjien tarkistaa raportteja ja suorittaa valvontatoimia niitä vastaan - manage_roles: Hallitse rooleja + manage_roles: Hallita rooleja manage_roles_description: Sallii käyttäjien hallita ja määrittää rooleja heidän alapuolellaan - manage_rules: Hallitse sääntöjä + manage_rules: Hallita sääntöjä manage_rules_description: Sallii käyttäjien muuttaa palvelimen sääntöjä - manage_settings: Hallitse asetuksia + manage_settings: Hallita asetuksia manage_settings_description: Sallii käyttäjien muuttaa sivuston asetuksia - manage_taxonomies: Hallitse luokittelua + manage_taxonomies: Hallita luokittelua manage_taxonomies_description: Sallii käyttäjien tarkistaa suositun sisällön ja päivittää aihetunnisteiden asetuksia - manage_user_access: Hallitse käyttäjäoikeuksia + manage_user_access: Hallita käyttäjäoikeuksia manage_user_access_description: Sallii käyttäjien poistaa muiden käyttäjien kaksivaiheinen todennus käytöstä, vaihtaa heidän sähköpostiosoitteensa ja nollata heidän salasanansa - manage_users: Hallitse käyttäjiä + manage_users: Hallita käyttäjiä manage_users_description: Sallii käyttäjien tarkastella muiden käyttäjien tietoja ja suorittaa valvontatoimia heitä kohtaan - manage_webhooks: Hallitse webhookeja + manage_webhooks: Hallita webhookeja manage_webhooks_description: Sallii käyttäjien luoda webhookeja hallinnollisiin tapahtumiin view_audit_log: Katsoa valvontalokia view_audit_log_description: Sallii käyttäjien nähdä palvelimen hallinnollisten toimien historian - view_dashboard: Näytä koontinäyttö + view_dashboard: Katsoa koontinäyttöä view_dashboard_description: Sallii käyttäjien käyttää kojelautaa ja erilaisia mittareita view_devops: DevOps - view_devops_description: Sallii käyttäjille oikeuden käyttää Sidekiq ja pgHero dashboardeja + view_devops_description: Sallii käyttäjille pääsyn Sidekiq- ja pgHero-hallintapaneeleihin title: Roolit rules: add_new: Lisää sääntö @@ -732,7 +732,7 @@ fi: settings: about: manage_rules: Hallitse palvelimen sääntöjä - preamble: Anna perusteellista tietoa siitä, miten palvelinta käytetään, valvotaan, rahoitetaan. + preamble: Anna perusteellista tietoa siitä, miten palvelinta käytetään, valvotaan ja rahoitetaan. rules_hint: On olemassa erityinen alue sääntöjä, joita käyttäjien odotetaan noudattavan. title: Tietoja appearance: @@ -752,7 +752,7 @@ fi: title: Jätä käyttäjät oletusarvoisesti hakukoneindeksoinnin ulkopuolelle discovery: follow_recommendations: Seuraamissuositukset - preamble: Mielenkiintoisen sisällön esille tuominen auttaa saamaan uusia käyttäjiä, jotka eivät ehkä tunne ketään Mastodonista. Määrittele, kuinka erilaiset etsintäominaisuudet toimivat palvelimellasi. + preamble: Mielenkiintoisen sisällön esille tuominen auttaa saamaan uusia käyttäjiä, jotka eivät ehkä tunne ketään Mastodonista. Määrittele, kuinka erilaiset löytämisominaisuudet toimivat palvelimellasi. profile_directory: Profiilihakemisto public_timelines: Julkiset aikajanat publish_discovered_servers: Julkaise löydetyt palvelimet @@ -765,17 +765,17 @@ fi: users: Kirjautuneille paikallisille käyttäjille registrations: preamble: Määritä, kuka voi luoda tilin palvelimellesi. - title: Rekisteröinnit + title: Rekisteröityminen registrations_mode: modes: approved: Rekisteröinti vaatii hyväksynnän none: Kukaan ei voi rekisteröityä open: Kaikki voivat rekisteröityä security: - authorized_fetch: Vaadi todennus yhdistetyiltä palvelimilta - authorized_fetch_hint: Todennuksen vaatiminen federoiduilta palvelimilta mahdollistaa sekä käyttäjä- että palvelintason estojen tiukemman valvonnan. Tämä tapahtuu kuitenkin suorituskyvyn kustannuksella, vähentää vastauksiesi tavoittavuutta ja voi aiheuttaa yhteensopivuusongelmia joidenkin federoitujen palvelujen kanssa. Tämä ei myöskään estä omistautuneita toimijoita hakemasta julkisia julkaisujasi ja tilejäsi. + authorized_fetch: Vaadi todennus liittoutuvilta palvelimilta + authorized_fetch_hint: Todennuksen vaatiminen liittoutuvilta palvelimilta mahdollistaa sekä käyttäjä- että palvelintason estojen tiukemman valvonnan. Tämä tapahtuu kuitenkin suorituskyvyn kustannuksella, vähentää vastauksiesi tavoittavuutta ja voi aiheuttaa yhteensopivuusongelmia joidenkin liittoutuvien palvelujen kanssa. Tämä ei myöskään estä omistautuneita toimijoita hakemasta julkisia julkaisujasi ja tilejäsi. authorized_fetch_overridden_hint: Et voi tällä hetkellä muuttaa tätä asetusta, koska se on ohitettu ympäristömuuttujalla. - federation_authentication: Yhdistettyjen palvelinten todentamisen täytäntöönpano + federation_authentication: Liittoutumisen todentamisen täytäntöönpano title: Palvelimen asetukset site_uploads: delete: Poista ladattu tiedosto @@ -1239,7 +1239,7 @@ fi: deprecated_api_multiple_keywords: Näitä parametreja ei voi muuttaa tästä sovelluksesta, koska ne koskevat useampaa kuin yhtä suodattimen avainsanaa. Käytä uudempaa sovellusta tai selainkäyttöliittymää. invalid_context: Ei sisältöä tai se on virheellinen index: - contexts: Suodattimet %{contexts} + contexts: Suodattaa kontektissa %{contexts} delete: Poista empty: Sinulla ei ole suodattimia. expires_in: Vanhenee %{distance} @@ -1414,8 +1414,8 @@ fi: not_found: ei voitu löytää on_cooldown: Sinä olet jäähyllä followers_count: Seuraajat muuton aikana - incoming_migrations: Siirtyminen toiselta tililtä - incoming_migrations_html: Siirtyäksesi toisesta tilistä tähän, sinun täytyy ensin luoda tilin alias. + incoming_migrations: Muutto toiselta tililtä + incoming_migrations_html: Muuttaaksesi toisesta tilistä tähän, sinun täytyy ensin luoda tilin alias. moved_msg: Tilisi ohjaa nyt kohteeseen %{acct} ja seuraajiasi siirretään. not_redirecting: Tilisi ei ohjaa tällä hetkellä mihinkään muuhun tiliin. on_cooldown: Olet siirtänyt tilisi äskettäin. Tämä toiminto tulee saataville uudelleen %{count} päivän kuluttua. @@ -1707,7 +1707,7 @@ fi: keep_self_bookmark: Säilytä kirjanmerkkeihin lisäämäsi julkaisut keep_self_bookmark_hint: Ei poista julkaisujasi, jos olet lisännyt ne kirjanmerkkeihin keep_self_fav: Säilytä suosikkeihin lisäämäsi julkaisut - keep_self_fav_hint: Ei poista julkaisujasi, jos olet lisännyt ne suosikkeihin + keep_self_fav_hint: Ei poista julkaisujasi, jos olet lisännyt ne suosikkeihisi min_age: '1209600': 2 viikkoa '15778476': 6 kuukautta @@ -1786,7 +1786,7 @@ fi: spam: Roskaposti violation: Sisältö rikkoo seuraavia yhteisön sääntöjä explanation: - delete_statuses: Joidenkin julkaisuistasi on havaittu rikkovan ainakin yhtä yhteisön sääntöä, ja instanssin %{instance} valvojat ovat poistaneet ne. + delete_statuses: Joidenkin julkaisuistasi on havaittu rikkovan ainakin yhtä yhteisön sääntöä, joten instanssin %{instance} valvojat ovat poistaneet ne. disable: Et voi enää käyttää tiliäsi, mutta profiilisi ja muut tiedot pysyvät muuttumattomina. Voit pyytää varmuuskopiota tiedoistasi, vaihtaa tilin asetuksia tai poistaa tilisi. mark_statuses_as_sensitive: Palvelimen %{instance} valvojat ovat merkinneet osan julkaisuistasi arkaluonteisiksi. Tämä tarkoittaa sitä, että ihmisten täytyy napauttaa mediaa ennen kuin sen esikatselu näytetään. Voit merkitä median itse arkaluonteiseksi, kun julkaiset tulevaisuudessa. sensitive: Tästä lähtien kaikki ladatut mediatiedostot merkitään arkaluonteisiksi ja piilotetaan napsautusvaroituksen taakse. @@ -1807,7 +1807,7 @@ fi: disable: Tili jäädytetty mark_statuses_as_sensitive: Julkaisut merkitty arkaluonteisiksi none: Varoitus - sensitive: Tili on merkitty arkaluonteiseksi + sensitive: Tili merkitty arkaluonteiseksi silence: Tiliä rajoitettu suspend: Tili jäädytetty welcome: diff --git a/config/locales/si.yml b/config/locales/si.yml index 39368b3f8..2c2a8ae82 100644 --- a/config/locales/si.yml +++ b/config/locales/si.yml @@ -791,9 +791,9 @@ si: guide_link_text: පරිවර්තකයින්ට දායක වීමට හැකිය. sensitive_content: සංවේදී අන්තර්ගත application_mailer: - notification_preferences: ඊමේල් මනාප වෙනස් කරන්න + notification_preferences: වි-තැපැල් අභිප්‍රේත වෙනස් කරන්න salutation: "%{name}," - settings: 'ඊමේල් මනාප වෙනස් කරන්න: %{link}' + settings: 'වි-තැපැල් අභිප්‍රේත වෙනස් කරන්න: %{link}' view: 'දැක්ම:' view_profile: පැතිකඩ බලන්න view_status: ලිපිය බලන්න @@ -1405,11 +1405,11 @@ si: title: සංරක්ෂිත රැගෙන යාම suspicious_sign_in: change_password: මුරපදය වෙනස් කරන්න - details: 'පුරනය වීමේ විස්තර මෙන්න:' - explanation: අපි නව IP ලිපිනයකින් ඔබගේ ගිණුමට පුරනය වීමක් අනාවරණය කරගෙන ඇත. - further_actions_html: මෙය ඔබ නොවේ නම්, අපි ඔබට වහාම %{action} ලෙස නිර්දේශ කර ඔබගේ ගිණුම සුරක්ෂිතව තබා ගැනීමට සාධක දෙකක සත්‍යාපනය සබල කරන්න. - subject: ඔබගේ ගිණුම නව IP ලිපිනයකින් ප්‍රවේශ වී ඇත - title: නව පුරනය වීමක් + details: 'ප්‍රවේශයට අදාළ විස්තර:' + explanation: ඔබගේ ගිණුමට නව අ.ජා.කෙ. (IP) ලිපිනයකින් ප්‍රවේශයක් අනාවරණය වී ඇත. + further_actions_html: මේ ඔබ නොවේ නම්, වහාම %{action}. ඔබගේ ගිණුම සුරක්‍ෂිතව තබා ගැනීමට ද්වි-සාධකය සබල කරන්න. + subject: ඔබගේ ගිණුමට නව අ.ජා.කෙ. (IP) ලිපිනයකින් ප්‍රවේශ වී ඇත + title: නව ප්‍රවේශයක් warning: appeal: අභියාචනයක් ඉදිරිපත් කරන්න appeal_description: මෙය දෝෂයක් බව ඔබ විශ්වාස කරන්නේ නම්, ඔබට %{instance}හි කාර්ය මණ්ඩලයට අභියාචනයක් ඉදිරිපත් කළ හැක. diff --git a/config/locales/simple_form.fi.yml b/config/locales/simple_form.fi.yml index f73667f5c..6c8875327 100644 --- a/config/locales/simple_form.fi.yml +++ b/config/locales/simple_form.fi.yml @@ -25,7 +25,7 @@ fi: types: disable: Estä käyttäjää käyttämästä tiliään, mutta älä poista tai piilota sen sisältöä. none: Käytä tätä lähettääksesi varoituksen käyttäjälle käynnistämättä mitään muita toimintoja. - sensitive: Pakota kaikki tämän käyttäjän mediatiedostot arkaluontoisiksi. + sensitive: Pakota kaikki tämän käyttäjän mediatiedostot arkaluonteisiksi. silence: Estä käyttäjää lähettämästä viestejä julkisesti, piilota hänen viestinsä ja ilmoituksensa ihmisiltä, jotka eivät seuraa häntä. Sulkee kaikki tämän tilin raportit. suspend: Estä kaikki vuorovaikutus tältä -tai tälle tilille ja poista sen kaikki sisältö. Päätös voidaan peruuttaa 30 päivän aikana. Sulkee kaikki raportit tätä tiliä vasten. warning_preset_id: Valinnainen. Voit silti lisätä mukautetun tekstin esiasetuksen loppuun @@ -79,14 +79,14 @@ fi: activity_api_enabled: Paikallisesti julkaistujen julkaisujen, aktiivisten käyttäjien ja rekisteröitymisten viikoittainen määrä backups_retention_period: Säilytä luodut arkistot määritetyn määrän päiviä. bootstrap_timeline_accounts: Nämä tilit kiinnitetään uusien käyttäjien seuraamissuositusten yläpuolelle. - closed_registrations_message: Näkyy, kun ilmoittautuminen on suljettu - content_cache_retention_period: Viestit muilta palvelimilta poistetaan määritetyn määrän päiviä jälkeen, kun arvo on asetettu positiiviseksi. Tämä voi olla peruuttamatonta. + closed_registrations_message: Näkyy, kun rekisteröityminen on suljettu + content_cache_retention_period: Kaikki julkaisut ja tehostukset muilta palvelimilta poistetaan, kun määritelty määrä päiviä on kulunut. Osaa julkaisuista voi olla mahdoton palauttaa. Kaikki julkaisuihin liittyvät kirjanmerkit, suosikit ja tehostukset menetetään, eikä niitä voi palauttaa. custom_css: Voit käyttää mukautettuja tyylejä Mastodonin verkkoversiossa. mascot: Ohittaa kuvituksen edistyneessä selainkäyttöliittymässä. media_cache_retention_period: Ladatut mediatiedostot poistetaan määritetyn määrän päiviä jälkeen, kun arvo on positiivinen ja ladataan uudelleen pyynnöstä. - peers_api_enabled: Luettelo verkkotunnuksista, jotka tämä palvelin on kohdannut fediversumissa. Se ei kerro, oletko liitossa tietyn palvelimen kanssa, vaan että palvelimesi on ylipäätään tietoinen siitä. Tätä tietoa käytetään palveluissa, jotka keräävät tilastoja liittoutumisesta yleisellä tasolla. + peers_api_enabled: Luettelo verkkotunnuksista, jotka tämä palvelin on kohdannut fediversumissa. Se ei kerro, oletko liitossa tietyn palvelimen kanssa, vaan että palvelimesi on ylipäätään tietoinen siitä. Tätä tietoa käytetään palveluissa, jotka keräävät tilastoja federoinnista yleisellä tasolla. profile_directory: Profiilihakemisto lueteloi kaikki käyttäjät, jotka ovat ilmoittaneet olevansa löydettävissä. - require_invite_text: Kun kirjautuminen vaatii manuaalisen hyväksynnän, tee ”Miksi haluat liittyä?” teksti syötetään pakolliseksi eikä vapaaehtoiseksi + require_invite_text: Kun rekisteröityminen vaatii manuaalisen hyväksynnän, tee ”Miksi haluat liittyä?” -tekstikentästä pakollinen vapaaehtoisen sijaan site_contact_email: Kuinka ihmiset voivat tavoittaa sinut oikeudellisissa tai tukikysymyksissä. site_contact_username: Miten ihmiset voivat tavoittaa sinut Mastodonissa. site_extended_description: Kaikki lisätiedot, jotka voivat olla hyödyllisiä kävijöille ja käyttäjille. Voidaan jäsentää Markdown-syntaksilla. @@ -96,10 +96,10 @@ fi: status_page_url: URL-osoite sivulle, jonka kautta tämän palvelimen tila voidaan ongelmatilanteissa tarkastaa theme: Teema, jonka uloskirjautuneet vierailijat ja uudet käyttäjät näkevät. thumbnail: Noin 2:1 kuva näytetään palvelimen tietojen rinnalla. - timeline_preview: Uloskirjautuneet vierailijat voivat selata uusimpia julkisia viestejä, jotka ovat saatavilla palvelimella. + timeline_preview: Uloskirjautuneet vierailijat voivat selata uusimpia julkisia julkaisuja, jotka ovat saatavilla palvelimella. trendable_by_default: Ohita suositun sisällön manuaalinen tarkistus. Yksittäisiä kohteita voidaan edelleen poistaa jälkikäteen. trends: Trendit osoittavat, mitkä julkaisut, aihetunnisteet ja uutiset ovat saamassa vetoa palvelimellasi. - trends_as_landing_page: Näytä vierailijoille ja uloskirjautuneille käyttäjille suosittu sisältö palvelininstanssin kuvaustekstin sijaan. Edellytyksenä on, että suosittu sisältö -ominaisuus on käytössä. + trends_as_landing_page: Näytä vierailijoille ja uloskirjautuneille käyttäjille suosittua sisältöä palvelimen kuvauksen sijaan. Edellyttää, että trendit on otettu käyttöön. form_challenge: current_password: Olet menossa suojatulle alueelle imports: @@ -129,9 +129,9 @@ fi: chosen_languages: Kun valittu, vain valituilla kielillä kirjoitetut julkaisut näkyvät julkisilla aikajanoilla role: Rooli määrää, mitkä käyttöoikeudet käyttäjällä on user_role: - color: Väri, jota käytetään roolin koko käyttöliittymässä, RGB heksamuodossa + color: Väri, jota käytetään roolille kaikkialla käyttöliittymässä, RGB-heksadesimaalimuodossa highlighted: Tämä tekee roolista julkisesti näkyvän - name: Roolin julkinen nimi, jos rooli on asetettu näytettäväksi mekkinä + name: Roolin julkinen nimi, jos rooli on asetettu näytettäväksi merkkinä permissions_as_keys: Käyttäjillä, joilla on tämä rooli, on käyttöoikeus... position: Korkeampi rooli ratkaisee konfliktit tietyissä tilanteissa. Tiettyjä toimintoja voidaan suorittaa vain rooleille, joiden prioriteetti on pienempi webhook: @@ -238,10 +238,10 @@ fi: hide: Piilota kokonaan warn: Piilota ja näytä varoitus form_admin_settings: - activity_api_enabled: Julkaise yhteenlasketut tilastot käyttäjätoiminnasta rajapinnassa + activity_api_enabled: Julkaise yhteenlasketut tilastot käyttäjätoiminnasta ohjelmointirajapinnassa backups_retention_period: Käyttäjän arkiston säilytysaika bootstrap_timeline_accounts: Suosittele aina näitä tilejä uusille käyttäjille - closed_registrations_message: Mukautettu viesti, kun kirjautumisia ei ole saatavilla + closed_registrations_message: Mukautettu viesti, kun rekisteröityminen ei ole saatavilla content_cache_retention_period: Sisällön välimuistin säilytysaika custom_css: Mukautettu CSS mascot: Mukautettu maskotti (legacy) @@ -251,7 +251,7 @@ fi: registrations_mode: Kuka voi rekisteröityä require_invite_text: Vaadi syy liittyä show_domain_blocks: Näytä verkkotunnusten estot - show_domain_blocks_rationale: Näytä miksi verkkotunnukset on estetty + show_domain_blocks_rationale: Näytä, miksi verkkotunnukset on estetty site_contact_email: Ota yhteyttä sähköpostilla site_contact_username: Yhteyshenkilön käyttäjänimi site_extended_description: Laajennettu kuvaus @@ -261,10 +261,10 @@ fi: status_page_url: Tilasivun URL-osoite theme: Oletusteema thumbnail: Palvelimen pikkukuva - timeline_preview: Salli todentamaton pääsy julkiselle aikajanalle + timeline_preview: Salli todentamaton pääsy julkisille aikajanoille trendable_by_default: Salli trendit ilman ennakkotarkastusta - trends: Trendit käyttöön - trends_as_landing_page: Käytä suosittua sisältöä aloitussivuna + trends: Ota trendit käyttöön + trends_as_landing_page: Käytä trendejä aloitussivuna interactions: must_be_follower: Estä ilmoitukset käyttäjiltä, jotka eivät seuraa sinua must_be_following: Estä ilmoitukset käyttäjiltä, joita et seuraa @@ -313,7 +313,7 @@ fi: time_zone: Aikavyöhyke user_role: color: Merkin väri - highlighted: Näyttä rooli merkkinä käyttäjäprofiileissa + highlighted: Näytä rooli merkkinä käyttäjäprofiileissa name: Nimi permissions_as_keys: Oikeudet position: Prioriteetti diff --git a/config/locales/simple_form.zh-TW.yml b/config/locales/simple_form.zh-TW.yml index 5108a9745..c48c659fa 100644 --- a/config/locales/simple_form.zh-TW.yml +++ b/config/locales/simple_form.zh-TW.yml @@ -5,7 +5,7 @@ zh-TW: account: discoverable: 公開嘟文及個人檔案可能於各 Mastodon 功能中被推薦,並且您的個人檔案可能被推薦至其他使用者。 display_name: 完整名稱或暱稱。 - fields: 烘培雞,自我認同代稱,年齡,及任何您想分享的。 + fields: 烘培雞、自我認同代稱、年齡,及任何您想分享的。 indexable: 您的公開嘟文可能會顯示於 Mastodon 之搜尋結果中。曾與您嘟文互動過的人可能無論如何都能搜尋它們。 note: '您可以 @mention 其他人或者使用 #主題標籤。' show_collections: 人們將能瀏覽您跟隨中及跟隨者帳號。您所跟隨之人能得知您正在跟隨其帳號。 @@ -31,7 +31,7 @@ zh-TW: warning_preset_id: 選用。您仍可在預設的結尾新增自訂文字 announcement: all_day: 核取後,只會顯示出時間範圍中的日期部分 - ends_at: 可選的,公告會在該時間點自動取消發布 + ends_at: 可選的,公告會於該時間點自動取消發布 scheduled_at: 空白則立即發布公告 starts_at: 可選的,讓公告在特定時間範圍內顯示 text: 您可以使用嘟文語法,但請小心別讓公告太鴨霸而佔據使用者的整個版面。 @@ -59,8 +59,8 @@ zh-TW: setting_display_media_default: 隱藏標為敏感內容的媒體 setting_display_media_hide_all: 總是隱藏所有媒體 setting_display_media_show_all: 總是顯示標為敏感內容的媒體 - setting_use_blurhash: 彩色漸層圖樣是基於隱藏媒體內容顏色產生,所有細節會變得模糊 - setting_use_pending_items: 關閉自動捲動更新,時間軸只會在點擊後更新 + setting_use_blurhash: 彩色漸層圖樣是基於隱藏媒體內容顏色產生,所有細節將變得模糊 + setting_use_pending_items: 關閉自動捲動更新,時間軸只會於點擊後更新 username: 您可以使用字幕、數字與底線 whole_word: 如果關鍵字或詞組僅有字母與數字,則其將只在符合整個單字的時候才會套用 domain_allow: @@ -126,7 +126,7 @@ zh-TW: tag: name: 您只能變更大小寫,例如,以使其更易讀。 user: - chosen_languages: 當選取時,只有選取語言之嘟文會在公開時間軸中顯示 + chosen_languages: 當選取時,只有選取語言之嘟文會於公開時間軸中顯示 role: 角色控制使用者有哪些權限 user_role: color: 在整個使用者介面中用於角色的顏色,十六進位格式的 RGB diff --git a/config/locales/sk.yml b/config/locales/sk.yml index 78b9b87db..c25fab6ae 100644 --- a/config/locales/sk.yml +++ b/config/locales/sk.yml @@ -353,6 +353,7 @@ sk: silence: Obmedz suspend: Vylúč title: Nové blokovanie domény + not_permitted: Nemáš povolenie na vykonanie tohto kroku obfuscate: Zatemniť názov domény private_comment: Súkromný komentár private_comment_hint: Odôvodni toto doménové obmedzenie, pre vnútorné vyrozumenie moderátorov. diff --git a/config/locales/zh-TW.yml b/config/locales/zh-TW.yml index 33870c0bb..e139742ff 100644 --- a/config/locales/zh-TW.yml +++ b/config/locales/zh-TW.yml @@ -390,7 +390,7 @@ zh-TW: domain: 站點 edit: 更改封鎖的站台 existing_domain_block: 您已對 %{name} 施加更嚴格的限制。 - existing_domain_block_html: 您已對 %{name} 施加更嚴格的限制,您需要先 解除封鎖。 + existing_domain_block_html: 您已對 %{name} 施加更嚴格的限制,您需要先解除封鎖。 export: 匯出 import: 匯入 new: @@ -451,9 +451,7 @@ zh-TW: title: 匯入網域黑名單 no_file: 尚未選擇檔案 follow_recommendations: - description_html: |- - 跟隨建議幫助新使用者們快速找到有趣的內容。當使用者沒有與其他帳號有足夠多的互動以建立個人化跟隨建議時,這些帳號將會被推薦。這些帳號將基於某選定語言之高互動和高本地跟隨者數量帳號而 - 每日重新更新。 + description_html: "跟隨建議幫助新使用者們快速找到有趣的內容。當使用者沒有與其他帳號有足夠多的互動以建立個人化跟隨建議時,這些帳號將會被推薦。這些帳號將基於某選定語言之高互動和高本地跟隨者數量帳號而每日重新更新。" language: 對於語言 status: 狀態 suppress: 取消跟隨建議 @@ -553,7 +551,7 @@ zh-TW: relays: add_new: 新增中繼站 delete: 刪除 - description_html: "聯邦中繼站 是種中繼伺服器,會在訂閱並推送至此中繼站的伺服器之間交換大量的公開嘟文。中繼站也能協助小型或中型伺服器從聯邦宇宙中探索內容,而無須本地使用者手動跟隨遠端伺服器的其他使用者。" + description_html: "聯邦中繼站 是種中繼伺服器,會於訂閱並推送至此中繼站的伺服器之間交換大量的公開嘟文。中繼站也能協助小型或中型伺服器從聯邦宇宙中探索內容,而無須本地使用者手動跟隨遠端伺服器的其他使用者。" disable: 停用 disabled: 停用 enable: 啟用 @@ -988,7 +986,7 @@ zh-TW: aliases: add_new: 建立別名 created_msg: 成功建立別名。您可以自舊帳號開始轉移。 - deleted_msg: 成功移除別名。您將無法再由舊帳號轉移到目前的帳號。 + deleted_msg: 成功移除別名。您將無法再由舊帳號轉移至目前的帳號。 empty: 您目前沒有任何別名。 hint_html: 如果想由其他帳號轉移至此帳號,您可以在此處新增別名,稍後系統將容許您將跟隨者由舊帳號轉移至此。此項作業是無害且可復原的帳號的遷移程序需要在舊帳號啟動。 remove: 取消連結別名 @@ -999,7 +997,7 @@ zh-TW: confirmation_dialogs: 確認對話框 discovery: 探索 localization: - body: Mastodon 是由志願者翻譯的。 + body: Mastodon 是由志願者所翻譯。 guide_link: https://crowdin.com/project/mastodon guide_link_text: 每個人都能貢獻。 sensitive_content: 敏感內容 @@ -1042,8 +1040,8 @@ zh-TW: log_in_with: 登入,使用 login: 登入 logout: 登出 - migrate_account: 轉移到另一個帳號 - migrate_account_html: 如果您希望引導他人跟隨另一個帳號,請 到這裡設定。 + migrate_account: 轉移至另一個帳號 + migrate_account_html: 如果您希望引導他人跟隨另一個帳號,請至這裡設定。 or_log_in_with: 或透過其他方式登入 privacy_policy_agreement_html: 我已閱讀且同意 隱私權政策 progress: @@ -1072,7 +1070,7 @@ zh-TW: email_below_hint_html: 請檢查您的垃圾郵件資料夾,或是請求另一個。如果是錯的,您可以更正您的電子郵件地址。 email_settings_hint_html: 請點擊我們寄給您連結以驗證 %{email}。我們將於此稍候。 link_not_received: 無法取得連結嗎? - new_confirmation_instructions_sent: 您將會在幾分鐘之內收到新的包含確認連結的電子郵件! + new_confirmation_instructions_sent: 您將於幾分鐘之內收到新的包含確認連結的電子郵件! title: 請檢查您的收件匣 sign_in: preamble_html: 請使用您於 %{domain} 的帳號密碼登入。若您的帳號託管於其他伺服器,您將無法在此登入。 @@ -1084,7 +1082,7 @@ zh-TW: status: account_status: 帳號狀態 confirming: 等待電子郵件確認完成。 - functional: 您的帳號可以正常使用了。 + functional: "您的帳號可以正常使用了。🎉" pending: 管管們正在處理您的申請,這可能需要一點時間處理。我們將於申請通過後以電子郵件方式通知您。 redirecting_to: 您的帳號因目前重定向至 %{acct} 而被停用。 view_strikes: 檢視針對您帳號過去的警示 @@ -1410,7 +1408,7 @@ zh-TW: followers: 此動作將會將目前帳號的所有跟隨者轉移至新帳號 only_redirect_html: 或者,您也可以僅在您的個人檔案中設定重新導向。 other_data: 其他資料並不會自動轉移 - redirect: 您目前的帳號將會在個人檔案頁面新增重新導向公告,並會被排除在搜尋結果之外 + redirect: 您目前的帳號將於個人檔案頁面新增重新導向公告,並會被排除在搜尋結果之外 moderation: title: 站務 move_handler: @@ -1499,9 +1497,7 @@ zh-TW: posting_defaults: 嘟文預設值 public_timelines: 公開時間軸 privacy: - hint_html: |- - 自訂您希望如何讓您的個人檔案及嘟文被找到。 - 藉由啟用一系列 Mastodon 功能以幫助您觸及更廣的受眾。煩請花些時間確認您是否欲啟用這些設定。 + hint_html: "自訂您希望如何讓您的個人檔案及嘟文被發現。藉由啟用一系列 Mastodon 功能以幫助您觸及更廣的受眾。煩請花些時間確認您是否欲啟用這些設定。" privacy: 隱私權 privacy_hint_html: 控制您希望向其他人揭露之內容。人們透過瀏覽其他人的跟隨者與其發嘟之應用程式發現有趣的個人檔案和酷炫的 Mastodon 應用程式,但您能選擇將其隱藏。 reach: 觸及 @@ -1665,7 +1661,7 @@ zh-TW: enabled: 自動刪除舊嘟文 enabled_hint: 一旦達到指定的保存期限,就會自動刪除您的嘟文,除非該嘟文符合下列例外 exceptions: 例外 - explanation: 因為刪除嘟文是耗費資源的操作,當伺服器不那麼忙碌時才會慢慢完成。因此,您的嘟文會在到達保存期限後一段時間才會被刪除。 + explanation: 因為刪除嘟文是耗費資源的操作,當伺服器不那麼忙碌時才會慢慢完成。因此,您的嘟文將於到達保存期限後一段時間才會被刪除。 ignore_favs: 忽略最愛數 ignore_reblogs: 忽略轉嘟數 interaction_exceptions: 基於互動的例外規則 From 23f8e93c6405277515d018d4ff2d550fe64ac159 Mon Sep 17 00:00:00 2001 From: Wladimir Palant Date: Mon, 16 Oct 2023 13:39:25 +0200 Subject: [PATCH 35/69] Fixes #23135 - Allow cross origin request for /nodeinfo/2.0 API (#27413) --- config/initializers/cors.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/initializers/cors.rb b/config/initializers/cors.rb index 3d94e38e8..642447784 100644 --- a/config/initializers/cors.rb +++ b/config/initializers/cors.rb @@ -14,6 +14,7 @@ Rails.application.config.middleware.insert_before 0, Rack::Cors do with_options headers: :any, credentials: false do with_options methods: [:get] do resource '/.well-known/*' + resource '/nodeinfo/*' resource '/@:username' resource '/users/:username' end From 299aa71c8f85542ace4d9cb61cbdf896ff342213 Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 16 Oct 2023 15:24:14 +0200 Subject: [PATCH 36/69] Fix handling of `inLanguage` attribute in preview card processing (#27423) --- app/lib/link_details_extractor.rb | 3 ++- spec/lib/link_details_extractor_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb index b95ec8051..a96612cab 100644 --- a/app/lib/link_details_extractor.rb +++ b/app/lib/link_details_extractor.rb @@ -36,7 +36,8 @@ class LinkDetailsExtractor end def language - json['inLanguage'] + lang = json['inLanguage'] + lang.is_a?(Hash) ? (lang['alternateName'] || lang['name']) : lang end def type diff --git a/spec/lib/link_details_extractor_spec.rb b/spec/lib/link_details_extractor_spec.rb index 599bc4e6d..8c485cef2 100644 --- a/spec/lib/link_details_extractor_spec.rb +++ b/spec/lib/link_details_extractor_spec.rb @@ -82,6 +82,10 @@ RSpec.describe LinkDetailsExtractor do 'name' => 'Pet News', 'url' => 'https://example.com', }, + 'inLanguage' => { + name: 'English', + alternateName: 'en', + }, }.to_json end @@ -115,6 +119,12 @@ RSpec.describe LinkDetailsExtractor do expect(subject.provider_name).to eq 'Pet News' end end + + describe '#language' do + it 'returns the language from structured data' do + expect(subject.language).to eq 'en' + end + end end context 'when is wrapped in CDATA tags' do From 00e92b40386e9ced25e5a6cad58f80dc78aca636 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 16 Oct 2023 09:37:22 -0400 Subject: [PATCH 37/69] Add coverage for `CLI::Statuses` command (#25321) --- spec/lib/mastodon/cli/statuses_spec.rb | 22 ++++++++++++++++++++++ spec/rails_helper.rb | 6 ++++++ 2 files changed, 28 insertions(+) diff --git a/spec/lib/mastodon/cli/statuses_spec.rb b/spec/lib/mastodon/cli/statuses_spec.rb index 2430a8841..38ebcd993 100644 --- a/spec/lib/mastodon/cli/statuses_spec.rb +++ b/spec/lib/mastodon/cli/statuses_spec.rb @@ -4,9 +4,31 @@ require 'rails_helper' require 'mastodon/cli/statuses' describe Mastodon::CLI::Statuses do + let(:cli) { described_class.new } + describe '.exit_on_failure?' do it 'returns true' do expect(described_class.exit_on_failure?).to be true end end + + describe '#remove', use_transactional_tests: false do + context 'with small batch size' do + let(:options) { { batch_size: 0 } } + + it 'exits with error message' do + expect { cli.invoke :remove, [], options }.to output( + a_string_including('Cannot run') + ).to_stdout.and raise_error(SystemExit) + end + end + + context 'with default batch size' do + it 'removes unreferenced statuses' do + expect { cli.invoke :remove }.to output( + a_string_including('Done after') + ).to_stdout + end + end + end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 06e8418a0..0bbf8fc52 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -85,6 +85,12 @@ RSpec.configure do |config| config.include Redisable config.include SignedRequestHelpers, type: :request + config.around(:each, use_transactional_tests: false) do |example| + self.use_transactional_tests = false + example.run + self.use_transactional_tests = true + end + config.before :each, type: :cli do stub_stdout stub_reset_connection_pools From f8afa0f614a1ec4637d98baf6125a7fc1c47c23c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 16 Oct 2023 09:38:27 -0400 Subject: [PATCH 38/69] Remove unused stub json ld context (#25454) --- .../requests/json-ld.activitystreams.txt | 391 ------------------ spec/fixtures/requests/json-ld.identity.txt | 100 ----- spec/fixtures/requests/json-ld.security.txt | 61 --- .../activitypub/linked_data_signature_spec.rb | 4 - spec/rails_helper.rb | 14 - ..._block_domain_from_account_service_spec.rb | 1 - 6 files changed, 571 deletions(-) delete mode 100644 spec/fixtures/requests/json-ld.activitystreams.txt delete mode 100644 spec/fixtures/requests/json-ld.identity.txt delete mode 100644 spec/fixtures/requests/json-ld.security.txt diff --git a/spec/fixtures/requests/json-ld.activitystreams.txt b/spec/fixtures/requests/json-ld.activitystreams.txt deleted file mode 100644 index 395797b27..000000000 --- a/spec/fixtures/requests/json-ld.activitystreams.txt +++ /dev/null @@ -1,391 +0,0 @@ -HTTP/1.1 200 OK -Date: Tue, 01 May 2018 23:25:57 GMT -Content-Location: activitystreams.jsonld -Vary: negotiate,accept -TCN: choice -Last-Modified: Mon, 16 Apr 2018 00:28:23 GMT -ETag: "1eb0-569ec4caa97c0;d3-540ee27e0eec0" -Accept-Ranges: bytes -Content-Length: 7856 -Cache-Control: max-age=21600 -Expires: Wed, 02 May 2018 05:25:57 GMT -P3P: policyref="http://www.w3.org/2014/08/p3p.xml" -Access-Control-Allow-Origin: * -Content-Type: application/ld+json -Strict-Transport-Security: max-age=15552000; includeSubdomains; preload -Content-Security-Policy: upgrade-insecure-requests - -{ - "@context": { - "@vocab": "_:", - "xsd": "http://www.w3.org/2001/XMLSchema#", - "as": "https://www.w3.org/ns/activitystreams#", - "ldp": "http://www.w3.org/ns/ldp#", - "id": "@id", - "type": "@type", - "Accept": "as:Accept", - "Activity": "as:Activity", - "IntransitiveActivity": "as:IntransitiveActivity", - "Add": "as:Add", - "Announce": "as:Announce", - "Application": "as:Application", - "Arrive": "as:Arrive", - "Article": "as:Article", - "Audio": "as:Audio", - "Block": "as:Block", - "Collection": "as:Collection", - "CollectionPage": "as:CollectionPage", - "Relationship": "as:Relationship", - "Create": "as:Create", - "Delete": "as:Delete", - "Dislike": "as:Dislike", - "Document": "as:Document", - "Event": "as:Event", - "Follow": "as:Follow", - "Flag": "as:Flag", - "Group": "as:Group", - "Ignore": "as:Ignore", - "Image": "as:Image", - "Invite": "as:Invite", - "Join": "as:Join", - "Leave": "as:Leave", - "Like": "as:Like", - "Link": "as:Link", - "Mention": "as:Mention", - "Note": "as:Note", - "Object": "as:Object", - "Offer": "as:Offer", - "OrderedCollection": "as:OrderedCollection", - "OrderedCollectionPage": "as:OrderedCollectionPage", - "Organization": "as:Organization", - "Page": "as:Page", - "Person": "as:Person", - "Place": "as:Place", - "Profile": "as:Profile", - "Question": "as:Question", - "Reject": "as:Reject", - "Remove": "as:Remove", - "Service": "as:Service", - "TentativeAccept": "as:TentativeAccept", - "TentativeReject": "as:TentativeReject", - "Tombstone": "as:Tombstone", - "Undo": "as:Undo", - "Update": "as:Update", - "Video": "as:Video", - "View": "as:View", - "Listen": "as:Listen", - "Read": "as:Read", - "Move": "as:Move", - "Travel": "as:Travel", - "IsFollowing": "as:IsFollowing", - "IsFollowedBy": "as:IsFollowedBy", - "IsContact": "as:IsContact", - "IsMember": "as:IsMember", - "subject": { - "@id": "as:subject", - "@type": "@id" - }, - "relationship": { - "@id": "as:relationship", - "@type": "@id" - }, - "actor": { - "@id": "as:actor", - "@type": "@id" - }, - "attributedTo": { - "@id": "as:attributedTo", - "@type": "@id" - }, - "attachment": { - "@id": "as:attachment", - "@type": "@id" - }, - "bcc": { - "@id": "as:bcc", - "@type": "@id" - }, - "bto": { - "@id": "as:bto", - "@type": "@id" - }, - "cc": { - "@id": "as:cc", - "@type": "@id" - }, - "context": { - "@id": "as:context", - "@type": "@id" - }, - "current": { - "@id": "as:current", - "@type": "@id" - }, - "first": { - "@id": "as:first", - "@type": "@id" - }, - "generator": { - "@id": "as:generator", - "@type": "@id" - }, - "icon": { - "@id": "as:icon", - "@type": "@id" - }, - "image": { - "@id": "as:image", - "@type": "@id" - }, - "inReplyTo": { - "@id": "as:inReplyTo", - "@type": "@id" - }, - "items": { - "@id": "as:items", - "@type": "@id" - }, - "instrument": { - "@id": "as:instrument", - "@type": "@id" - }, - "orderedItems": { - "@id": "as:items", - "@type": "@id", - "@container": "@list" - }, - "last": { - "@id": "as:last", - "@type": "@id" - }, - "location": { - "@id": "as:location", - "@type": "@id" - }, - "next": { - "@id": "as:next", - "@type": "@id" - }, - "object": { - "@id": "as:object", - "@type": "@id" - }, - "oneOf": { - "@id": "as:oneOf", - "@type": "@id" - }, - "anyOf": { - "@id": "as:anyOf", - "@type": "@id" - }, - "closed": { - "@id": "as:closed", - "@type": "xsd:dateTime" - }, - "origin": { - "@id": "as:origin", - "@type": "@id" - }, - "accuracy": { - "@id": "as:accuracy", - "@type": "xsd:float" - }, - "prev": { - "@id": "as:prev", - "@type": "@id" - }, - "preview": { - "@id": "as:preview", - "@type": "@id" - }, - "replies": { - "@id": "as:replies", - "@type": "@id" - }, - "result": { - "@id": "as:result", - "@type": "@id" - }, - "audience": { - "@id": "as:audience", - "@type": "@id" - }, - "partOf": { - "@id": "as:partOf", - "@type": "@id" - }, - "tag": { - "@id": "as:tag", - "@type": "@id" - }, - "target": { - "@id": "as:target", - "@type": "@id" - }, - "to": { - "@id": "as:to", - "@type": "@id" - }, - "url": { - "@id": "as:url", - "@type": "@id" - }, - "altitude": { - "@id": "as:altitude", - "@type": "xsd:float" - }, - "content": "as:content", - "contentMap": { - "@id": "as:content", - "@container": "@language" - }, - "name": "as:name", - "nameMap": { - "@id": "as:name", - "@container": "@language" - }, - "duration": { - "@id": "as:duration", - "@type": "xsd:duration" - }, - "endTime": { - "@id": "as:endTime", - "@type": "xsd:dateTime" - }, - "height": { - "@id": "as:height", - "@type": "xsd:nonNegativeInteger" - }, - "href": { - "@id": "as:href", - "@type": "@id" - }, - "hreflang": "as:hreflang", - "latitude": { - "@id": "as:latitude", - "@type": "xsd:float" - }, - "longitude": { - "@id": "as:longitude", - "@type": "xsd:float" - }, - "mediaType": "as:mediaType", - "published": { - "@id": "as:published", - "@type": "xsd:dateTime" - }, - "radius": { - "@id": "as:radius", - "@type": "xsd:float" - }, - "rel": "as:rel", - "startIndex": { - "@id": "as:startIndex", - "@type": "xsd:nonNegativeInteger" - }, - "startTime": { - "@id": "as:startTime", - "@type": "xsd:dateTime" - }, - "summary": "as:summary", - "summaryMap": { - "@id": "as:summary", - "@container": "@language" - }, - "totalItems": { - "@id": "as:totalItems", - "@type": "xsd:nonNegativeInteger" - }, - "units": "as:units", - "updated": { - "@id": "as:updated", - "@type": "xsd:dateTime" - }, - "width": { - "@id": "as:width", - "@type": "xsd:nonNegativeInteger" - }, - "describes": { - "@id": "as:describes", - "@type": "@id" - }, - "formerType": { - "@id": "as:formerType", - "@type": "@id" - }, - "deleted": { - "@id": "as:deleted", - "@type": "xsd:dateTime" - }, - "inbox": { - "@id": "ldp:inbox", - "@type": "@id" - }, - "outbox": { - "@id": "as:outbox", - "@type": "@id" - }, - "following": { - "@id": "as:following", - "@type": "@id" - }, - "followers": { - "@id": "as:followers", - "@type": "@id" - }, - "streams": { - "@id": "as:streams", - "@type": "@id" - }, - "preferredUsername": "as:preferredUsername", - "endpoints": { - "@id": "as:endpoints", - "@type": "@id" - }, - "uploadMedia": { - "@id": "as:uploadMedia", - "@type": "@id" - }, - "proxyUrl": { - "@id": "as:proxyUrl", - "@type": "@id" - }, - "liked": { - "@id": "as:liked", - "@type": "@id" - }, - "oauthAuthorizationEndpoint": { - "@id": "as:oauthAuthorizationEndpoint", - "@type": "@id" - }, - "oauthTokenEndpoint": { - "@id": "as:oauthTokenEndpoint", - "@type": "@id" - }, - "provideClientKey": { - "@id": "as:provideClientKey", - "@type": "@id" - }, - "signClientKey": { - "@id": "as:signClientKey", - "@type": "@id" - }, - "sharedInbox": { - "@id": "as:sharedInbox", - "@type": "@id" - }, - "Public": { - "@id": "as:Public", - "@type": "@id" - }, - "source": "as:source", - "likes": { - "@id": "as:likes", - "@type": "@id" - }, - "shares": { - "@id": "as:shares", - "@type": "@id" - } - } -} diff --git a/spec/fixtures/requests/json-ld.identity.txt b/spec/fixtures/requests/json-ld.identity.txt deleted file mode 100644 index 8810526cb..000000000 --- a/spec/fixtures/requests/json-ld.identity.txt +++ /dev/null @@ -1,100 +0,0 @@ -HTTP/1.1 200 OK -Accept-Ranges: bytes -Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Accept-Encoding -Access-Control-Allow-Origin: * -Content-Type: application/ld+json -Date: Tue, 01 May 2018 23:28:21 GMT -Etag: "e26-547a6fc75b04a-gzip" -Last-Modified: Fri, 03 Feb 2017 21:30:09 GMT -Server: Apache/2.4.7 (Ubuntu) -Vary: Accept-Encoding -Transfer-Encoding: chunked - -{ - "@context": { - "id": "@id", - "type": "@type", - - "cred": "https://w3id.org/credentials#", - "dc": "http://purl.org/dc/terms/", - "identity": "https://w3id.org/identity#", - "perm": "https://w3id.org/permissions#", - "ps": "https://w3id.org/payswarm#", - "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", - "rdfs": "http://www.w3.org/2000/01/rdf-schema#", - "sec": "https://w3id.org/security#", - "schema": "http://schema.org/", - "xsd": "http://www.w3.org/2001/XMLSchema#", - - "Group": "https://www.w3.org/ns/activitystreams#Group", - - "claim": {"@id": "cred:claim", "@type": "@id"}, - "credential": {"@id": "cred:credential", "@type": "@id"}, - "issued": {"@id": "cred:issued", "@type": "xsd:dateTime"}, - "issuer": {"@id": "cred:issuer", "@type": "@id"}, - "recipient": {"@id": "cred:recipient", "@type": "@id"}, - "Credential": "cred:Credential", - "CryptographicKeyCredential": "cred:CryptographicKeyCredential", - - "about": {"@id": "schema:about", "@type": "@id"}, - "address": {"@id": "schema:address", "@type": "@id"}, - "addressCountry": "schema:addressCountry", - "addressLocality": "schema:addressLocality", - "addressRegion": "schema:addressRegion", - "comment": "rdfs:comment", - "created": {"@id": "dc:created", "@type": "xsd:dateTime"}, - "creator": {"@id": "dc:creator", "@type": "@id"}, - "description": "schema:description", - "email": "schema:email", - "familyName": "schema:familyName", - "givenName": "schema:givenName", - "image": {"@id": "schema:image", "@type": "@id"}, - "label": "rdfs:label", - "name": "schema:name", - "postalCode": "schema:postalCode", - "streetAddress": "schema:streetAddress", - "title": "dc:title", - "url": {"@id": "schema:url", "@type": "@id"}, - "Person": "schema:Person", - "PostalAddress": "schema:PostalAddress", - "Organization": "schema:Organization", - - "identityService": {"@id": "identity:identityService", "@type": "@id"}, - "idp": {"@id": "identity:idp", "@type": "@id"}, - "Identity": "identity:Identity", - - "paymentProcessor": "ps:processor", - "preferences": {"@id": "ps:preferences", "@type": "@vocab"}, - - "cipherAlgorithm": "sec:cipherAlgorithm", - "cipherData": "sec:cipherData", - "cipherKey": "sec:cipherKey", - "digestAlgorithm": "sec:digestAlgorithm", - "digestValue": "sec:digestValue", - "domain": "sec:domain", - "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, - "initializationVector": "sec:initializationVector", - "member": {"@id": "schema:member", "@type": "@id"}, - "memberOf": {"@id": "schema:memberOf", "@type": "@id"}, - "nonce": "sec:nonce", - "normalizationAlgorithm": "sec:normalizationAlgorithm", - "owner": {"@id": "sec:owner", "@type": "@id"}, - "password": "sec:password", - "privateKey": {"@id": "sec:privateKey", "@type": "@id"}, - "privateKeyPem": "sec:privateKeyPem", - "publicKey": {"@id": "sec:publicKey", "@type": "@id"}, - "publicKeyPem": "sec:publicKeyPem", - "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"}, - "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"}, - "signature": "sec:signature", - "signatureAlgorithm": "sec:signatureAlgorithm", - "signatureValue": "sec:signatureValue", - "CryptographicKey": "sec:Key", - "EncryptedMessage": "sec:EncryptedMessage", - "GraphSignature2012": "sec:GraphSignature2012", - "LinkedDataSignature2015": "sec:LinkedDataSignature2015", - - "accessControl": {"@id": "perm:accessControl", "@type": "@id"}, - "writePermission": {"@id": "perm:writePermission", "@type": "@id"} - } -} diff --git a/spec/fixtures/requests/json-ld.security.txt b/spec/fixtures/requests/json-ld.security.txt deleted file mode 100644 index 0d29903e6..000000000 --- a/spec/fixtures/requests/json-ld.security.txt +++ /dev/null @@ -1,61 +0,0 @@ -HTTP/1.1 200 OK -Accept-Ranges: bytes -Access-Control-Allow-Headers: DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Accept-Encoding -Access-Control-Allow-Origin: * -Content-Type: application/ld+json -Date: Wed, 02 May 2018 16:25:32 GMT -Etag: "7e3-5651ec0f7c5ed-gzip" -Last-Modified: Tue, 13 Feb 2018 21:34:04 GMT -Server: Apache/2.4.7 (Ubuntu) -Vary: Accept-Encoding -Content-Length: 2019 - -{ - "@context": { - "id": "@id", - "type": "@type", - - "dc": "http://purl.org/dc/terms/", - "sec": "https://w3id.org/security#", - "xsd": "http://www.w3.org/2001/XMLSchema#", - - "EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016", - "Ed25519Signature2018": "sec:Ed25519Signature2018", - "EncryptedMessage": "sec:EncryptedMessage", - "GraphSignature2012": "sec:GraphSignature2012", - "LinkedDataSignature2015": "sec:LinkedDataSignature2015", - "LinkedDataSignature2016": "sec:LinkedDataSignature2016", - "CryptographicKey": "sec:Key", - - "authenticationTag": "sec:authenticationTag", - "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm", - "cipherAlgorithm": "sec:cipherAlgorithm", - "cipherData": "sec:cipherData", - "cipherKey": "sec:cipherKey", - "created": {"@id": "dc:created", "@type": "xsd:dateTime"}, - "creator": {"@id": "dc:creator", "@type": "@id"}, - "digestAlgorithm": "sec:digestAlgorithm", - "digestValue": "sec:digestValue", - "domain": "sec:domain", - "encryptionKey": "sec:encryptionKey", - "expiration": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, - "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"}, - "initializationVector": "sec:initializationVector", - "iterationCount": "sec:iterationCount", - "nonce": "sec:nonce", - "normalizationAlgorithm": "sec:normalizationAlgorithm", - "owner": {"@id": "sec:owner", "@type": "@id"}, - "password": "sec:password", - "privateKey": {"@id": "sec:privateKey", "@type": "@id"}, - "privateKeyPem": "sec:privateKeyPem", - "publicKey": {"@id": "sec:publicKey", "@type": "@id"}, - "publicKeyBase58": "sec:publicKeyBase58", - "publicKeyPem": "sec:publicKeyPem", - "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"}, - "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"}, - "salt": "sec:salt", - "signature": "sec:signature", - "signatureAlgorithm": "sec:signingAlgorithm", - "signatureValue": "sec:signatureValue" - } -} diff --git a/spec/lib/activitypub/linked_data_signature_spec.rb b/spec/lib/activitypub/linked_data_signature_spec.rb index 6a6ad1a70..d5b713b34 100644 --- a/spec/lib/activitypub/linked_data_signature_spec.rb +++ b/spec/lib/activitypub/linked_data_signature_spec.rb @@ -18,10 +18,6 @@ RSpec.describe ActivityPub::LinkedDataSignature do let(:json) { raw_json.merge('signature' => signature) } - before do - stub_jsonld_contexts! - end - describe '#verify_actor!' do context 'when signature matches' do let(:raw_signature) do diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 0bbf8fc52..8d9677f6c 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -100,14 +100,6 @@ RSpec.configure do |config| Capybara.current_driver = :rack_test end - config.before :each, type: :controller do - stub_jsonld_contexts! - end - - config.before :each, type: :service do - stub_jsonld_contexts! - end - config.before :suite do if RUN_SYSTEM_SPECS Webpacker.compile @@ -198,9 +190,3 @@ def stub_reset_connection_pools allow(ActiveRecord::Base).to receive(:establish_connection) allow(RedisConfiguration).to receive(:establish_pool) end - -def stub_jsonld_contexts! - stub_request(:get, 'https://www.w3.org/ns/activitystreams').to_return(request_fixture('json-ld.activitystreams.txt')) - stub_request(:get, 'https://w3id.org/identity/v1').to_return(request_fixture('json-ld.identity.txt')) - stub_request(:get, 'https://w3id.org/security/v1').to_return(request_fixture('json-ld.security.txt')) -end diff --git a/spec/services/after_block_domain_from_account_service_spec.rb b/spec/services/after_block_domain_from_account_service_spec.rb index 9bfaa3580..05af12599 100644 --- a/spec/services/after_block_domain_from_account_service_spec.rb +++ b/spec/services/after_block_domain_from_account_service_spec.rb @@ -9,7 +9,6 @@ RSpec.describe AfterBlockDomainFromAccountService, type: :service do let!(:alice) { Fabricate(:account, username: 'alice') } before do - stub_jsonld_contexts! allow(ActivityPub::DeliveryWorker).to receive(:perform_async) end From f5bc1f20e2ba4bafcd8e57c01413c60002696b58 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 16 Oct 2023 09:41:23 -0400 Subject: [PATCH 39/69] Add coverage for `ExistingUsernameValidator` (#25592) Co-authored-by: Claire --- app/validators/existing_username_validator.rb | 37 ++++++--- .../existing_username_validator_spec.rb | 83 +++++++++++++++++++ 2 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 spec/validators/existing_username_validator_spec.rb diff --git a/app/validators/existing_username_validator.rb b/app/validators/existing_username_validator.rb index 037d92f39..09d53ca68 100644 --- a/app/validators/existing_username_validator.rb +++ b/app/validators/existing_username_validator.rb @@ -2,25 +2,40 @@ class ExistingUsernameValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - return if value.blank? + @value = value + return if @value.blank? - usernames_and_domains = value.split(',').filter_map do |str| - username, domain = str.strip.gsub(/\A@/, '').split('@', 2) + if options[:multiple] + record.errors.add(attribute, not_found_multiple_message) if usernames_with_no_accounts.any? + elsif usernames_with_no_accounts.any? || usernames_and_domains.size > 1 + record.errors.add(attribute, not_found_message) + end + end + + private + + def usernames_and_domains + @value.split(',').filter_map do |string| + username, domain = string.strip.gsub(/\A@/, '').split('@', 2) domain = nil if TagManager.instance.local_domain?(domain) next if username.blank? - [str, username, domain] + [string, username, domain] end + end - usernames_with_no_accounts = usernames_and_domains.filter_map do |(str, username, domain)| - str unless Account.find_remote(username, domain) + def usernames_with_no_accounts + usernames_and_domains.filter_map do |(string, username, domain)| + string unless Account.find_remote(username, domain) end + end - if options[:multiple] - record.errors.add(attribute, I18n.t('existing_username_validator.not_found_multiple', usernames: usernames_with_no_accounts.join(', '))) if usernames_with_no_accounts.any? - elsif usernames_with_no_accounts.any? || usernames_and_domains.size > 1 - record.errors.add(attribute, I18n.t('existing_username_validator.not_found')) - end + def not_found_multiple_message + I18n.t('existing_username_validator.not_found_multiple', usernames: usernames_with_no_accounts.join(', ')) + end + + def not_found_message + I18n.t('existing_username_validator.not_found') end end diff --git a/spec/validators/existing_username_validator_spec.rb b/spec/validators/existing_username_validator_spec.rb new file mode 100644 index 000000000..4f1dd55a1 --- /dev/null +++ b/spec/validators/existing_username_validator_spec.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe ExistingUsernameValidator do + let(:record_class) do + Class.new do + include ActiveModel::Validations + attr_accessor :contact, :friends + + def self.name + 'Record' + end + + validates :contact, existing_username: true + validates :friends, existing_username: { multiple: true } + end + end + let(:record) { record_class.new } + + describe '#validate_each' do + context 'with a nil value' do + it 'does not add errors' do + record.contact = nil + + expect(record).to be_valid + expect(record.errors).to be_empty + end + end + + context 'when there are no accounts' do + it 'adds errors to the record' do + record.contact = 'user@example.com' + + expect(record).to_not be_valid + expect(record.errors.first.attribute).to eq(:contact) + expect(record.errors.first.type).to eq I18n.t('existing_username_validator.not_found') + end + end + + context 'when there are accounts' do + before { Fabricate(:account, domain: 'example.com', username: 'user') } + + context 'when the value does not match' do + it 'adds errors to the record' do + record.contact = 'friend@other.host' + + expect(record).to_not be_valid + expect(record.errors.first.attribute).to eq(:contact) + expect(record.errors.first.type).to eq I18n.t('existing_username_validator.not_found') + end + + context 'when multiple is true' do + it 'adds errors to the record' do + record.friends = 'friend@other.host' + + expect(record).to_not be_valid + expect(record.errors.first.attribute).to eq(:friends) + expect(record.errors.first.type).to eq I18n.t('existing_username_validator.not_found_multiple', usernames: 'friend@other.host') + end + end + end + + context 'when the value does match' do + it 'does not add errors to the record' do + record.contact = 'user@example.com' + + expect(record).to be_valid + expect(record.errors).to be_empty + end + + context 'when multiple is true' do + it 'does not add errors to the record' do + record.friends = 'user@example.com' + + expect(record).to be_valid + expect(record.errors).to be_empty + end + end + end + end + end +end From d9caa6ed240d02e4d0cd385ac558abfdd311efba Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 16 Oct 2023 10:49:12 -0300 Subject: [PATCH 40/69] Migrate to request specs in `/api/v1/admin/accounts` (#25563) --- .rubocop_todo.yml | 2 - .../api/v1/admin/accounts_controller_spec.rb | 174 -------- spec/requests/api/v1/admin/accounts_spec.rb | 401 ++++++++++++++++++ 3 files changed, 401 insertions(+), 176 deletions(-) delete mode 100644 spec/controllers/api/v1/admin/accounts_controller_spec.rb create mode 100644 spec/requests/api/v1/admin/accounts_spec.rb diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d58bee4ba..29d9f484c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -178,7 +178,6 @@ RSpec/LetSetup: - 'spec/controllers/admin/reports/actions_controller_spec.rb' - 'spec/controllers/admin/statuses_controller_spec.rb' - 'spec/controllers/api/v1/accounts/statuses_controller_spec.rb' - - 'spec/controllers/api/v1/admin/accounts_controller_spec.rb' - 'spec/controllers/api/v1/filters_controller_spec.rb' - 'spec/controllers/api/v2/admin/accounts_controller_spec.rb' - 'spec/controllers/api/v2/filters/keywords_controller_spec.rb' @@ -415,7 +414,6 @@ Rails/SkipsModelValidations: - 'lib/mastodon/cli/accounts.rb' - 'lib/mastodon/cli/main.rb' - 'lib/mastodon/cli/maintenance.rb' - - 'spec/controllers/api/v1/admin/accounts_controller_spec.rb' - 'spec/lib/activitypub/activity/follow_spec.rb' - 'spec/services/follow_service_spec.rb' - 'spec/services/update_account_service_spec.rb' diff --git a/spec/controllers/api/v1/admin/accounts_controller_spec.rb b/spec/controllers/api/v1/admin/accounts_controller_spec.rb deleted file mode 100644 index 4b56b2547..000000000 --- a/spec/controllers/api/v1/admin/accounts_controller_spec.rb +++ /dev/null @@ -1,174 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Api::V1::Admin::AccountsController do - render_views - - let(:role) { UserRole.find_by(name: 'Moderator') } - let(:user) { Fabricate(:user, role: role) } - let(:scopes) { 'admin:read admin:write' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:account) { Fabricate(:account) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'GET #index' do - let!(:remote_account) { Fabricate(:account, domain: 'example.org') } - let!(:other_remote_account) { Fabricate(:account, domain: 'foo.bar') } - let!(:suspended_account) { Fabricate(:account, suspended: true) } - let!(:suspended_remote) { Fabricate(:account, domain: 'foo.bar', suspended: true) } - let!(:disabled_account) { Fabricate(:user, disabled: true).account } - let!(:pending_account) { Fabricate(:user, approved: false).account } - let!(:admin_account) { user.account } - - let(:params) { {} } - - before do - pending_account.user.update(approved: false) - get :index, params: params - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - - [ - [{ active: 'true', local: 'true', staff: 'true' }, [:admin_account]], - [{ by_domain: 'example.org', remote: 'true' }, [:remote_account]], - [{ suspended: 'true' }, [:suspended_account]], - [{ disabled: 'true' }, [:disabled_account]], - [{ pending: 'true' }, [:pending_account]], - ].each do |params, expected_results| - context "when called with #{params.inspect}" do - let(:params) { params } - - it "returns the correct accounts (#{expected_results.inspect})", :aggregate_failures do - expect(response).to have_http_status(200) - - json = body_as_json - - expect(json.map { |a| a[:id].to_i }).to eq(expected_results.map { |symbol| send(symbol).id }) - end - end - end - end - - describe 'GET #show' do - before do - get :show, params: { id: account.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - - it 'returns http success' do - expect(response).to have_http_status(200) - end - end - - describe 'POST #approve' do - before do - account.user.update(approved: false) - post :approve, params: { id: account.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - - it 'approves user', :aggregate_failures do - expect(response).to have_http_status(200) - expect(account.reload.user_approved?).to be true - - log_item = Admin::ActionLog.last - - expect(log_item).to_not be_nil - expect(log_item.action).to eq :approve - expect(log_item.account_id).to eq user.account_id - expect(log_item.target_id).to eq account.user.id - end - end - - describe 'POST #reject' do - before do - account.user.update(approved: false) - post :reject, params: { id: account.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - - it 'removes user', :aggregate_failures do - expect(response).to have_http_status(200) - expect(User.where(id: account.user.id).count).to eq 0 - - log_item = Admin::ActionLog.last - - expect(log_item).to_not be_nil - expect(log_item.action).to eq :reject - expect(log_item.account_id).to eq user.account_id - expect(log_item.target_id).to eq account.user.id - end - end - - describe 'POST #enable' do - before do - account.user.update(disabled: true) - post :enable, params: { id: account.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - - it 'enables user', :aggregate_failures do - expect(response).to have_http_status(200) - expect(account.reload.user_disabled?).to be false - end - end - - describe 'POST #unsuspend' do - before do - account.suspend! - post :unsuspend, params: { id: account.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - - it 'unsuspends account', :aggregate_failures do - expect(response).to have_http_status(200) - expect(account.reload.suspended?).to be false - end - end - - describe 'POST #unsensitive' do - before do - account.touch(:sensitized_at) - post :unsensitive, params: { id: account.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - - it 'unsensitizes account', :aggregate_failures do - expect(response).to have_http_status(200) - expect(account.reload.sensitized?).to be false - end - end - - describe 'POST #unsilence' do - before do - account.touch(:silenced_at) - post :unsilence, params: { id: account.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - - it 'unsilences account', :aggregate_failures do - expect(response).to have_http_status(200) - expect(account.reload.silenced?).to be false - end - end -end diff --git a/spec/requests/api/v1/admin/accounts_spec.rb b/spec/requests/api/v1/admin/accounts_spec.rb new file mode 100644 index 000000000..8e158f623 --- /dev/null +++ b/spec/requests/api/v1/admin/accounts_spec.rb @@ -0,0 +1,401 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Accounts' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read:accounts admin:write:accounts' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/admin/accounts' do + subject do + get '/api/v1/admin/accounts', headers: headers, params: params + end + + shared_examples 'a successful request' do + it 'returns the correct accounts', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json.pluck(:id)).to match_array(expected_results.map { |a| a.id.to_s }) + end + end + + let!(:remote_account) { Fabricate(:account, domain: 'example.org') } + let!(:suspended_account) { Fabricate(:account, suspended: true) } + let!(:disabled_account) { Fabricate(:user, disabled: true).account } + let!(:pending_account) { Fabricate(:user, approved: false).account } + let!(:admin_account) { user.account } + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read read:accounts admin:write admin:write:accounts' + it_behaves_like 'forbidden for wrong role', '' + + context 'when requesting active local staff accounts' do + let(:expected_results) { [admin_account] } + let(:params) { { active: 'true', local: 'true', staff: 'true' } } + + it_behaves_like 'a successful request' + end + + context 'when requesting remote accounts from a specified domain' do + let(:expected_results) { [remote_account] } + let(:params) { { by_domain: 'example.org', remote: 'true' } } + + before do + Fabricate(:account, domain: 'foo.bar') + end + + it_behaves_like 'a successful request' + end + + context 'when requesting suspended accounts' do + let(:expected_results) { [suspended_account] } + let(:params) { { suspended: 'true' } } + + before do + Fabricate(:account, domain: 'foo.bar', suspended: true) + end + + it_behaves_like 'a successful request' + end + + context 'when requesting disabled accounts' do + let(:expected_results) { [disabled_account] } + let(:params) { { disabled: 'true' } } + + it_behaves_like 'a successful request' + end + + context 'when requesting pending accounts' do + let(:expected_results) { [pending_account] } + let(:params) { { pending: 'true' } } + + before do + pending_account.user.update(approved: false) + end + + it_behaves_like 'a successful request' + end + + context 'when no parameter is given' do + let(:expected_results) { [disabled_account, pending_account, admin_account] } + + it_behaves_like 'a successful request' + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of accounts', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json.size).to eq(params[:limit]) + end + end + end + + describe 'GET /api/v1/admin/accounts/:id' do + subject do + get "/api/v1/admin/accounts/#{account.id}", headers: headers + end + + let(:account) { Fabricate(:account) } + + it_behaves_like 'forbidden for wrong scope', 'read read:accounts admin:write admin:write:accounts' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns the requested account successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to match( + a_hash_including(id: account.id.to_s, username: account.username, email: account.user.email) + ) + end + + context 'when the account is not found' do + it 'returns http not found' do + get '/api/v1/admin/accounts/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/accounts/:id/approve' do + subject do + post "/api/v1/admin/accounts/#{account.id}/approve", headers: headers + end + + let(:account) { Fabricate(:account) } + + context 'when the account is pending' do + before do + account.user.update(approved: false) + end + + it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read' + it_behaves_like 'forbidden for wrong role', '' + + it 'approves the user successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(account.reload.user_approved?).to be(true) + end + + it 'logs action', :aggregate_failures do + subject + + log_item = Admin::ActionLog.last + + expect(log_item).to be_present + expect(log_item.action).to eq :approve + expect(log_item.account_id).to eq user.account_id + expect(log_item.target_id).to eq account.user.id + end + end + + context 'when the account is already approved' do + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + context 'when the account is not found' do + it 'returns http not found' do + post '/api/v1/admin/accounts/-1/approve', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/accounts/:id/reject' do + subject do + post "/api/v1/admin/accounts/#{account.id}/reject", headers: headers + end + + let(:account) { Fabricate(:account) } + + context 'when the account is pending' do + before do + account.user.update(approved: false) + end + + it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read' + it_behaves_like 'forbidden for wrong role', '' + + it 'removes the user successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(User.where(id: account.user.id)).to_not exist + end + + it 'logs action', :aggregate_failures do + subject + + log_item = Admin::ActionLog.last + + expect(log_item).to be_present + expect(log_item.action).to eq :reject + expect(log_item.account_id).to eq user.account_id + expect(log_item.target_id).to eq account.user.id + end + end + + context 'when account is already approved' do + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + context 'when the account is not found' do + it 'returns http not found' do + post '/api/v1/admin/accounts/-1/reject', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/accounts/:id/enable' do + subject do + post "/api/v1/admin/accounts/#{account.id}/enable", headers: headers + end + + let(:account) { Fabricate(:account) } + + before do + account.user.update(disabled: true) + end + + it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read' + it_behaves_like 'forbidden for wrong role', '' + + it 'enables the user successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(account.reload.user_disabled?).to be false + end + + context 'when the account is not found' do + it 'returns http not found' do + post '/api/v1/admin/accounts/-1/enable', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/accounts/:id/unsuspend' do + subject do + post "/api/v1/admin/accounts/#{account.id}/unsuspend", headers: headers + end + + let(:account) { Fabricate(:account) } + + context 'when the account is suspended' do + before do + account.suspend! + end + + it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read' + it_behaves_like 'forbidden for wrong role', '' + + it 'unsuspends the account successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(account.reload.suspended?).to be false + end + end + + context 'when the account is not suspended' do + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + context 'when the account is not found' do + it 'returns http not found' do + post '/api/v1/admin/accounts/-1/unsuspend', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/accounts/:id/unsensitive' do + subject do + post "/api/v1/admin/accounts/#{account.id}/unsensitive", headers: headers + end + + let(:account) { Fabricate(:account) } + + before do + account.update(sensitized_at: 10.days.ago) + end + + it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read' + it_behaves_like 'forbidden for wrong role', '' + + it 'unsensitizes the account successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(account.reload.sensitized?).to be false + end + + context 'when the account is not found' do + it 'returns http not found' do + post '/api/v1/admin/accounts/-1/unsensitive', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/accounts/:id/unsilence' do + subject do + post "/api/v1/admin/accounts/#{account.id}/unsilence", headers: headers + end + + let(:account) { Fabricate(:account) } + + before do + account.update(silenced_at: 3.days.ago) + end + + it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read' + it_behaves_like 'forbidden for wrong role', '' + + it 'unsilences the account successfully', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(account.reload.silenced?).to be false + end + + context 'when the account is not found' do + it 'returns http not found' do + post '/api/v1/admin/accounts/-1/unsilence', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'DELETE /api/v1/admin/accounts/:id' do + subject do + delete "/api/v1/admin/accounts/#{account.id}", headers: headers + end + + let(:account) { Fabricate(:account) } + + context 'when account is suspended' do + before do + account.suspend! + end + + it_behaves_like 'forbidden for wrong scope', 'write write:accounts read admin:read' + it_behaves_like 'forbidden for wrong role', '' + + it 'deletes the account successfully', :aggregate_failures do + allow(Admin::AccountDeletionWorker).to receive(:perform_async) + subject + + expect(response).to have_http_status(200) + expect(Admin::AccountDeletionWorker).to have_received(:perform_async).with(account.id).once + end + end + + context 'when account is not suspended' do + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + context 'when the account is not found' do + it 'returns http not found' do + delete '/api/v1/admin/accounts/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end +end From 8e6116503d697ed3b9206ae10dd913f8adf64ae8 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 16 Oct 2023 11:03:35 -0300 Subject: [PATCH 41/69] Migrate to request specs in `/api/v1/blocks` (#25517) --- .../api/v1/blocks_controller_spec.rb | 68 ---------------- spec/requests/api/v1/blocks_spec.rb | 80 +++++++++++++++++++ 2 files changed, 80 insertions(+), 68 deletions(-) delete mode 100644 spec/controllers/api/v1/blocks_controller_spec.rb create mode 100644 spec/requests/api/v1/blocks_spec.rb diff --git a/spec/controllers/api/v1/blocks_controller_spec.rb b/spec/controllers/api/v1/blocks_controller_spec.rb deleted file mode 100644 index ba63560a9..000000000 --- a/spec/controllers/api/v1/blocks_controller_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Api::V1::BlocksController do - render_views - - let(:user) { Fabricate(:user) } - let(:scopes) { 'read:blocks' } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - - before { allow(controller).to receive(:doorkeeper_token) { token } } - - describe 'GET #index' do - it 'limits according to limit parameter', :aggregate_failures do - Array.new(2) { Fabricate(:block, account: user.account) } - get :index, params: { limit: 1 } - - expect(response).to have_http_status(200) - expect(body_as_json.size).to eq 1 - end - - it 'queries blocks in range according to max_id', :aggregate_failures do - blocks = Array.new(2) { Fabricate(:block, account: user.account) } - - get :index, params: { max_id: blocks[1] } - - expect(response).to have_http_status(200) - expect(body_as_json.size).to eq 1 - expect(body_as_json[0][:id]).to eq blocks[0].target_account_id.to_s - end - - it 'queries blocks in range according to since_id', :aggregate_failures do - blocks = Array.new(2) { Fabricate(:block, account: user.account) } - - get :index, params: { since_id: blocks[0] } - - expect(response).to have_http_status(200) - expect(body_as_json.size).to eq 1 - expect(body_as_json[0][:id]).to eq blocks[1].target_account_id.to_s - end - - it 'sets pagination header for next path', :aggregate_failures do - blocks = Array.new(2) { Fabricate(:block, account: user.account) } - get :index, params: { limit: 1, since_id: blocks[0] } - - expect(response).to have_http_status(200) - expect(response.headers['Link'].find_link(%w(rel next)).href).to eq api_v1_blocks_url(limit: 1, max_id: blocks[1]) - end - - it 'sets pagination header for previous path', :aggregate_failures do - block = Fabricate(:block, account: user.account) - get :index - - expect(response).to have_http_status(200) - expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq api_v1_blocks_url(since_id: block) - end - - context 'with wrong scopes' do - let(:scopes) { 'write:blocks' } - - it 'returns http forbidden' do - get :index - expect(response).to have_http_status(403) - end - end - end -end diff --git a/spec/requests/api/v1/blocks_spec.rb b/spec/requests/api/v1/blocks_spec.rb new file mode 100644 index 000000000..62543157c --- /dev/null +++ b/spec/requests/api/v1/blocks_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Blocks' do + let(:user) { Fabricate(:user) } + let(:scopes) { 'read:blocks' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/blocks' do + subject do + get '/api/v1/blocks', headers: headers, params: params + end + + let!(:blocks) { Fabricate.times(3, :block, account: user.account) } + let(:params) { {} } + + let(:expected_response) do + blocks.map { |block| a_hash_including(id: block.target_account.id.to_s, username: block.target_account.username) } + end + + it_behaves_like 'forbidden for wrong scope', 'write write:blocks' + + it 'returns the blocked accounts', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to match_array(expected_response) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of blocked accounts' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + + it 'sets the correct pagination header for the prev path' do + subject + + expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_blocks_url(limit: params[:limit], since_id: blocks.last.id)) + end + + it 'sets the correct pagination header for the next path' do + subject + + expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_blocks_url(limit: params[:limit], max_id: blocks[1].id)) + end + end + + context 'with max_id param' do + let(:params) { { max_id: blocks[1].id } } + + it 'queries the blocks in range according to max_id', :aggregate_failures do + subject + + response_body = body_as_json + + expect(response_body.size).to be 1 + expect(response_body[0][:id]).to eq(blocks[0].target_account.id.to_s) + end + end + + context 'with since_id param' do + let(:params) { { since_id: blocks[1].id } } + + it 'queries the blocks in range according to since_id', :aggregate_failures do + subject + + response_body = body_as_json + + expect(response_body.size).to be 1 + expect(response_body[0][:id]).to eq(blocks[2].target_account.id.to_s) + end + end + end +end From cafdaec6cfd4d0b4033f0495017651e0a81187ca Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 16 Oct 2023 11:03:48 -0300 Subject: [PATCH 42/69] Migrate to request specs in `/api/v1/lists/:id/accounts` (#25468) --- .../api/v1/lists/accounts_controller_spec.rb | 80 -------- spec/requests/api/v1/lists/accounts_spec.rb | 178 ++++++++++++++++++ 2 files changed, 178 insertions(+), 80 deletions(-) delete mode 100644 spec/controllers/api/v1/lists/accounts_controller_spec.rb create mode 100644 spec/requests/api/v1/lists/accounts_spec.rb diff --git a/spec/controllers/api/v1/lists/accounts_controller_spec.rb b/spec/controllers/api/v1/lists/accounts_controller_spec.rb deleted file mode 100644 index 21e155a50..000000000 --- a/spec/controllers/api/v1/lists/accounts_controller_spec.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::Lists::AccountsController do - render_views - - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:list) { Fabricate(:list, account: user.account) } - - before do - follow = Fabricate(:follow, account: user.account) - list.accounts << follow.target_account - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'GET #index' do - let(:scopes) { 'read:lists' } - - it 'returns http success' do - get :show, params: { list_id: list.id } - - expect(response).to have_http_status(200) - end - end - - describe 'POST #create' do - let(:scopes) { 'write:lists' } - let(:bob) { Fabricate(:account, username: 'bob') } - - context 'when the added account is followed' do - before do - user.account.follow!(bob) - post :create, params: { list_id: list.id, account_ids: [bob.id] } - end - - it 'adds account to the list', :aggregate_failures do - expect(response).to have_http_status(200) - expect(list.accounts.include?(bob)).to be true - end - end - - context 'when the added account has been sent a follow request' do - before do - user.account.follow_requests.create!(target_account: bob) - post :create, params: { list_id: list.id, account_ids: [bob.id] } - end - - it 'adds account to the list', :aggregate_failures do - expect(response).to have_http_status(200) - expect(list.accounts.include?(bob)).to be true - end - end - - context 'when the added account is not followed' do - before do - post :create, params: { list_id: list.id, account_ids: [bob.id] } - end - - it 'does not add the account to the list', :aggregate_failures do - expect(response).to have_http_status(404) - expect(list.accounts.include?(bob)).to be false - end - end - end - - describe 'DELETE #destroy' do - let(:scopes) { 'write:lists' } - - before do - delete :destroy, params: { list_id: list.id, account_ids: [list.accounts.first.id] } - end - - it 'removes account from the list', :aggregate_failures do - expect(response).to have_http_status(200) - expect(list.accounts.count).to eq 0 - end - end -end diff --git a/spec/requests/api/v1/lists/accounts_spec.rb b/spec/requests/api/v1/lists/accounts_spec.rb new file mode 100644 index 000000000..4d2a168b3 --- /dev/null +++ b/spec/requests/api/v1/lists/accounts_spec.rb @@ -0,0 +1,178 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Accounts' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:lists write:lists' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/lists/:id/accounts' do + subject do + get "/api/v1/lists/#{list.id}/accounts", headers: headers, params: params + end + + let(:params) { { limit: 0 } } + let(:list) { Fabricate(:list, account: user.account) } + let(:accounts) { Fabricate.times(3, :account) } + + let(:expected_response) do + accounts.map do |account| + a_hash_including(id: account.id.to_s, username: account.username, acct: account.acct) + end + end + + before do + accounts.each { |account| user.account.follow!(account) } + list.accounts << accounts + end + + it_behaves_like 'forbidden for wrong scope', 'write write:lists' + + it 'returns the accounts in the requested list', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json).to match_array(expected_response) + end + + context 'with limit param' do + let(:params) { { limit: 1 } } + + it 'returns only the requested number of accounts' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + end + + describe 'POST /api/v1/lists/:id/accounts' do + subject do + post "/api/v1/lists/#{list.id}/accounts", headers: headers, params: params + end + + let(:list) { Fabricate(:list, account: user.account) } + let(:bob) { Fabricate(:account, username: 'bob') } + let(:params) { { account_ids: [bob.id] } } + + it_behaves_like 'forbidden for wrong scope', 'read read:lists' + + context 'when the added account is followed' do + before do + user.account.follow!(bob) + end + + it 'adds account to the list', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(list.accounts).to include(bob) + end + end + + context 'when the added account has been sent a follow request' do + before do + user.account.follow_requests.create!(target_account: bob) + end + + it 'adds account to the list', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(list.accounts).to include(bob) + end + end + + context 'when the added account is not followed' do + it 'does not add the account to the list', :aggregate_failures do + subject + + expect(response).to have_http_status(404) + expect(list.accounts).to_not include(bob) + end + end + + context 'when the list is not owned by the requesting user' do + let(:list) { Fabricate(:list) } + + before do + user.account.follow!(bob) + end + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + + context 'when account is already in the list' do + before do + user.account.follow!(bob) + list.accounts << bob + end + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + describe 'DELETE /api/v1/lists/:id/accounts' do + subject do + delete "/api/v1/lists/#{list.id}/accounts", headers: headers, params: params + end + + context 'when the list is owned by the requesting user' do + let(:list) { Fabricate(:list, account: user.account) } + let(:bob) { Fabricate(:account, username: 'bob') } + let(:peter) { Fabricate(:account, username: 'peter') } + let(:params) { { account_ids: [bob.id] } } + + before do + user.account.follow!(bob) + user.account.follow!(peter) + list.accounts << [bob, peter] + end + + it 'removes the specified account from the list', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(list.accounts).to_not include(bob) + end + + it 'does not remove any other account from the list' do + subject + + expect(list.accounts).to include(peter) + end + + context 'when the specified account is not in the list' do + let(:params) { { account_ids: [0] } } + + it 'does not remove any account from the list', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(list.accounts).to contain_exactly(bob, peter) + end + end + end + + context 'when the list is not owned by the requesting user' do + let(:list) { Fabricate(:list) } + let(:params) { {} } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end +end From c91c0175db1cc8b954a977d29472886234ce9586 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 16 Oct 2023 10:04:29 -0400 Subject: [PATCH 43/69] Extract `card` partial from `disputes/strikes/show` view (#27426) --- app/views/disputes/strikes/_card.html.haml | 38 ++++++++++++++++++ app/views/disputes/strikes/show.html.haml | 46 +--------------------- 2 files changed, 39 insertions(+), 45 deletions(-) create mode 100644 app/views/disputes/strikes/_card.html.haml diff --git a/app/views/disputes/strikes/_card.html.haml b/app/views/disputes/strikes/_card.html.haml new file mode 100644 index 000000000..55551cc7d --- /dev/null +++ b/app/views/disputes/strikes/_card.html.haml @@ -0,0 +1,38 @@ +.strike-card + - unless strike.none_action? + %p= t "user_mailer.warning.explanation.#{strike.action}", instance: Rails.configuration.x.local_domain + - if strike.text.present? + = linkify(strike.text) + - if strike.report && !strike.report.other? + %p + %strong= t('user_mailer.warning.reason') + = t("user_mailer.warning.categories.#{strike.report.category}") + - if strike.report.violation? && strike.report.rule_ids.present? + %ul.strike-card__rules + - strike.report.rules.each do |rule| + %li + %span.strike-card__rules__text= rule.text + - if strike.status_ids.present? && !strike.status_ids.empty? + %p + %strong= t('user_mailer.warning.statuses') + .strike-card__statuses-list + - status_map = strike.statuses.includes(:application, :media_attachments).index_by(&:id) + - strike.status_ids.each do |status_id| + .strike-card__statuses-list__item + - if (status = status_map[status_id.to_i]) + .one-liner + .emojify= one_line_preview(status) + - status.ordered_media_attachments.each do |media_attachment| + %abbr{ title: media_attachment.description } + = fa_icon 'link' + = media_attachment.file_file_name + .strike-card__statuses-list__item__meta + = link_to ActivityPub::TagManager.instance.url_for(status), target: '_blank', rel: 'noopener noreferrer' do + %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) + - unless status.application.nil? + · + = status.application.name + - else + .one-liner= t('disputes.strikes.status', id: status_id) + .strike-card__statuses-list__item__meta + = t('disputes.strikes.status_removed') diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml index de883bd87..1c16e0bbf 100644 --- a/app/views/disputes/strikes/show.html.haml +++ b/app/views/disputes/strikes/show.html.haml @@ -21,51 +21,7 @@ .report-header .report-header__card - .strike-card - - unless @strike.none_action? - %p= t "user_mailer.warning.explanation.#{@strike.action}", instance: Rails.configuration.x.local_domain - - - if @strike.text.present? - = linkify(@strike.text) - - - if @strike.report && !@strike.report.other? - %p - %strong= t('user_mailer.warning.reason') - = t("user_mailer.warning.categories.#{@strike.report.category}") - - - if @strike.report.violation? && @strike.report.rule_ids.present? - %ul.strike-card__rules - - @strike.report.rules.each do |rule| - %li - %span.strike-card__rules__text= rule.text - - - if @strike.status_ids.present? && !@strike.status_ids.empty? - %p - %strong= t('user_mailer.warning.statuses') - - .strike-card__statuses-list - - status_map = @strike.statuses.includes(:application, :media_attachments).index_by(&:id) - - - @strike.status_ids.each do |status_id| - .strike-card__statuses-list__item - - if (status = status_map[status_id.to_i]) - .one-liner - .emojify= one_line_preview(status) - - - status.ordered_media_attachments.each do |media_attachment| - %abbr{ title: media_attachment.description } - = fa_icon 'link' - = media_attachment.file_file_name - .strike-card__statuses-list__item__meta - = link_to ActivityPub::TagManager.instance.url_for(status), target: '_blank', rel: 'noopener noreferrer' do - %time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at) - - unless status.application.nil? - · - = status.application.name - - else - .one-liner= t('disputes.strikes.status', id: status_id) - .strike-card__statuses-list__item__meta - = t('disputes.strikes.status_removed') + = render 'card', strike: @strike .report-header__details .report-header__details__item From 08a376cbcbb2fa46c32c43937b77ddb081418af8 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 16 Oct 2023 10:36:28 -0400 Subject: [PATCH 44/69] Fix `Style/CombinableLoops` cop (#27429) --- .rubocop_todo.yml | 6 ------ app/models/form/custom_emoji_batch.rb | 18 +++++++++++------- app/models/form/ip_block_batch.rb | 6 +++++- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 29d9f484c..980339f49 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -517,12 +517,6 @@ Style/ClassVars: Exclude: - 'config/initializers/devise.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/CombinableLoops: - Exclude: - - 'app/models/form/custom_emoji_batch.rb' - - 'app/models/form/ip_block_batch.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowedVars. Style/FetchEnvVar: diff --git a/app/models/form/custom_emoji_batch.rb b/app/models/form/custom_emoji_batch.rb index 484415f90..c63996e06 100644 --- a/app/models/form/custom_emoji_batch.rb +++ b/app/models/form/custom_emoji_batch.rb @@ -34,7 +34,7 @@ class Form::CustomEmojiBatch end def update! - custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) } + verify_authorization(:update?) category = if category_id.present? CustomEmojiCategory.find(category_id) @@ -49,7 +49,7 @@ class Form::CustomEmojiBatch end def list! - custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) } + verify_authorization(:update?) custom_emojis.each do |custom_emoji| custom_emoji.update(visible_in_picker: true) @@ -58,7 +58,7 @@ class Form::CustomEmojiBatch end def unlist! - custom_emojis.each { |custom_emoji| authorize(custom_emoji, :update?) } + verify_authorization(:update?) custom_emojis.each do |custom_emoji| custom_emoji.update(visible_in_picker: false) @@ -67,7 +67,7 @@ class Form::CustomEmojiBatch end def enable! - custom_emojis.each { |custom_emoji| authorize(custom_emoji, :enable?) } + verify_authorization(:enable?) custom_emojis.each do |custom_emoji| custom_emoji.update(disabled: false) @@ -76,7 +76,7 @@ class Form::CustomEmojiBatch end def disable! - custom_emojis.each { |custom_emoji| authorize(custom_emoji, :disable?) } + verify_authorization(:disable?) custom_emojis.each do |custom_emoji| custom_emoji.update(disabled: true) @@ -85,7 +85,7 @@ class Form::CustomEmojiBatch end def copy! - custom_emojis.each { |custom_emoji| authorize(custom_emoji, :copy?) } + verify_authorization(:copy?) custom_emojis.each do |custom_emoji| copied_custom_emoji = custom_emoji.copy! @@ -94,11 +94,15 @@ class Form::CustomEmojiBatch end def delete! - custom_emojis.each { |custom_emoji| authorize(custom_emoji, :destroy?) } + verify_authorization(:destroy?) custom_emojis.each do |custom_emoji| custom_emoji.destroy log_action :destroy, custom_emoji end end + + def verify_authorization(permission) + custom_emojis.each { |custom_emoji| authorize(custom_emoji, permission) } + end end diff --git a/app/models/form/ip_block_batch.rb b/app/models/form/ip_block_batch.rb index f6fe9b593..bdfeb91c8 100644 --- a/app/models/form/ip_block_batch.rb +++ b/app/models/form/ip_block_batch.rb @@ -21,11 +21,15 @@ class Form::IpBlockBatch end def delete! - ip_blocks.each { |ip_block| authorize(ip_block, :destroy?) } + verify_authorization(:destroy?) ip_blocks.each do |ip_block| ip_block.destroy log_action :destroy, ip_block end end + + def verify_authorization(permission) + ip_blocks.each { |ip_block| authorize(ip_block, permission) } + end end From e0ed0f8c7c6d74534ba896ebe0d3a25495f27f0d Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Mon, 16 Oct 2023 12:15:24 -0300 Subject: [PATCH 45/69] Migrate to request specs in `/api/v1/notifications` (#25553) --- .../api/v1/notifications_controller_spec.rb | 137 ------------- spec/requests/api/v1/notifications_spec.rb | 183 ++++++++++++++++++ 2 files changed, 183 insertions(+), 137 deletions(-) delete mode 100644 spec/controllers/api/v1/notifications_controller_spec.rb create mode 100644 spec/requests/api/v1/notifications_spec.rb diff --git a/spec/controllers/api/v1/notifications_controller_spec.rb b/spec/controllers/api/v1/notifications_controller_spec.rb deleted file mode 100644 index 6615848b8..000000000 --- a/spec/controllers/api/v1/notifications_controller_spec.rb +++ /dev/null @@ -1,137 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Api::V1::NotificationsController do - render_views - - let(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:other) { Fabricate(:user) } - let(:third) { Fabricate(:user) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - describe 'GET #show' do - let(:scopes) { 'read:notifications' } - - it 'returns http success' do - notification = Fabricate(:notification, account: user.account) - get :show, params: { id: notification.id } - - expect(response).to have_http_status(200) - end - end - - describe 'POST #dismiss' do - let(:scopes) { 'write:notifications' } - - it 'destroys the notification' do - notification = Fabricate(:notification, account: user.account) - post :dismiss, params: { id: notification.id } - - expect(response).to have_http_status(200) - expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound) - end - end - - describe 'POST #clear' do - let(:scopes) { 'write:notifications' } - - it 'clears notifications for the account' do - notification = Fabricate(:notification, account: user.account) - post :clear - - expect(notification.account.reload.notifications).to be_empty - expect(response).to have_http_status(200) - end - end - - describe 'GET #index' do - let(:scopes) { 'read:notifications' } - - before do - first_status = PostStatusService.new.call(user.account, text: 'Test') - @reblog_of_first_status = ReblogService.new.call(other.account, first_status) - mentioning_status = PostStatusService.new.call(other.account, text: 'Hello @alice') - @mention_from_status = mentioning_status.mentions.first - @favourite = FavouriteService.new.call(other.account, first_status) - @second_favourite = FavouriteService.new.call(third.account, first_status) - @follow = FollowService.new.call(other.account, user.account) - end - - describe 'with no options' do - before do - get :index - end - - it 'returns expected notification types', :aggregate_failures do - expect(response).to have_http_status(200) - - expect(body_json_types).to include 'reblog' - expect(body_json_types).to include 'mention' - expect(body_json_types).to include 'favourite' - expect(body_json_types).to include 'follow' - end - end - - describe 'with account_id param' do - before do - get :index, params: { account_id: third.account.id } - end - - it 'returns only notifications from specified user', :aggregate_failures do - expect(response).to have_http_status(200) - - expect(body_json_account_ids.uniq).to eq [third.account.id.to_s] - end - - def body_json_account_ids - body_as_json.map { |x| x[:account][:id] } - end - end - - describe 'with invalid account_id param' do - before do - get :index, params: { account_id: 'foo' } - end - - it 'returns nothing', :aggregate_failures do - expect(response).to have_http_status(200) - - expect(body_as_json.size).to eq 0 - end - end - - describe 'with exclude_types param' do - before do - get :index, params: { exclude_types: %w(mention) } - end - - it 'returns everything but excluded type', :aggregate_failures do - expect(response).to have_http_status(200) - - expect(body_as_json.size).to_not eq 0 - expect(body_json_types.uniq).to_not include 'mention' - end - end - - describe 'with types param' do - before do - get :index, params: { types: %w(mention) } - end - - it 'returns only requested type', :aggregate_failures do - expect(response).to have_http_status(200) - - expect(body_json_types.uniq).to eq ['mention'] - end - end - - def body_json_types - body_as_json.pluck(:type) - end - end -end diff --git a/spec/requests/api/v1/notifications_spec.rb b/spec/requests/api/v1/notifications_spec.rb new file mode 100644 index 000000000..7a879c35b --- /dev/null +++ b/spec/requests/api/v1/notifications_spec.rb @@ -0,0 +1,183 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Notifications' do + let(:user) { Fabricate(:user, account_attributes: { username: 'alice' }) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'read:notifications write:notifications' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + describe 'GET /api/v1/notifications' do + subject do + get '/api/v1/notifications', headers: headers, params: params + end + + let(:bob) { Fabricate(:user) } + let(:tom) { Fabricate(:user) } + let(:params) { {} } + + before do + first_status = PostStatusService.new.call(user.account, text: 'Test') + ReblogService.new.call(bob.account, first_status) + mentioning_status = PostStatusService.new.call(bob.account, text: 'Hello @alice') + mentioning_status.mentions.first + FavouriteService.new.call(bob.account, first_status) + FavouriteService.new.call(tom.account, first_status) + FollowService.new.call(bob.account, user.account) + end + + it_behaves_like 'forbidden for wrong scope', 'write write:notifications' + + context 'with no options' do + it 'returns expected notification types', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_json_types).to include 'reblog' + expect(body_json_types).to include 'mention' + expect(body_json_types).to include 'favourite' + expect(body_json_types).to include 'follow' + end + end + + context 'with account_id param' do + let(:params) { { account_id: tom.account.id } } + + it 'returns only notifications from specified user', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_json_account_ids.uniq).to eq [tom.account.id.to_s] + end + + def body_json_account_ids + body_as_json.map { |x| x[:account][:id] } + end + end + + context 'with invalid account_id param' do + let(:params) { { account_id: 'foo' } } + + it 'returns nothing', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json.size).to eq 0 + end + end + + context 'with exclude_types param' do + let(:params) { { exclude_types: %w(mention) } } + + it 'returns everything but excluded type', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_as_json.size).to_not eq 0 + expect(body_json_types.uniq).to_not include 'mention' + end + end + + context 'with types param' do + let(:params) { { types: %w(mention) } } + + it 'returns only requested type', :aggregate_failures do + subject + + expect(response).to have_http_status(200) + expect(body_json_types.uniq).to eq ['mention'] + end + end + + context 'with limit param' do + let(:params) { { limit: 3 } } + + it 'returns the requested number of notifications paginated', :aggregate_failures do + subject + + notifications = user.account.notifications + + expect(body_as_json.size).to eq(params[:limit]) + expect(response.headers['Link'].find_link(%w(rel prev)).href).to eq(api_v1_notifications_url(limit: params[:limit], min_id: notifications.last.id.to_s)) + expect(response.headers['Link'].find_link(%w(rel next)).href).to eq(api_v1_notifications_url(limit: params[:limit], max_id: notifications[2].id.to_s)) + end + end + + def body_json_types + body_as_json.pluck(:type) + end + end + + describe 'GET /api/v1/notifications/:id' do + subject do + get "/api/v1/notifications/#{notification.id}", headers: headers + end + + let(:notification) { Fabricate(:notification, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'write write:notifications' + + it 'returns http success' do + subject + + expect(response).to have_http_status(200) + end + + context 'when notification belongs to someone else' do + let(:notification) { Fabricate(:notification) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/notifications/:id/dismiss' do + subject do + post "/api/v1/notifications/#{notification.id}/dismiss", headers: headers + end + + let!(:notification) { Fabricate(:notification, account: user.account) } + + it_behaves_like 'forbidden for wrong scope', 'read read:notifications' + + it 'destroys the notification' do + subject + + expect(response).to have_http_status(200) + expect { notification.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + + context 'when notification belongs to someone else' do + let(:notification) { Fabricate(:notification) } + + it 'returns http not found' do + subject + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/notifications/clear' do + subject do + post '/api/v1/notifications/clear', headers: headers + end + + before do + Fabricate.times(3, :notification, account: user.account) + end + + it_behaves_like 'forbidden for wrong scope', 'read read:notifications' + + it 'clears notifications for the account' do + subject + + expect(user.account.reload.notifications).to be_empty + expect(response).to have_http_status(200) + end + end +end From 708299bb0d17ec60f51226f43c26bd66e6cb8adc Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 16 Oct 2023 11:20:28 -0400 Subject: [PATCH 46/69] Misc coverage improvements for `Admin::` and `Settings::` controllers (#25346) --- .../admin/disputes/appeals_controller_spec.rb | 10 ++++ .../admin/domain_blocks_controller_spec.rb | 11 ++++ .../export_domain_allows_controller_spec.rb | 8 +++ .../export_domain_blocks_controller_spec.rb | 8 +++ .../admin/instances_controller_spec.rb | 57 +++++++++++++++++++ .../admin/settings/about_controller_spec.rb | 8 +++ .../settings/appearance_controller_spec.rb | 8 +++ .../content_retention_controller_spec.rb | 8 +++ .../settings/discovery_controller_spec.rb | 8 +++ .../settings/registrations_controller_spec.rb | 8 +++ .../controllers/admin/tags_controller_spec.rb | 22 +++++++ .../admin/webhooks_controller_spec.rb | 18 ++++++ .../settings/imports_controller_spec.rb | 13 +++++ 13 files changed, 187 insertions(+) diff --git a/spec/controllers/admin/disputes/appeals_controller_spec.rb b/spec/controllers/admin/disputes/appeals_controller_spec.rb index 3c3f23f52..4afe07492 100644 --- a/spec/controllers/admin/disputes/appeals_controller_spec.rb +++ b/spec/controllers/admin/disputes/appeals_controller_spec.rb @@ -15,6 +15,16 @@ RSpec.describe Admin::Disputes::AppealsController do let(:strike) { Fabricate(:account_warning, target_account: target_account, action: :suspend) } let(:appeal) { Fabricate(:appeal, strike: strike, account: target_account) } + describe 'GET #index' do + let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } + + it 'lists appeals' do + get :index + + expect(response).to have_http_status(200) + end + end + describe 'POST #approve' do let(:current_user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } diff --git a/spec/controllers/admin/domain_blocks_controller_spec.rb b/spec/controllers/admin/domain_blocks_controller_spec.rb index 9be55906e..13826be36 100644 --- a/spec/controllers/admin/domain_blocks_controller_spec.rb +++ b/spec/controllers/admin/domain_blocks_controller_spec.rb @@ -165,6 +165,17 @@ RSpec.describe Admin::DomainBlocksController do end end + describe 'GET #edit' do + let(:domain_block) { Fabricate(:domain_block) } + + it 'returns http success' do + get :edit, params: { id: domain_block.id } + + expect(assigns(:domain_block)).to be_instance_of(DomainBlock) + expect(response).to have_http_status(200) + end + end + describe 'PUT #update' do subject do post :update, params: { :id => domain_block.id, :domain_block => { domain: 'example.com', severity: new_severity }, 'confirm' => '' } diff --git a/spec/controllers/admin/export_domain_allows_controller_spec.rb b/spec/controllers/admin/export_domain_allows_controller_spec.rb index 9d50c04aa..e1e5ecc1f 100644 --- a/spec/controllers/admin/export_domain_allows_controller_spec.rb +++ b/spec/controllers/admin/export_domain_allows_controller_spec.rb @@ -9,6 +9,14 @@ RSpec.describe Admin::ExportDomainAllowsController do sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user end + describe 'GET #new' do + it 'returns http success' do + get :new + + expect(response).to have_http_status(200) + end + end + describe 'GET #export' do it 'renders instances' do Fabricate(:domain_allow, domain: 'good.domain') diff --git a/spec/controllers/admin/export_domain_blocks_controller_spec.rb b/spec/controllers/admin/export_domain_blocks_controller_spec.rb index 1a6307773..5a282c957 100644 --- a/spec/controllers/admin/export_domain_blocks_controller_spec.rb +++ b/spec/controllers/admin/export_domain_blocks_controller_spec.rb @@ -9,6 +9,14 @@ RSpec.describe Admin::ExportDomainBlocksController do sign_in Fabricate(:user, role: UserRole.find_by(name: 'Admin')), scope: :user end + describe 'GET #new' do + it 'returns http success' do + get :new + + expect(response).to have_http_status(200) + end + end + describe 'GET #export' do it 'renders instances' do Fabricate(:domain_block, domain: 'bad.domain', severity: 'silence', public_comment: 'bad server') diff --git a/spec/controllers/admin/instances_controller_spec.rb b/spec/controllers/admin/instances_controller_spec.rb index dd772d103..5fed5d98d 100644 --- a/spec/controllers/admin/instances_controller_spec.rb +++ b/spec/controllers/admin/instances_controller_spec.rb @@ -34,6 +34,63 @@ RSpec.describe Admin::InstancesController do end end + describe 'GET #show' do + it 'shows an instance page' do + get :show, params: { id: account_popular_main.domain } + + expect(response).to have_http_status(200) + end + end + + describe 'POST #clear_delivery_errors' do + let(:tracker) { instance_double(DeliveryFailureTracker, clear_failures!: true) } + + before { allow(DeliveryFailureTracker).to receive(:new).and_return(tracker) } + + it 'clears instance delivery errors' do + post :clear_delivery_errors, params: { id: account_popular_main.domain } + + expect(response).to redirect_to(admin_instance_path(account_popular_main.domain)) + expect(tracker).to have_received(:clear_failures!) + end + end + + describe 'POST #restart_delivery' do + let(:tracker) { instance_double(DeliveryFailureTracker, track_success!: true) } + + before { allow(DeliveryFailureTracker).to receive(:new).and_return(tracker) } + + context 'with an unavailable instance' do + before { Fabricate(:unavailable_domain, domain: account_popular_main.domain) } + + it 'tracks success on the instance' do + post :restart_delivery, params: { id: account_popular_main.domain } + + expect(response).to redirect_to(admin_instance_path(account_popular_main.domain)) + expect(tracker).to have_received(:track_success!) + end + end + + context 'with an available instance' do + it 'does not track success on the instance' do + post :restart_delivery, params: { id: account_popular_main.domain } + + expect(response).to redirect_to(admin_instance_path(account_popular_main.domain)) + expect(tracker).to_not have_received(:track_success!) + end + end + end + + describe 'POST #stop_delivery' do + it 'clears instance delivery errors' do + expect do + post :stop_delivery, params: { id: account_popular_main.domain } + end.to change(UnavailableDomain, :count).by(1) + + expect(response).to redirect_to(admin_instance_path(account_popular_main.domain)) + end + end + describe 'DELETE #destroy' do subject { delete :destroy, params: { id: Instance.first.id } } diff --git a/spec/controllers/admin/settings/about_controller_spec.rb b/spec/controllers/admin/settings/about_controller_spec.rb index 2ae26090b..f322cb443 100644 --- a/spec/controllers/admin/settings/about_controller_spec.rb +++ b/spec/controllers/admin/settings/about_controller_spec.rb @@ -18,4 +18,12 @@ describe Admin::Settings::AboutController do expect(response).to have_http_status(:success) end end + + describe 'PUT #update' do + it 'updates the settings' do + put :update, params: { form_admin_settings: { site_extended_description: 'new site description' } } + + expect(response).to redirect_to(admin_settings_about_path) + end + end end diff --git a/spec/controllers/admin/settings/appearance_controller_spec.rb b/spec/controllers/admin/settings/appearance_controller_spec.rb index 65b29acc3..ea6f3b783 100644 --- a/spec/controllers/admin/settings/appearance_controller_spec.rb +++ b/spec/controllers/admin/settings/appearance_controller_spec.rb @@ -18,4 +18,12 @@ describe Admin::Settings::AppearanceController do expect(response).to have_http_status(:success) end end + + describe 'PUT #update' do + it 'updates the settings' do + put :update, params: { form_admin_settings: { custom_css: 'html { display: inline; }' } } + + expect(response).to redirect_to(admin_settings_appearance_path) + end + end end diff --git a/spec/controllers/admin/settings/content_retention_controller_spec.rb b/spec/controllers/admin/settings/content_retention_controller_spec.rb index 53ce84d18..fb6a3d284 100644 --- a/spec/controllers/admin/settings/content_retention_controller_spec.rb +++ b/spec/controllers/admin/settings/content_retention_controller_spec.rb @@ -18,4 +18,12 @@ describe Admin::Settings::ContentRetentionController do expect(response).to have_http_status(:success) end end + + describe 'PUT #update' do + it 'updates the settings' do + put :update, params: { form_admin_settings: { media_cache_retention_period: '2' } } + + expect(response).to redirect_to(admin_settings_content_retention_path) + end + end end diff --git a/spec/controllers/admin/settings/discovery_controller_spec.rb b/spec/controllers/admin/settings/discovery_controller_spec.rb index c7307ffc8..33109e3c0 100644 --- a/spec/controllers/admin/settings/discovery_controller_spec.rb +++ b/spec/controllers/admin/settings/discovery_controller_spec.rb @@ -18,4 +18,12 @@ describe Admin::Settings::DiscoveryController do expect(response).to have_http_status(:success) end end + + describe 'PUT #update' do + it 'updates the settings' do + put :update, params: { form_admin_settings: { trends: '1' } } + + expect(response).to redirect_to(admin_settings_discovery_path) + end + end end diff --git a/spec/controllers/admin/settings/registrations_controller_spec.rb b/spec/controllers/admin/settings/registrations_controller_spec.rb index 3fc1f9d13..e07654460 100644 --- a/spec/controllers/admin/settings/registrations_controller_spec.rb +++ b/spec/controllers/admin/settings/registrations_controller_spec.rb @@ -18,4 +18,12 @@ describe Admin::Settings::RegistrationsController do expect(response).to have_http_status(:success) end end + + describe 'PUT #update' do + it 'updates the settings' do + put :update, params: { form_admin_settings: { registrations_mode: 'open' } } + + expect(response).to redirect_to(admin_settings_registrations_path) + end + end end diff --git a/spec/controllers/admin/tags_controller_spec.rb b/spec/controllers/admin/tags_controller_spec.rb index 313298f14..4e06adaca 100644 --- a/spec/controllers/admin/tags_controller_spec.rb +++ b/spec/controllers/admin/tags_controller_spec.rb @@ -20,4 +20,26 @@ RSpec.describe Admin::TagsController do expect(response).to have_http_status(200) end end + + describe 'PUT #update' do + let!(:tag) { Fabricate(:tag, listable: false) } + + context 'with valid params' do + it 'updates the tag' do + put :update, params: { id: tag.id, tag: { listable: '1' } } + + expect(response).to redirect_to(admin_tag_path(tag.id)) + expect(tag.reload).to be_listable + end + end + + context 'with invalid params' do + it 'does not update the tag' do + put :update, params: { id: tag.id, tag: { name: 'cant-change-name' } } + + expect(response).to have_http_status(200) + expect(response).to render_template(:show) + end + end + end end diff --git a/spec/controllers/admin/webhooks_controller_spec.rb b/spec/controllers/admin/webhooks_controller_spec.rb index 0ccfbbcc6..17d850602 100644 --- a/spec/controllers/admin/webhooks_controller_spec.rb +++ b/spec/controllers/admin/webhooks_controller_spec.rb @@ -86,6 +86,24 @@ describe Admin::WebhooksController do end end + describe 'POST #enable' do + it 'enables the webhook' do + post :enable, params: { id: webhook.id } + + expect(webhook.reload).to be_enabled + expect(response).to redirect_to(admin_webhook_path(webhook)) + end + end + + describe 'POST #disable' do + it 'disables the webhook' do + post :disable, params: { id: webhook.id } + + expect(webhook.reload).to_not be_enabled + expect(response).to redirect_to(admin_webhook_path(webhook)) + end + end + describe 'DELETE #destroy' do it 'destroys the record' do expect do diff --git a/spec/controllers/settings/imports_controller_spec.rb b/spec/controllers/settings/imports_controller_spec.rb index 76e1e4ecb..35d2f0819 100644 --- a/spec/controllers/settings/imports_controller_spec.rb +++ b/spec/controllers/settings/imports_controller_spec.rb @@ -252,6 +252,19 @@ RSpec.describe Settings::ImportsController do include_examples 'export failed rows', "https://foo.com/1\nhttps://foo.com/2\n" end + + context 'with lists' do + let(:import_type) { 'lists' } + + let!(:rows) do + [ + { 'list_name' => 'Amigos', 'acct' => 'user@example.com' }, + { 'list_name' => 'Frenemies', 'acct' => 'user@org.org' }, + ].map { |data| Fabricate(:bulk_import_row, bulk_import: bulk_import, data: data) } + end + + include_examples 'export failed rows', "Amigos,user@example.com\nFrenemies,user@org.org\n" + end end describe 'POST #create' do From 893b2f33fd92a320146e6abf172e21b1b340ae24 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 16 Oct 2023 11:52:06 -0400 Subject: [PATCH 47/69] Extract shared example for cacheable response in specs (#25388) --- spec/controllers/accounts_controller_spec.rb | 25 +++---------------- .../collections_controller_spec.rb | 16 ------------ .../activitypub/outboxes_controller_spec.rb | 16 ------------ .../activitypub/replies_controller_spec.rb | 16 ------------ spec/controllers/statuses_controller_spec.rb | 23 ++--------------- spec/support/examples/cache.rb | 22 ++++++++++++++++ 6 files changed, 27 insertions(+), 91 deletions(-) create mode 100644 spec/support/examples/cache.rb diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb index 5a8585b06..d46fdb108 100644 --- a/spec/controllers/accounts_controller_spec.rb +++ b/spec/controllers/accounts_controller_spec.rb @@ -7,25 +7,6 @@ RSpec.describe AccountsController do let(:account) { Fabricate(:account) } - shared_examples 'cacheable response' do - it 'does not set cookies' do - expect(response.cookies).to be_empty - expect(response.headers['Set-Cookies']).to be_nil - end - - it 'does not set sessions' do - expect(session).to be_empty - end - - it 'returns Vary header' do - expect(response.headers['Vary']).to include 'Accept' - end - - it 'returns public Cache-Control header' do - expect(response.headers['Cache-Control']).to include 'public' - end - end - describe 'GET #show' do let(:format) { 'html' } @@ -186,7 +167,7 @@ RSpec.describe AccountsController do expect(response.media_type).to eq 'application/activity+json' end - it_behaves_like 'cacheable response' + it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' it 'renders account' do json = body_as_json @@ -244,7 +225,7 @@ RSpec.describe AccountsController do expect(response.media_type).to eq 'application/activity+json' end - it_behaves_like 'cacheable response' + it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' it 'renders account' do json = body_as_json @@ -311,7 +292,7 @@ RSpec.describe AccountsController do expect(response).to have_http_status(200) end - it_behaves_like 'cacheable response' + it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' end context 'with a normal account in an RSS request' do diff --git a/spec/controllers/activitypub/collections_controller_spec.rb b/spec/controllers/activitypub/collections_controller_spec.rb index e2802cf56..cf484ff5a 100644 --- a/spec/controllers/activitypub/collections_controller_spec.rb +++ b/spec/controllers/activitypub/collections_controller_spec.rb @@ -7,22 +7,6 @@ RSpec.describe ActivityPub::CollectionsController do let!(:private_pinned) { Fabricate(:status, account: account, text: 'secret private stuff', visibility: :private) } let(:remote_account) { nil } - shared_examples 'cacheable response' do - it 'does not set cookies' do - expect(response.cookies).to be_empty - expect(response.headers['Set-Cookies']).to be_nil - end - - it 'does not set sessions' do - response - expect(session).to be_empty - end - - it 'returns public Cache-Control header' do - expect(response.headers['Cache-Control']).to include 'public' - end - end - before do allow(controller).to receive(:signed_request_actor).and_return(remote_account) diff --git a/spec/controllers/activitypub/outboxes_controller_spec.rb b/spec/controllers/activitypub/outboxes_controller_spec.rb index 6946fdfcf..53c4f0c09 100644 --- a/spec/controllers/activitypub/outboxes_controller_spec.rb +++ b/spec/controllers/activitypub/outboxes_controller_spec.rb @@ -5,22 +5,6 @@ require 'rails_helper' RSpec.describe ActivityPub::OutboxesController do let!(:account) { Fabricate(:account) } - shared_examples 'cacheable response' do - it 'does not set cookies' do - expect(response.cookies).to be_empty - expect(response.headers['Set-Cookies']).to be_nil - end - - it 'does not set sessions' do - response - expect(session).to be_empty - end - - it 'returns public Cache-Control header' do - expect(response.headers['Cache-Control']).to include 'public' - end - end - before do Fabricate(:status, account: account, visibility: :public) Fabricate(:status, account: account, visibility: :unlisted) diff --git a/spec/controllers/activitypub/replies_controller_spec.rb b/spec/controllers/activitypub/replies_controller_spec.rb index c7b65f004..7e6e0ffb0 100644 --- a/spec/controllers/activitypub/replies_controller_spec.rb +++ b/spec/controllers/activitypub/replies_controller_spec.rb @@ -8,22 +8,6 @@ RSpec.describe ActivityPub::RepliesController do let(:remote_reply_id) { 'https://foobar.com/statuses/1234' } let(:remote_querier) { nil } - shared_examples 'cacheable response' do - it 'does not set cookies' do - expect(response.cookies).to be_empty - expect(response.headers['Set-Cookies']).to be_nil - end - - it 'does not set sessions' do - response - expect(session).to be_empty - end - - it 'returns public Cache-Control header' do - expect(response.headers['Cache-Control']).to include 'public' - end - end - shared_examples 'common behavior' do context 'when status is private' do let(:parent_visibility) { :private } diff --git a/spec/controllers/statuses_controller_spec.rb b/spec/controllers/statuses_controller_spec.rb index bd98929c0..8b715824b 100644 --- a/spec/controllers/statuses_controller_spec.rb +++ b/spec/controllers/statuses_controller_spec.rb @@ -5,25 +5,6 @@ require 'rails_helper' describe StatusesController do render_views - shared_examples 'cacheable response' do - it 'does not set cookies' do - expect(response.cookies).to be_empty - expect(response.headers['Set-Cookies']).to be_nil - end - - it 'does not set sessions' do - expect(session).to be_empty - end - - it 'returns Vary header' do - expect(response.headers['Vary']).to include 'Accept, Accept-Language, Cookie' - end - - it 'returns public Cache-Control header' do - expect(response.headers['Cache-Control']).to include 'public' - end - end - describe 'GET #show' do let(:account) { Fabricate(:account) } let(:status) { Fabricate(:status, account: account) } @@ -88,7 +69,7 @@ describe StatusesController do context 'with JSON' do let(:format) { 'json' } - it_behaves_like 'cacheable response' + it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' it 'renders ActivityPub Note object successfully', :aggregate_failures do expect(response).to have_http_status(200) @@ -371,7 +352,7 @@ describe StatusesController do context 'with JSON' do let(:format) { 'json' } - it_behaves_like 'cacheable response' + it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' it 'renders ActivityPub Note object successfully', :aggregate_failures do expect(response).to have_http_status(200) diff --git a/spec/support/examples/cache.rb b/spec/support/examples/cache.rb new file mode 100644 index 000000000..43cfbade8 --- /dev/null +++ b/spec/support/examples/cache.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +shared_examples 'cacheable response' do |expects_vary: false| + it 'does not set cookies' do + expect(response.cookies).to be_empty + expect(response.headers['Set-Cookies']).to be_nil + end + + it 'does not set sessions' do + expect(session).to be_empty + end + + if expects_vary + it 'returns Vary header' do + expect(response.headers['Vary']).to include(expects_vary) + end + end + + it 'returns public Cache-Control header' do + expect(response.headers['Cache-Control']).to include('public') + end +end From 8d0f12f776bcf7b6e0f1caabc812c5f473094a6a Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 16 Oct 2023 12:02:15 -0400 Subject: [PATCH 48/69] Extract partials from `admin/accounts/show` view (#27428) --- app/views/admin/accounts/_buttons.html.haml | 41 ++++ app/views/admin/accounts/_counters.html.haml | 43 ++++ .../admin/accounts/_local_account.html.haml | 82 +++++++ .../admin/accounts/_remote_account.html.haml | 15 ++ app/views/admin/accounts/show.html.haml | 210 +----------------- 5 files changed, 185 insertions(+), 206 deletions(-) create mode 100644 app/views/admin/accounts/_buttons.html.haml create mode 100644 app/views/admin/accounts/_counters.html.haml create mode 100644 app/views/admin/accounts/_local_account.html.haml create mode 100644 app/views/admin/accounts/_remote_account.html.haml diff --git a/app/views/admin/accounts/_buttons.html.haml b/app/views/admin/accounts/_buttons.html.haml new file mode 100644 index 000000000..6eb141abc --- /dev/null +++ b/app/views/admin/accounts/_buttons.html.haml @@ -0,0 +1,41 @@ +- if account.suspended? + %hr.spacer/ + - if account.suspension_origin_remote? + %p.muted-hint= deletion_request.present? ? t('admin.accounts.remote_suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.remote_suspension_irreversible') + - else + %p.muted-hint= deletion_request.present? ? t('admin.accounts.suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.suspension_irreversible') + = link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsuspend, account) + = link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button' if can?(:redownload, account) && account.suspension_origin_remote? + - if deletion_request.present? + = link_to t('admin.accounts.delete'), admin_account_path(account.id), method: :delete, class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, account) +- else + .action-buttons + %div + - if account.local? && account.user_approved? + = link_to t('admin.accounts.warn'), new_admin_account_action_path(account.id, type: 'none'), class: 'button' if can?(:warn, account) + - if account.user_disabled? + = link_to t('admin.accounts.enable'), enable_admin_account_path(account.id), method: :post, class: 'button' if can?(:enable, account.user) + - else + = link_to t('admin.accounts.disable'), new_admin_account_action_path(account.id, type: 'disable'), class: 'button' if can?(:disable, account.user) + - if account.sensitized? + = link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsensitive, account) + - elsif !account.local? || account.user_approved? + = link_to t('admin.accounts.sensitive'), new_admin_account_action_path(account.id, type: 'sensitive'), class: 'button' if can?(:sensitive, account) + - if account.silenced? + = link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsilence, account) + - elsif !account.local? || account.user_approved? + = link_to t('admin.accounts.silence'), new_admin_account_action_path(account.id, type: 'silence'), class: 'button' if can?(:silence, account) + - if account.local? + - if account.user_pending? + = link_to t('admin.accounts.approve'), approve_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, account.user) + = link_to t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, account.user) + - unless account.user_confirmed? + = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(account.id), method: :post, class: 'button' if can?(:confirm, account.user) + - if !account.local? || account.user_approved? + = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(account.id, type: 'suspend'), class: 'button' if can?(:suspend, account) + %div + - if account.local? + - if !account.memorial? && account.user_approved? + = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, account) + - else + = link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button' if can?(:redownload, account) diff --git a/app/views/admin/accounts/_counters.html.haml b/app/views/admin/accounts/_counters.html.haml new file mode 100644 index 000000000..00ab98d09 --- /dev/null +++ b/app/views/admin/accounts/_counters.html.haml @@ -0,0 +1,43 @@ +.dashboard__counters.admin-account-counters + %div + = link_to admin_account_statuses_path(account.id) do + .dashboard__counters__num= number_with_delimiter account.statuses_count + .dashboard__counters__label= t 'admin.accounts.statuses' + %div + = link_to admin_account_statuses_path(account.id, { media: true }) do + .dashboard__counters__num= number_to_human_size account.media_attachments.sum('file_file_size') + .dashboard__counters__label= t 'admin.accounts.media_attachments' + %div + = link_to admin_account_relationships_path(account.id, location: account.local? ? nil : 'local', relationship: 'followed_by') do + .dashboard__counters__num= number_with_delimiter account.local_followers_count + .dashboard__counters__label= t 'admin.accounts.followers' + %div + = link_to admin_reports_path(account_id: account.id) do + .dashboard__counters__num= number_with_delimiter account.reports.count + .dashboard__counters__label= t 'admin.accounts.show.created_reports' + %div + = link_to admin_reports_path(target_account_id: account.id) do + .dashboard__counters__num= number_with_delimiter account.targeted_reports.count + .dashboard__counters__label= t 'admin.accounts.show.targeted_reports' + %div + = link_to admin_action_logs_path(target_account_id: account.id) do + .dashboard__counters__text + - if account.local? && account.user.nil? + = t('admin.accounts.deleted') + - elsif account.memorial? + = t('admin.accounts.memorialized') + - elsif account.suspended? + = t('admin.accounts.suspended') + - elsif account.silenced? + = t('admin.accounts.silenced') + - elsif account.local? && account.user&.disabled? + = t('admin.accounts.disabled') + - elsif account.local? && !account.user&.confirmed? + = t('admin.accounts.confirming') + - elsif account.local? && !account.user_approved? + = t('admin.accounts.pending') + - elsif account.sensitized? + = t('admin.accounts.sensitive') + - else + = t('admin.accounts.no_limits_imposed') + .dashboard__counters__label= t 'admin.accounts.login_status' diff --git a/app/views/admin/accounts/_local_account.html.haml b/app/views/admin/accounts/_local_account.html.haml new file mode 100644 index 000000000..4b361fc8d --- /dev/null +++ b/app/views/admin/accounts/_local_account.html.haml @@ -0,0 +1,82 @@ +- if account.avatar? + %tr + %th= t('admin.accounts.avatar') + %td= table_link_to 'trash', t('admin.accounts.remove_avatar'), remove_avatar_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_avatar, account) + %td +- if account.header? + %tr + %th= t('admin.accounts.header') + %td= table_link_to 'trash', t('admin.accounts.remove_header'), remove_header_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_header, account) + %td +%tr + %th= t('admin.accounts.role') + %td + - if account.user_role&.everyone? + = t('admin.accounts.no_role_assigned') + - else + = account.user_role&.name + %td + = table_link_to 'vcard', t('admin.accounts.change_role.label'), admin_user_role_path(account.user) if can?(:change_role, account.user) +%tr + %th{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= t('admin.accounts.email') + %td{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= account.user_email + %td= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(account.id) if can?(:change_email, account.user) +%tr + %td= table_link_to 'search', t('admin.accounts.search_same_email_domain'), admin_accounts_path(email: "%@#{account.user_email.split('@').last}") +- if can?(:create, :email_domain_block) + %tr + %td= table_link_to 'ban', t('admin.accounts.add_email_domain_block'), new_admin_email_domain_block_path(_domain: account.user_email.split('@').last) +- if account.user_unconfirmed_email.present? + %tr + %th= t('admin.accounts.unconfirmed_email') + %td= account.user_unconfirmed_email + %td +%tr + %th= t('admin.accounts.email_status') + %td + - if account.user&.confirmed? + = t('admin.accounts.confirmed') + - else + = t('admin.accounts.confirming') + %td= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(account.id), method: :post if can?(:confirm, account.user) +%tr + %th{ rowspan: can?(:reset_password, account.user) ? 2 : 1 }= t('admin.accounts.security') + %td{ rowspan: can?(:reset_password, account.user) ? 2 : 1 } + - if account.user&.two_factor_enabled? + = t 'admin.accounts.security_measures.password_and_2fa' + - else + = t 'admin.accounts.security_measures.only_password' + %td + - if account.user&.two_factor_enabled? + = table_link_to 'unlock', t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(account.user.id), method: :delete if can?(:disable_2fa, account.user) +- if can?(:reset_password, account.user) + %tr + %td + = table_link_to 'key', t('admin.accounts.reset_password'), admin_account_reset_path(account.id), method: :create, data: { confirm: t('admin.accounts.are_you_sure') } +%tr + %th= t('simple_form.labels.defaults.locale') + %td= standard_locale_name(account.user_locale) + %td +%tr + %th= t('admin.accounts.joined') + %td + %time.formatted{ datetime: account.created_at.iso8601, title: l(account.created_at) }= l account.created_at + %td +- recent_ips = account.user.ips.order(used_at: :desc).to_a +- recent_ips.each_with_index do |recent_ip, i| + %tr + - if i.zero? + %th{ rowspan: recent_ips.size }= t('admin.accounts.most_recent_ip') + %td= recent_ip.ip + %td= table_link_to 'search', t('admin.accounts.search_same_ip'), admin_accounts_path(ip: recent_ip.ip) +%tr + %th= t('admin.accounts.most_recent_activity') + %td + - if account.user_current_sign_in_at + %time.formatted{ datetime: account.user_current_sign_in_at.iso8601, title: l(account.user_current_sign_in_at) }= l account.user_current_sign_in_at + %td +- if account.user&.invited? + %tr + %th= t('admin.accounts.invited_by') + %td= admin_account_link_to account.user.invite.user.account + %td diff --git a/app/views/admin/accounts/_remote_account.html.haml b/app/views/admin/accounts/_remote_account.html.haml new file mode 100644 index 000000000..99996e1d4 --- /dev/null +++ b/app/views/admin/accounts/_remote_account.html.haml @@ -0,0 +1,15 @@ +%tr + %th= t('admin.accounts.inbox_url') + %td + = account.inbox_url + = fa_icon DeliveryFailureTracker.available?(account.inbox_url) ? 'check' : 'times' + %td + = table_link_to 'search', domain_block.present? ? t('admin.domain_blocks.view') : t('admin.accounts.view_domain'), admin_instance_path(account.domain) +%tr + %th= t('admin.accounts.shared_inbox_url') + %td + = account.shared_inbox_url + = fa_icon DeliveryFailureTracker.available?(account.shared_inbox_url) ? 'check' : 'times' + %td + - if domain_block.nil? + = table_link_to 'ban', t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: account.domain) diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 7801ef191..80b8fc92c 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -27,49 +27,7 @@ %div .account__header__content.emojify= prerender_custom_emojis(account_bio_format(account), account.emojis) -.dashboard__counters.admin-account-counters - %div - = link_to admin_account_statuses_path(@account.id) do - .dashboard__counters__num= number_with_delimiter @account.statuses_count - .dashboard__counters__label= t 'admin.accounts.statuses' - %div - = link_to admin_account_statuses_path(@account.id, { media: true }) do - .dashboard__counters__num= number_to_human_size @account.media_attachments.sum('file_file_size') - .dashboard__counters__label= t 'admin.accounts.media_attachments' - %div - = link_to admin_account_relationships_path(@account.id, location: @account.local? ? nil : 'local', relationship: 'followed_by') do - .dashboard__counters__num= number_with_delimiter @account.local_followers_count - .dashboard__counters__label= t 'admin.accounts.followers' - %div - = link_to admin_reports_path(account_id: @account.id) do - .dashboard__counters__num= number_with_delimiter @account.reports.count - .dashboard__counters__label= t '.created_reports' - %div - = link_to admin_reports_path(target_account_id: @account.id) do - .dashboard__counters__num= number_with_delimiter @account.targeted_reports.count - .dashboard__counters__label= t '.targeted_reports' - %div - = link_to admin_action_logs_path(target_account_id: @account.id) do - .dashboard__counters__text - - if @account.local? && @account.user.nil? - = t('admin.accounts.deleted') - - elsif @account.memorial? - = t('admin.accounts.memorialized') - - elsif @account.suspended? - = t('admin.accounts.suspended') - - elsif @account.silenced? - = t('admin.accounts.silenced') - - elsif @account.local? && @account.user&.disabled? - = t('admin.accounts.disabled') - - elsif @account.local? && !@account.user&.confirmed? - = t('admin.accounts.confirming') - - elsif @account.local? && !@account.user_approved? - = t('admin.accounts.pending') - - elsif @account.sensitized? - = t('admin.accounts.sensitive') - - else - = t('admin.accounts.no_limits_imposed') - .dashboard__counters__label= t 'admin.accounts.login_status' += render 'admin/accounts/counters', account: @account - if @account.local? && @account.user.nil? = link_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unblock_email, @account) && CanonicalEmailBlock.exists?(reference_account_id: @account.id) @@ -78,171 +36,11 @@ %table.table.inline-table %tbody - if @account.local? - - if @account.avatar? - %tr - %th= t('admin.accounts.avatar') - %td= table_link_to 'trash', t('admin.accounts.remove_avatar'), remove_avatar_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_avatar, @account) - %td - - - if @account.header? - %tr - %th= t('admin.accounts.header') - %td= table_link_to 'trash', t('admin.accounts.remove_header'), remove_header_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:remove_header, @account) - %td - - %tr - %th= t('admin.accounts.role') - %td - - if @account.user_role&.everyone? - = t('admin.accounts.no_role_assigned') - - else - = @account.user_role&.name - %td - = table_link_to 'vcard', t('admin.accounts.change_role.label'), admin_user_role_path(@account.user) if can?(:change_role, @account.user) - - %tr - %th{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= t('admin.accounts.email') - %td{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= @account.user_email - %td= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user) - - %tr - %td= table_link_to 'search', t('admin.accounts.search_same_email_domain'), admin_accounts_path(email: "%@#{@account.user_email.split('@').last}") - - - if can?(:create, :email_domain_block) - %tr - %td= table_link_to 'ban', t('admin.accounts.add_email_domain_block'), new_admin_email_domain_block_path(_domain: @account.user_email.split('@').last) - - - if @account.user_unconfirmed_email.present? - %tr - %th= t('admin.accounts.unconfirmed_email') - %td= @account.user_unconfirmed_email - %td - - %tr - %th= t('admin.accounts.email_status') - %td - - if @account.user&.confirmed? - = t('admin.accounts.confirmed') - - else - = t('admin.accounts.confirming') - %td= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(@account.id), method: :post if can?(:confirm, @account.user) - %tr - %th{ rowspan: can?(:reset_password, @account.user) ? 2 : 1 }= t('admin.accounts.security') - %td{ rowspan: can?(:reset_password, @account.user) ? 2 : 1 } - - if @account.user&.two_factor_enabled? - = t 'admin.accounts.security_measures.password_and_2fa' - - else - = t 'admin.accounts.security_measures.only_password' - %td - - if @account.user&.two_factor_enabled? - = table_link_to 'unlock', t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete if can?(:disable_2fa, @account.user) - - - if can?(:reset_password, @account.user) - %tr - %td - = table_link_to 'key', t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, data: { confirm: t('admin.accounts.are_you_sure') } - - %tr - %th= t('simple_form.labels.defaults.locale') - %td= standard_locale_name(@account.user_locale) - %td - - %tr - %th= t('admin.accounts.joined') - %td - %time.formatted{ datetime: @account.created_at.iso8601, title: l(@account.created_at) }= l @account.created_at - %td - - - recent_ips = @account.user.ips.order(used_at: :desc).to_a - - - recent_ips.each_with_index do |recent_ip, i| - %tr - - if i.zero? - %th{ rowspan: recent_ips.size }= t('admin.accounts.most_recent_ip') - %td= recent_ip.ip - %td= table_link_to 'search', t('admin.accounts.search_same_ip'), admin_accounts_path(ip: recent_ip.ip) - - %tr - %th= t('admin.accounts.most_recent_activity') - %td - - if @account.user_current_sign_in_at - %time.formatted{ datetime: @account.user_current_sign_in_at.iso8601, title: l(@account.user_current_sign_in_at) }= l @account.user_current_sign_in_at - %td - - - if @account.user&.invited? - %tr - %th= t('admin.accounts.invited_by') - %td= admin_account_link_to @account.user.invite.user.account - %td - + = render 'admin/accounts/local_account', account: @account - else - %tr - %th= t('admin.accounts.inbox_url') - %td - = @account.inbox_url - = fa_icon DeliveryFailureTracker.available?(@account.inbox_url) ? 'check' : 'times' - %td - = table_link_to 'search', @domain_block.present? ? t('admin.domain_blocks.view') : t('admin.accounts.view_domain'), admin_instance_path(@account.domain) - %tr - %th= t('admin.accounts.shared_inbox_url') - %td - = @account.shared_inbox_url - = fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check' : 'times' - %td - - if @domain_block.nil? - = table_link_to 'ban', t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain) + = render 'admin/accounts/remote_account', account: @account, domain_block: @domain_block - - if @account.suspended? - %hr.spacer/ - - - if @account.suspension_origin_remote? - %p.muted-hint= @deletion_request.present? ? t('admin.accounts.remote_suspension_reversible_hint_html', date: content_tag(:strong, l(@deletion_request.due_at.to_date))) : t('admin.accounts.remote_suspension_irreversible') - - else - %p.muted-hint= @deletion_request.present? ? t('admin.accounts.suspension_reversible_hint_html', date: content_tag(:strong, l(@deletion_request.due_at.to_date))) : t('admin.accounts.suspension_irreversible') - - = link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account) - = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) && @account.suspension_origin_remote? - - - if @deletion_request.present? - = link_to t('admin.accounts.delete'), admin_account_path(@account.id), method: :delete, class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, @account) - - else - .action-buttons - %div - - if @account.local? && @account.user_approved? - = link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account) - - - if @account.user_disabled? - = link_to t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post, class: 'button' if can?(:enable, @account.user) - - else - = link_to t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable'), class: 'button' if can?(:disable, @account.user) - - - if @account.sensitized? - = link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsensitive, @account) - - elsif !@account.local? || @account.user_approved? - = link_to t('admin.accounts.sensitive'), new_admin_account_action_path(@account.id, type: 'sensitive'), class: 'button' if can?(:sensitive, @account) - - - if @account.silenced? - = link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account) - - elsif !@account.local? || @account.user_approved? - = link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button' if can?(:silence, @account) - - - if @account.local? - - if @account.user_pending? - = link_to t('admin.accounts.approve'), approve_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, @account.user) - = link_to t('admin.accounts.reject'), reject_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, @account.user) - - - unless @account.user_confirmed? - = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user) - - - if !@account.local? || @account.user_approved? - = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button' if can?(:suspend, @account) - - %div - - if @account.local? - - if !@account.memorial? && @account.user_approved? - = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account) - - else - = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) + = render 'admin/accounts/buttons', account: @account, deletion_request: @deletion_request %hr.spacer/ From 33b073f77daa52c04d6615feaec0edf7651ad413 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 16 Oct 2023 12:07:17 -0400 Subject: [PATCH 49/69] Extract partials from `admin/reports/show` view (#27427) --- app/views/admin/reports/_comment.html.haml | 24 ++++ .../admin/reports/_header_card.html.haml | 46 +++++++ .../admin/reports/_header_details.html.haml | 53 +++++++ app/views/admin/reports/show.html.haml | 130 +----------------- 4 files changed, 126 insertions(+), 127 deletions(-) create mode 100644 app/views/admin/reports/_comment.html.haml create mode 100644 app/views/admin/reports/_header_card.html.haml create mode 100644 app/views/admin/reports/_header_details.html.haml diff --git a/app/views/admin/reports/_comment.html.haml b/app/views/admin/reports/_comment.html.haml new file mode 100644 index 000000000..8c07210af --- /dev/null +++ b/app/views/admin/reports/_comment.html.haml @@ -0,0 +1,24 @@ +- if report.account.instance_actor? + %p= t('admin.reports.comment_description_html', name: content_tag(:strong, site_hostname, class: 'username')) +- elsif report.account.local? + %p= t('admin.reports.comment_description_html', name: content_tag(:strong, report.account.username, class: 'username')) +- else + %p= t('admin.reports.comment_description_html', name: t('admin.reports.remote_user_placeholder', instance: report.account.domain)) +.report-notes + .report-notes__item + - if report.account.local? && !report.account.instance_actor? + = image_tag report.account.avatar.url, class: 'report-notes__item__avatar' + - else + = image_tag(full_asset_url('avatars/original/missing.png', skip_pipeline: true), class: 'report-notes__item__avatar') + .report-notes__item__header + %span.username + - if report.account.instance_actor? + = site_hostname + - elsif report.account.local? + = link_to report.account.username, admin_account_path(report.account_id) + - else + = link_to report.account.domain, admin_instance_path(report.account.domain) + %time.relative-formatted{ datetime: report.created_at.iso8601 } + = l report.created_at.to_date + .report-notes__item__content + = simple_format(h(report.comment)) diff --git a/app/views/admin/reports/_header_card.html.haml b/app/views/admin/reports/_header_card.html.haml new file mode 100644 index 000000000..6fd8b4ecc --- /dev/null +++ b/app/views/admin/reports/_header_card.html.haml @@ -0,0 +1,46 @@ +.report-header__card + .account-card + .account-card__header + = image_tag report.target_account.header.url, alt: '' + .account-card__title + .account-card__title__avatar + = image_tag report.target_account.avatar.url, alt: '' + .display-name + %bdi + %strong.emojify.p-name= display_name(report.target_account, custom_emojify: true) + %span + = acct(report.target_account) + = fa_icon('lock') if report.target_account.locked? + - if report.target_account.note.present? + .account-card__bio.emojify + = prerender_custom_emojis(account_bio_format(report.target_account), report.target_account.emojis) + .account-card__actions + .account-card__counters + .account-card__counters__item + = friendly_number_to_human report.target_account.statuses_count + %small= t('accounts.posts', count: report.target_account.statuses_count).downcase + .account-card__counters__item + = friendly_number_to_human report.target_account.followers_count + %small= t('accounts.followers', count: report.target_account.followers_count).downcase + .account-card__counters__item + = friendly_number_to_human report.target_account.following_count + %small= t('accounts.following', count: report.target_account.following_count).downcase + .account-card__actions__button + = link_to t('admin.reports.view_profile'), admin_account_path(report.target_account_id), class: 'button' + .report-header__details.report-header__details--horizontal + .report-header__details__item + .report-header__details__item__header + %strong= t('admin.accounts.joined') + .report-header__details__item__content + %time.time-ago{ datetime: report.target_account.created_at.iso8601, title: l(report.target_account.created_at) }= l report.target_account.created_at + .report-header__details__item + .report-header__details__item__header + %strong= t('accounts.last_active') + .report-header__details__item__content + - if report.target_account.last_status_at.present? + %time.time-ago{ datetime: report.target_account.last_status_at.to_date.iso8601, title: l(report.target_account.last_status_at.to_date) }= l report.target_account.last_status_at + .report-header__details__item + .report-header__details__item__header + %strong= t('admin.accounts.strikes') + .report-header__details__item__content + = report.target_account.previous_strikes_count diff --git a/app/views/admin/reports/_header_details.html.haml b/app/views/admin/reports/_header_details.html.haml new file mode 100644 index 000000000..5878cd2ff --- /dev/null +++ b/app/views/admin/reports/_header_details.html.haml @@ -0,0 +1,53 @@ +.report-header__details + .report-header__details__item + .report-header__details__item__header + %strong= t('admin.reports.created_at') + .report-header__details__item__content + %time.formatted{ datetime: report.created_at.iso8601 } + .report-header__details__item + .report-header__details__item__header + %strong= t('admin.reports.reported_by') + .report-header__details__item__content + - if report.account.instance_actor? + = site_hostname + - elsif report.account.local? + = admin_account_link_to report.account + - else + = report.account.domain + .report-header__details__item + .report-header__details__item__header + %strong= t('admin.reports.status') + .report-header__details__item__content + - if report.action_taken? + = t('admin.reports.resolved') + - else + = t('admin.reports.unresolved') + - unless report.target_account.local? + .report-header__details__item + .report-header__details__item__header + %strong= t('admin.reports.forwarded') + .report-header__details__item__content + - if report.forwarded? + = t('simple_form.yes') + - else + = t('simple_form.no') + - if report.action_taken_by_account.present? + .report-header__details__item + .report-header__details__item__header + %strong= t('admin.reports.action_taken_by') + .report-header__details__item__content + = admin_account_link_to report.action_taken_by_account + - else + .report-header__details__item + .report-header__details__item__header + %strong= t('admin.reports.assigned') + .report-header__details__item__content + - if report.assigned_account.nil? + = t 'admin.reports.no_one_assigned' + - else + = admin_account_link_to report.assigned_account + — + - if report.assigned_account != current_user.account + = table_link_to 'user', t('admin.reports.assign_to_self'), assign_to_self_admin_report_path(report), method: :post + - elsif !report.assigned_account.nil? + = table_link_to 'trash', t('admin.reports.unassign'), unassign_admin_report_path(report), method: :post diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index 41ce73cfc..13a4d4834 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -8,106 +8,8 @@ = link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button' .report-header - .report-header__card - .account-card - .account-card__header - = image_tag @report.target_account.header.url, alt: '' - .account-card__title - .account-card__title__avatar - = image_tag @report.target_account.avatar.url, alt: '' - .display-name - %bdi - %strong.emojify.p-name= display_name(@report.target_account, custom_emojify: true) - %span - = acct(@report.target_account) - = fa_icon('lock') if @report.target_account.locked? - - if @report.target_account.note.present? - .account-card__bio.emojify - = prerender_custom_emojis(account_bio_format(@report.target_account), @report.target_account.emojis) - .account-card__actions - .account-card__counters - .account-card__counters__item - = friendly_number_to_human @report.target_account.statuses_count - %small= t('accounts.posts', count: @report.target_account.statuses_count).downcase - .account-card__counters__item - = friendly_number_to_human @report.target_account.followers_count - %small= t('accounts.followers', count: @report.target_account.followers_count).downcase - .account-card__counters__item - = friendly_number_to_human @report.target_account.following_count - %small= t('accounts.following', count: @report.target_account.following_count).downcase - .account-card__actions__button - = link_to t('admin.reports.view_profile'), admin_account_path(@report.target_account_id), class: 'button' - .report-header__details.report-header__details--horizontal - .report-header__details__item - .report-header__details__item__header - %strong= t('admin.accounts.joined') - .report-header__details__item__content - %time.time-ago{ datetime: @report.target_account.created_at.iso8601, title: l(@report.target_account.created_at) }= l @report.target_account.created_at - .report-header__details__item - .report-header__details__item__header - %strong= t('accounts.last_active') - .report-header__details__item__content - - if @report.target_account.last_status_at.present? - %time.time-ago{ datetime: @report.target_account.last_status_at.to_date.iso8601, title: l(@report.target_account.last_status_at.to_date) }= l @report.target_account.last_status_at - .report-header__details__item - .report-header__details__item__header - %strong= t('admin.accounts.strikes') - .report-header__details__item__content - = @report.target_account.previous_strikes_count - - .report-header__details - .report-header__details__item - .report-header__details__item__header - %strong= t('admin.reports.created_at') - .report-header__details__item__content - %time.formatted{ datetime: @report.created_at.iso8601 } - .report-header__details__item - .report-header__details__item__header - %strong= t('admin.reports.reported_by') - .report-header__details__item__content - - if @report.account.instance_actor? - = site_hostname - - elsif @report.account.local? - = admin_account_link_to @report.account - - else - = @report.account.domain - .report-header__details__item - .report-header__details__item__header - %strong= t('admin.reports.status') - .report-header__details__item__content - - if @report.action_taken? - = t('admin.reports.resolved') - - else - = t('admin.reports.unresolved') - - unless @report.target_account.local? - .report-header__details__item - .report-header__details__item__header - %strong= t('admin.reports.forwarded') - .report-header__details__item__content - - if @report.forwarded? - = t('simple_form.yes') - - else - = t('simple_form.no') - - if @report.action_taken_by_account.present? - .report-header__details__item - .report-header__details__item__header - %strong= t('admin.reports.action_taken_by') - .report-header__details__item__content - = admin_account_link_to @report.action_taken_by_account - - else - .report-header__details__item - .report-header__details__item__header - %strong= t('admin.reports.assigned') - .report-header__details__item__content - - if @report.assigned_account.nil? - = t 'admin.reports.no_one_assigned' - - else - = admin_account_link_to @report.assigned_account - — - - if @report.assigned_account != current_user.account - = table_link_to 'user', t('admin.reports.assign_to_self'), assign_to_self_admin_report_path(@report), method: :post - - elsif !@report.assigned_account.nil? - = table_link_to 'trash', t('admin.reports.unassign'), unassign_admin_report_path(@report), method: :post + = render 'admin/reports/header_card', report: @report + = render 'admin/reports/header_details', report: @report %hr.spacer @@ -118,33 +20,7 @@ = react_admin_component :report_reason_selector, id: @report.id, category: @report.category, rule_ids: @report.rule_ids&.map(&:to_s), disabled: @report.action_taken? - if @report.comment.present? - - if @report.account.instance_actor? - %p= t('admin.reports.comment_description_html', name: content_tag(:strong, site_hostname, class: 'username')) - - elsif @report.account.local? - %p= t('admin.reports.comment_description_html', name: content_tag(:strong, @report.account.username, class: 'username')) - - else - %p= t('admin.reports.comment_description_html', name: t('admin.reports.remote_user_placeholder', instance: @report.account.domain)) - - .report-notes - .report-notes__item - - if @report.account.local? && !@report.account.instance_actor? - = image_tag @report.account.avatar.url, class: 'report-notes__item__avatar' - - else - = image_tag(full_asset_url('avatars/original/missing.png', skip_pipeline: true), class: 'report-notes__item__avatar') - - .report-notes__item__header - %span.username - - if @report.account.instance_actor? - = site_hostname - - elsif @report.account.local? - = link_to @report.account.username, admin_account_path(@report.account_id) - - else - = link_to @report.account.domain, admin_instance_path(@report.account.domain) - %time.relative-formatted{ datetime: @report.created_at.iso8601 } - = l @report.created_at.to_date - - .report-notes__item__content - = simple_format(h(@report.comment)) + = render 'admin/reports/comment', report: @report %hr.spacer/ From fc9ab61448af94513524f14f5fd287d2a26ceeab Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 16 Oct 2023 12:20:58 -0400 Subject: [PATCH 50/69] Expand spec coverage of `CLI::Media` (#27437) --- spec/lib/mastodon/cli/media_spec.rb | 69 +++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/spec/lib/mastodon/cli/media_spec.rb b/spec/lib/mastodon/cli/media_spec.rb index 29f7d424a..9543640e9 100644 --- a/spec/lib/mastodon/cli/media_spec.rb +++ b/spec/lib/mastodon/cli/media_spec.rb @@ -4,9 +4,78 @@ require 'rails_helper' require 'mastodon/cli/media' describe Mastodon::CLI::Media do + let(:cli) { described_class.new } + describe '.exit_on_failure?' do it 'returns true' do expect(described_class.exit_on_failure?).to be true end end + + describe '#remove' do + context 'with --prune-profiles and --remove-headers' do + let(:options) { { prune_profiles: true, remove_headers: true } } + + it 'warns about usage and exits' do + expect { cli.invoke(:remove, [], options) }.to output( + a_string_including('--prune-profiles and --remove-headers should not be specified simultaneously') + ).to_stdout.and raise_error(SystemExit) + end + end + + context 'with --include-follows but not including --prune-profiles and --remove-headers' do + let(:options) { { include_follows: true } } + + it 'warns about usage and exits' do + expect { cli.invoke(:remove, [], options) }.to output( + a_string_including('--include-follows can only be used with --prune-profiles or --remove-headers') + ).to_stdout.and raise_error(SystemExit) + end + end + + context 'with a relevant account' do + let!(:account) do + Fabricate(:account, domain: 'example.com', updated_at: 1.month.ago, last_webfingered_at: 1.month.ago, avatar: attachment_fixture('attachment.jpg'), header: attachment_fixture('attachment.jpg')) + end + + context 'with --prune-profiles' do + let(:options) { { prune_profiles: true } } + + it 'removes account avatars' do + expect { cli.invoke(:remove, [], options) }.to output( + a_string_including('Visited 1') + ).to_stdout + + expect(account.reload.avatar).to be_blank + end + end + + context 'with --remove-headers' do + let(:options) { { remove_headers: true } } + + it 'removes account header' do + expect { cli.invoke(:remove, [], options) }.to output( + a_string_including('Visited 1') + ).to_stdout + + expect(account.reload.header).to be_blank + end + end + end + + context 'with a relevant media attachment' do + let!(:media_attachment) { Fabricate(:media_attachment, remote_url: 'https://example.com/image.jpg', created_at: 1.month.ago) } + + context 'without options' do + it 'removes account avatars' do + expect { cli.invoke(:remove) }.to output( + a_string_including('Removed 1') + ).to_stdout + + expect(media_attachment.reload.file).to be_blank + expect(media_attachment.reload.thumbnail).to be_blank + end + end + end + end end From 108470341788befa4602f315798c8de55d170553 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 17 Oct 2023 13:03:54 +0200 Subject: [PATCH 51/69] Update changelog (#27440) --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fab3104ba..f9303f011 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,46 @@ All notable changes to this project will be documented in this file. +## [4.2.1] - 2023-10-10 + +### Added + +- Add redirection on `/deck` URLs for logged-out users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27128)) +- Add support for v4.2.0 migrations to `tootctl maintenance fix-duplicates` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27147)) + +### Changed + +- Change some worker lock TTLs to be shorter-lived ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27246)) +- Change user archive export allowed period from 7 days to 6 days ([suddjian](https://github.com/mastodon/mastodon/pull/27200)) + +### Fixed + +- Fix duplicate reports being sent when reporting some remote posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27355)) +- Fix clicking on already-opened thread post scrolling to the top of the thread ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27331), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27338), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27350)) +- Fix some remote posts getting truncated ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27307)) +- Fix some cases of infinite scroll code trying to fetch inaccessible posts in a loop ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27286)) +- Fix `Vary` headers not being set on some redirects ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27272)) +- Fix mentions being matched in some URL query strings ([mjankowski](https://github.com/mastodon/mastodon/pull/25656)) +- Fix unexpected linebreak in version string in the Web UI ([vmstan](https://github.com/mastodon/mastodon/pull/26986)) +- Fix double scroll bars in some columns in advanced interface ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27187)) +- Fix boosts of local users being filtered in account timelines ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27204)) +- Fix multiple instances of the trend refresh scheduler sometimes running at once ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27253)) +- Fix importer returning negative row estimates ([jgillich](https://github.com/mastodon/mastodon/pull/27258)) +- Fix incorrectly keeping outdated update notices absent from the API endpoint ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27021)) +- Fix import progress not updating on certain failures ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27247)) +- Fix websocket connections being incorrectly decremented twice on errors ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/27238)) +- Fix explore prompt appearing because of posts being received out of order ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27211)) +- Fix explore prompt sometimes showing up when the home TL is loading ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27062)) +- Fix link handling of mentions in user profiles when logged out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27185)) +- Fix filtering audit log for entries about disabling 2FA ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27186)) +- Fix notification toasts not respecting reduce-motion ([c960657](https://github.com/mastodon/mastodon/pull/27178)) +- Fix retention dashboard not displaying correct month ([vmstan](https://github.com/mastodon/mastodon/pull/27180)) +- Fix tIME chunk not being properly removed from PNG uploads ([TheEssem](https://github.com/mastodon/mastodon/pull/27111)) +- Fix division by zero in video in bitrate computation code ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27129)) +- Fix inefficient queries in “Follows and followers” as well as several admin pages ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27116), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27306)) +- Fix ActiveRecord using two connection pools when no replica is defined ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27061)) +- Fix the search documentation URL in system checks ([renchap](https://github.com/mastodon/mastodon/pull/27036)) + ## [4.2.0] - 2023-09-21 The following changelog entries focus on changes visible to users, administrators, client developers or federated software developers, but there has also been a lot of code modernization, refactoring, and tooling work, in particular by [@danielmbrasil](https://github.com/danielmbrasil), [@mjankowski](https://github.com/mjankowski), [@nschonni](https://github.com/nschonni), [@renchap](https://github.com/renchap), and [@takayamaki](https://github.com/takayamaki). From 19900f647e81cb7078b9d329c4b57477068c4064 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 17 Oct 2023 07:05:28 -0400 Subject: [PATCH 52/69] Add coverage for `UnreservedUsernameValidator` (#25590) --- .../unreserved_username_validator.rb | 25 +++- .../unreserved_username_validator_spec.rb | 123 ++++++++++++++---- 2 files changed, 120 insertions(+), 28 deletions(-) diff --git a/app/validators/unreserved_username_validator.rb b/app/validators/unreserved_username_validator.rb index f82f4b91d..55a8c835f 100644 --- a/app/validators/unreserved_username_validator.rb +++ b/app/validators/unreserved_username_validator.rb @@ -11,16 +11,31 @@ class UnreservedUsernameValidator < ActiveModel::Validator private + def reserved_username? + pam_username_reserved? || settings_username_reserved? + end + + def pam_username_reserved? + pam_controlled? && pam_reserves_username? + end + def pam_controlled? - return false unless Devise.pam_authentication && Devise.pam_controlled_service + Devise.pam_authentication && Devise.pam_controlled_service + end - Rpam2.account(Devise.pam_controlled_service, @username).present? + def pam_reserves_username? + Rpam2.account(Devise.pam_controlled_service, @username) end - def reserved_username? - return true if pam_controlled? - return false unless Setting.reserved_usernames + def settings_username_reserved? + settings_has_reserved_usernames? && settings_reserves_username? + end + + def settings_has_reserved_usernames? + Setting.reserved_usernames.present? + end + def settings_reserves_username? Setting.reserved_usernames.include?(@username.downcase) end end diff --git a/spec/validators/unreserved_username_validator_spec.rb b/spec/validators/unreserved_username_validator_spec.rb index 6f353eeaf..0eb5f8368 100644 --- a/spec/validators/unreserved_username_validator_spec.rb +++ b/spec/validators/unreserved_username_validator_spec.rb @@ -2,41 +2,118 @@ require 'rails_helper' -RSpec.describe UnreservedUsernameValidator, type: :validator do - describe '#validate' do - before do - allow(validator).to receive(:reserved_username?) { reserved_username } - validator.validate(account) - end +describe UnreservedUsernameValidator do + let(:record_class) do + Class.new do + include ActiveModel::Validations + attr_accessor :username - let(:validator) { described_class.new } - let(:account) { instance_double(Account, username: username, errors: errors) } - let(:errors) { instance_double(ActiveModel::Errors, add: nil) } + validates_with UnreservedUsernameValidator + end + end + let(:record) { record_class.new } - context 'when @username is blank?' do - let(:username) { nil } + describe '#validate' do + context 'when username is nil' do + it 'does not add errors' do + record.username = nil - it 'not calls errors.add' do - expect(errors).to_not have_received(:add).with(:username, any_args) + expect(record).to be_valid + expect(record.errors).to be_empty end end - context 'when @username is not blank?' do - let(:username) { 'f' } + context 'when PAM is enabled' do + before do + allow(Devise).to receive(:pam_authentication).and_return(true) + end + + context 'with a pam service available' do + let(:service) { double } + let(:pam_class) do + Class.new do + def self.account(service, username); end + end + end + + before do + stub_const('Rpam2', pam_class) + allow(Devise).to receive(:pam_controlled_service).and_return(service) + end + + context 'when the account exists' do + before do + allow(Rpam2).to receive(:account).with(service, 'username').and_return(true) + end + + it 'adds errors to the record' do + record.username = 'username' + + expect(record).to_not be_valid + expect(record.errors.first.attribute).to eq(:username) + expect(record.errors.first.type).to eq(:reserved) + end + end + + context 'when the account does not exist' do + before do + allow(Rpam2).to receive(:account).with(service, 'username').and_return(false) + end - context 'with reserved_username?' do - let(:reserved_username) { true } + it 'does not add errors to the record' do + record.username = 'username' - it 'calls errors.add' do - expect(errors).to have_received(:add).with(:username, :reserved) + expect(record).to be_valid + expect(record.errors).to be_empty + end end end - context 'when username is not reserved' do - let(:reserved_username) { false } + context 'without a pam service' do + before do + allow(Devise).to receive(:pam_controlled_service).and_return(false) + end + + context 'when there are not any reserved usernames' do + before do + stub_reserved_usernames(nil) + end + + it 'does not add errors to the record' do + record.username = 'username' + + expect(record).to be_valid + expect(record.errors).to be_empty + end + end + + context 'when there are reserved usernames' do + before do + stub_reserved_usernames(%w(alice bob)) + end + + context 'when the username is reserved' do + it 'adds errors to the record' do + record.username = 'alice' + + expect(record).to_not be_valid + expect(record.errors.first.attribute).to eq(:username) + expect(record.errors.first.type).to eq(:reserved) + end + end + + context 'when the username is not reserved' do + it 'does not add errors to the record' do + record.username = 'chris' + + expect(record).to be_valid + expect(record.errors).to be_empty + end + end + end - it 'not calls errors.add' do - expect(errors).to_not have_received(:add).with(:username, any_args) + def stub_reserved_usernames(value) + allow(Setting).to receive(:[]).with('reserved_usernames').and_return(value) end end end From d54fec24e5ab97c0529172ee30d45350453e5b3c Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 17 Oct 2023 07:06:16 -0400 Subject: [PATCH 53/69] Add coverage for `CLI::PreviewCards#remove` command (#27441) --- spec/lib/mastodon/cli/preview_cards_spec.rb | 43 +++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/spec/lib/mastodon/cli/preview_cards_spec.rb b/spec/lib/mastodon/cli/preview_cards_spec.rb index b4b018b3b..1e064ed58 100644 --- a/spec/lib/mastodon/cli/preview_cards_spec.rb +++ b/spec/lib/mastodon/cli/preview_cards_spec.rb @@ -4,9 +4,52 @@ require 'rails_helper' require 'mastodon/cli/preview_cards' describe Mastodon::CLI::PreviewCards do + let(:cli) { described_class.new } + describe '.exit_on_failure?' do it 'returns true' do expect(described_class.exit_on_failure?).to be true end end + + describe '#remove' do + context 'with relevant preview cards' do + before do + Fabricate(:preview_card, updated_at: 10.years.ago, type: :link) + Fabricate(:preview_card, updated_at: 10.months.ago, type: :photo) + Fabricate(:preview_card, updated_at: 10.days.ago, type: :photo) + end + + context 'with no arguments' do + it 'deletes thumbnails for local preview cards' do + expect { cli.invoke(:remove) }.to output( + a_string_including('Removed 2 preview cards') + .and(a_string_including('approx. 119 KB')) + ).to_stdout + end + end + + context 'with the --link option' do + let(:options) { { link: true } } + + it 'deletes thumbnails for local preview cards' do + expect { cli.invoke(:remove, [], options) }.to output( + a_string_including('Removed 1 link-type preview cards') + .and(a_string_including('approx. 59.6 KB')) + ).to_stdout + end + end + + context 'with the --days option' do + let(:options) { { days: 365 } } + + it 'deletes thumbnails for local preview cards' do + expect { cli.invoke(:remove, [], options) }.to output( + a_string_including('Removed 1 preview cards') + .and(a_string_including('approx. 59.6 KB')) + ).to_stdout + end + end + end + end end From 12bb7be8b558122e44a66a4337e7db999c11e0b3 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 17 Oct 2023 07:32:10 -0400 Subject: [PATCH 54/69] Spec speed ups on `AccountsController` spec (#25391) --- spec/controllers/accounts_controller_spec.rb | 550 +++++++------------ 1 file changed, 210 insertions(+), 340 deletions(-) diff --git a/spec/controllers/accounts_controller_spec.rb b/spec/controllers/accounts_controller_spec.rb index d46fdb108..cc9e3198b 100644 --- a/spec/controllers/accounts_controller_spec.rb +++ b/spec/controllers/accounts_controller_spec.rb @@ -7,449 +7,319 @@ RSpec.describe AccountsController do let(:account) { Fabricate(:account) } - describe 'GET #show' do - let(:format) { 'html' } + shared_examples 'unapproved account check' do + before { account.user.update(approved: false) } - let!(:status) { Fabricate(:status, account: account) } - let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) } - let!(:status_self_reply) { Fabricate(:status, account: account, thread: status) } - let!(:status_media) { Fabricate(:status, account: account) } - let!(:status_pinned) { Fabricate(:status, account: account) } - let!(:status_private) { Fabricate(:status, account: account, visibility: :private) } - let!(:status_direct) { Fabricate(:status, account: account, visibility: :direct) } - let!(:status_reblog) { Fabricate(:status, account: account, reblog: Fabricate(:status)) } + it 'returns http not found' do + get :show, params: { username: account.username, format: format } - before do - status_media.media_attachments << Fabricate(:media_attachment, account: account, type: :image) - account.pinned_statuses << status_pinned - account.pinned_statuses << status_private + expect(response).to have_http_status(404) end + end - shared_examples 'preliminary checks' do - context 'when account is not approved' do - before do - account.user.update(approved: false) - end - - it 'returns http not found' do - get :show, params: { username: account.username, format: format } - expect(response).to have_http_status(404) - end - end + shared_examples 'permanently suspended account check' do + before do + account.suspend! + account.deletion_request.destroy end - context 'with HTML' do - let(:format) { 'html' } + it 'returns http gone' do + get :show, params: { username: account.username, format: format } - it_behaves_like 'preliminary checks' - - context 'when account is permanently suspended' do - before do - account.suspend! - account.deletion_request.destroy - end - - it 'returns http gone' do - get :show, params: { username: account.username, format: format } - expect(response).to have_http_status(410) - end - end - - context 'when account is temporarily suspended' do - before do - account.suspend! - end - - it 'returns http forbidden' do - get :show, params: { username: account.username, format: format } - expect(response).to have_http_status(403) - end - end + expect(response).to have_http_status(410) + end + end - shared_examples 'common response characteristics' do - it 'returns http success' do - expect(response).to have_http_status(200) - end + shared_examples 'temporarily suspended account check' do |code: 403| + before { account.suspend! } - it 'returns Link header' do - expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account) - end + it 'returns appropriate http response code' do + get :show, params: { username: account.username, format: format } - it 'renders show template' do - expect(response).to render_template(:show) - end - end + expect(response).to have_http_status(code) + end + end - context 'with a normal account in an HTML request' do - before do - get :show, params: { username: account.username, format: format } - end + describe 'GET #show' do + context 'with basic account status checks' do + context 'with HTML' do + let(:format) { 'html' } - it_behaves_like 'common response characteristics' + it_behaves_like 'unapproved account check' + it_behaves_like 'permanently suspended account check' + it_behaves_like 'temporarily suspended account check' end - context 'with replies' do - before do - allow(controller).to receive(:replies_requested?).and_return(true) - get :show, params: { username: account.username, format: format } - end + context 'with JSON' do + let(:format) { 'json' } - it_behaves_like 'common response characteristics' + it_behaves_like 'unapproved account check' + it_behaves_like 'permanently suspended account check' + it_behaves_like 'temporarily suspended account check', code: 200 end - context 'with media' do - before do - allow(controller).to receive(:media_requested?).and_return(true) - get :show, params: { username: account.username, format: format } - end + context 'with RSS' do + let(:format) { 'rss' } - it_behaves_like 'common response characteristics' - end - - context 'with tag' do - let(:tag) { Fabricate(:tag) } - - let!(:status_tag) { Fabricate(:status, account: account) } - - before do - allow(controller).to receive(:tag_requested?).and_return(true) - status_tag.tags << tag - get :show, params: { username: account.username, format: format, tag: tag.to_param } - end - - it_behaves_like 'common response characteristics' + it_behaves_like 'unapproved account check' + it_behaves_like 'permanently suspended account check' + it_behaves_like 'temporarily suspended account check' end end - context 'with JSON' do - let(:authorized_fetch_mode) { false } - let(:format) { 'json' } + context 'with existing statuses' do + let!(:status) { Fabricate(:status, account: account) } + let!(:status_reply) { Fabricate(:status, account: account, thread: Fabricate(:status)) } + let!(:status_self_reply) { Fabricate(:status, account: account, thread: status) } + let!(:status_media) { Fabricate(:status, account: account) } + let!(:status_pinned) { Fabricate(:status, account: account) } + let!(:status_private) { Fabricate(:status, account: account, visibility: :private) } + let!(:status_direct) { Fabricate(:status, account: account, visibility: :direct) } + let!(:status_reblog) { Fabricate(:status, account: account, reblog: Fabricate(:status)) } before do - allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode) + status_media.media_attachments << Fabricate(:media_attachment, account: account, type: :image) + account.pinned_statuses << status_pinned + account.pinned_statuses << status_private end - it_behaves_like 'preliminary checks' - - context 'when account is suspended permanently' do - before do - account.suspend! - account.deletion_request.destroy - end - - it 'returns http gone' do - get :show, params: { username: account.username, format: format } - expect(response).to have_http_status(410) - end - end + context 'with HTML' do + let(:format) { 'html' } - context 'when account is suspended temporarily' do - before do - account.suspend! - end + shared_examples 'common HTML response' do + it 'returns a standard HTML response', :aggregate_failures do + expect(response).to have_http_status(200) - it 'returns http success' do - get :show, params: { username: account.username, format: format } - expect(response).to have_http_status(200) - end - end + expect(response.headers['Link'].to_s).to include ActivityPub::TagManager.instance.uri_for(account) - context 'with a normal account in a JSON request' do - before do - get :show, params: { username: account.username, format: format } + expect(response).to render_template(:show) + end end - it 'returns http success' do - expect(response).to have_http_status(200) - end + context 'with a normal account in an HTML request' do + before do + get :show, params: { username: account.username, format: format } + end - it 'returns application/activity+json' do - expect(response.media_type).to eq 'application/activity+json' + it_behaves_like 'common HTML response' end - it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' + context 'with replies' do + before do + allow(controller).to receive(:replies_requested?).and_return(true) + get :show, params: { username: account.username, format: format } + end - it 'renders account' do - json = body_as_json - expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) + it_behaves_like 'common HTML response' end - context 'with authorized fetch mode' do - let(:authorized_fetch_mode) { true } - - it 'returns http unauthorized' do - expect(response).to have_http_status(401) + context 'with media' do + before do + allow(controller).to receive(:media_requested?).and_return(true) + get :show, params: { username: account.username, format: format } end - end - end - - context 'when signed in' do - let(:user) { Fabricate(:user) } - before do - sign_in(user) - get :show, params: { username: account.username, format: format } + it_behaves_like 'common HTML response' end - it 'returns http success' do - expect(response).to have_http_status(200) - end + context 'with tag' do + let(:tag) { Fabricate(:tag) } - it 'returns application/activity+json' do - expect(response.media_type).to eq 'application/activity+json' - end + let!(:status_tag) { Fabricate(:status, account: account) } - it 'returns private Cache-Control header' do - expect(response.headers['Cache-Control']).to include 'private' - end + before do + allow(controller).to receive(:tag_requested?).and_return(true) + status_tag.tags << tag + get :show, params: { username: account.username, format: format, tag: tag.to_param } + end - it 'renders account' do - json = body_as_json - expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) + it_behaves_like 'common HTML response' end end - context 'with signature' do - let(:remote_account) { Fabricate(:account, domain: 'example.com') } + context 'with JSON' do + let(:authorized_fetch_mode) { false } + let(:format) { 'json' } before do - allow(controller).to receive(:signed_request_actor).and_return(remote_account) - get :show, params: { username: account.username, format: format } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns application/activity+json' do - expect(response.media_type).to eq 'application/activity+json' + allow(controller).to receive(:authorized_fetch_mode?).and_return(authorized_fetch_mode) end - it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' - - it 'renders account' do - json = body_as_json - expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) - end - - context 'with authorized fetch mode' do - let(:authorized_fetch_mode) { true } + context 'with a normal account in a JSON request' do + before do + get :show, params: { username: account.username, format: format } + end - it 'returns http success' do + it 'returns a JSON version of the account', :aggregate_failures do expect(response).to have_http_status(200) - end - it 'returns application/activity+json' do expect(response.media_type).to eq 'application/activity+json' - end - - it 'returns private Cache-Control header' do - expect(response.headers['Cache-Control']).to include 'private' - end - - it 'returns Vary header with Signature' do - expect(response.headers['Vary']).to include 'Signature' - end - it 'renders account' do - json = body_as_json - expect(json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) + expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) end - end - end - end - context 'with RSS' do - let(:format) { 'rss' } + it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' - it_behaves_like 'preliminary checks' + context 'with authorized fetch mode' do + let(:authorized_fetch_mode) { true } - context 'when account is permanently suspended' do - before do - account.suspend! - account.deletion_request.destroy - end - - it 'returns http gone' do - get :show, params: { username: account.username, format: format } - expect(response).to have_http_status(410) - end - end - - context 'when account is temporarily suspended' do - before do - account.suspend! + it 'returns http unauthorized' do + expect(response).to have_http_status(401) + end + end end - it 'returns http forbidden' do - get :show, params: { username: account.username, format: format } - expect(response).to have_http_status(403) - end - end + context 'when signed in' do + let(:user) { Fabricate(:user) } - shared_examples 'common response characteristics' do - it 'returns http success' do - expect(response).to have_http_status(200) - end + before do + sign_in(user) + get :show, params: { username: account.username, format: format } + end - it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' - end + it 'returns a private JSON version of the account', :aggregate_failures do + expect(response).to have_http_status(200) - context 'with a normal account in an RSS request' do - before do - get :show, params: { username: account.username, format: format } - end + expect(response.media_type).to eq 'application/activity+json' - it_behaves_like 'common response characteristics' + expect(response.headers['Cache-Control']).to include 'private' - it 'renders public status' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status)) + expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) + end end - it 'renders self-reply' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply)) - end + context 'with signature' do + let(:remote_account) { Fabricate(:account, domain: 'example.com') } - it 'renders status with media' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media)) - end + before do + allow(controller).to receive(:signed_request_actor).and_return(remote_account) + get :show, params: { username: account.username, format: format } + end - it 'does not render reblog' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog)) - end + it 'returns a JSON version of the account', :aggregate_failures do + expect(response).to have_http_status(200) - it 'does not render private status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private)) - end + expect(response.media_type).to eq 'application/activity+json' - it 'does not render direct status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct)) - end + expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) + end - it 'does not render reply to someone else' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply)) - end - end + it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' - context 'with replies' do - before do - allow(controller).to receive(:replies_requested?).and_return(true) - get :show, params: { username: account.username, format: format } - end + context 'with authorized fetch mode' do + let(:authorized_fetch_mode) { true } - it_behaves_like 'common response characteristics' + it 'returns a private signature JSON version of the account', :aggregate_failures do + expect(response).to have_http_status(200) - it 'renders public status' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status)) - end + expect(response.media_type).to eq 'application/activity+json' - it 'renders self-reply' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_self_reply)) - end + expect(response.headers['Cache-Control']).to include 'private' - it 'renders status with media' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media)) - end + expect(response.headers['Vary']).to include 'Signature' - it 'does not render reblog' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog)) - end - - it 'does not render private status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private)) - end - - it 'does not render direct status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct)) - end - - it 'renders reply to someone else' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_reply)) + expect(body_as_json).to include(:id, :type, :preferredUsername, :inbox, :publicKey, :name, :summary) + end + end end end - context 'with media' do - before do - allow(controller).to receive(:media_requested?).and_return(true) - get :show, params: { username: account.username, format: format } - end - - it_behaves_like 'common response characteristics' + context 'with RSS' do + let(:format) { 'rss' } - it 'does not render public status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status)) - end - - it 'does not render self-reply' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply)) - end - - it 'renders status with media' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_media)) - end + shared_examples 'common RSS response' do + it 'returns http success' do + expect(response).to have_http_status(200) + end - it 'does not render reblog' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog)) + it_behaves_like 'cacheable response', expects_vary: 'Accept, Accept-Language, Cookie' end - it 'does not render private status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private)) - end + context 'with a normal account in an RSS request' do + before do + get :show, params: { username: account.username, format: format } + end - it 'does not render direct status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct)) - end + it_behaves_like 'common RSS response' - it 'does not render reply to someone else' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply)) + it 'responds with correct statuses', :aggregate_failures do + expect(response.body).to include_status_tag(status_media) + expect(response.body).to include_status_tag(status_self_reply) + expect(response.body).to include_status_tag(status) + expect(response.body).to_not include_status_tag(status_direct) + expect(response.body).to_not include_status_tag(status_private) + expect(response.body).to_not include_status_tag(status_reblog.reblog) + expect(response.body).to_not include_status_tag(status_reply) + end end - end - - context 'with tag' do - let(:tag) { Fabricate(:tag) } - - let!(:status_tag) { Fabricate(:status, account: account) } - before do - allow(controller).to receive(:tag_requested?).and_return(true) - status_tag.tags << tag - get :show, params: { username: account.username, format: format, tag: tag.to_param } - end + context 'with replies' do + before do + allow(controller).to receive(:replies_requested?).and_return(true) + get :show, params: { username: account.username, format: format } + end - it_behaves_like 'common response characteristics' + it_behaves_like 'common RSS response' - it 'does not render public status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status)) + it 'responds with correct statuses with replies', :aggregate_failures do + expect(response.body).to include_status_tag(status_media) + expect(response.body).to include_status_tag(status_reply) + expect(response.body).to include_status_tag(status_self_reply) + expect(response.body).to include_status_tag(status) + expect(response.body).to_not include_status_tag(status_direct) + expect(response.body).to_not include_status_tag(status_private) + expect(response.body).to_not include_status_tag(status_reblog.reblog) + end end - it 'does not render self-reply' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_self_reply)) - end + context 'with media' do + before do + allow(controller).to receive(:media_requested?).and_return(true) + get :show, params: { username: account.username, format: format } + end - it 'does not render status with media' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_media)) - end + it_behaves_like 'common RSS response' - it 'does not render reblog' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reblog.reblog)) + it 'responds with correct statuses with media', :aggregate_failures do + expect(response.body).to include_status_tag(status_media) + expect(response.body).to_not include_status_tag(status_direct) + expect(response.body).to_not include_status_tag(status_private) + expect(response.body).to_not include_status_tag(status_reblog.reblog) + expect(response.body).to_not include_status_tag(status_reply) + expect(response.body).to_not include_status_tag(status_self_reply) + expect(response.body).to_not include_status_tag(status) + end end - it 'does not render private status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_private)) - end + context 'with tag' do + let(:tag) { Fabricate(:tag) } - it 'does not render direct status' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_direct)) - end + let!(:status_tag) { Fabricate(:status, account: account) } - it 'does not render reply to someone else' do - expect(response.body).to_not include(ActivityPub::TagManager.instance.url_for(status_reply)) - end + before do + allow(controller).to receive(:tag_requested?).and_return(true) + status_tag.tags << tag + get :show, params: { username: account.username, format: format, tag: tag.to_param } + end - it 'renders status with tag' do - expect(response.body).to include(ActivityPub::TagManager.instance.url_for(status_tag)) + it_behaves_like 'common RSS response' + + it 'responds with correct statuses with a tag', :aggregate_failures do + expect(response.body).to include_status_tag(status_tag) + expect(response.body).to_not include_status_tag(status_direct) + expect(response.body).to_not include_status_tag(status_media) + expect(response.body).to_not include_status_tag(status_private) + expect(response.body).to_not include_status_tag(status_reblog.reblog) + expect(response.body).to_not include_status_tag(status_reply) + expect(response.body).to_not include_status_tag(status_self_reply) + expect(response.body).to_not include_status_tag(status) + end end end end end + + def include_status_tag(status) + include ActivityPub::TagManager.instance.url_for(status) + end end From c4bddc9855886b4ba60316fbcbc04375ee720da7 Mon Sep 17 00:00:00 2001 From: Victor Lee <43049052+leevic31@users.noreply.github.com> Date: Tue, 17 Oct 2023 08:56:24 -0400 Subject: [PATCH 55/69] Add spec for poll model (#23399) Co-authored-by: Nick Schonning Co-authored-by: Claire --- spec/models/poll_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/models/poll_spec.rb b/spec/models/poll_spec.rb index 8ae04ca41..5aa5548cc 100644 --- a/spec/models/poll_spec.rb +++ b/spec/models/poll_spec.rb @@ -29,4 +29,23 @@ describe Poll do end end end + + describe 'validations' do + context 'when valid' do + let(:poll) { Fabricate.build(:poll) } + + it 'is valid with valid attributes' do + expect(poll).to be_valid + end + end + + context 'when not valid' do + let(:poll) { Fabricate.build(:poll, expires_at: nil) } + + it 'is invalid without an expire date' do + poll.valid? + expect(poll).to model_have_error_on_field(:expires_at) + end + end + end end From 935d54124e80e9fe5365c724e5c8827a2b3ed5b3 Mon Sep 17 00:00:00 2001 From: Stanislas Signoud Date: Tue, 17 Oct 2023 14:59:07 +0200 Subject: [PATCH 56/69] Fix missing redirections to make sure /home redirect to the advanced UI (#27378) --- app/javascript/mastodon/features/ui/index.jsx | 2 ++ app/javascript/mastodon/initial_state.js | 1 + 2 files changed, 3 insertions(+) diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index ac5e2d936..2f39be24a 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -184,7 +184,9 @@ class SwitchingColumnsArea extends PureComponent { {singleColumn ? : null} {singleColumn && pathName.startsWith('/deck/') ? : null} + {/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */} {!singleColumn && pathName === '/getting-started' ? : null} + {!singleColumn && pathName === '/home' ? : null} diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 11cd2a167..bf5ce556e 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -100,6 +100,7 @@ const initialPath = document.querySelector("head meta[name=initialPath]")?.getAt /** @type {boolean} */ export const hasMultiColumnPath = initialPath === '/' || initialPath === '/getting-started' + || initialPath === '/home' || initialPath.startsWith('/deck'); /** From 1fb4e40145f49bd335d22c7f560e770647fe77f2 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Tue, 17 Oct 2023 14:59:39 +0200 Subject: [PATCH 57/69] Run scheduled actions only on the main repo (#27343) --- .github/workflows/build-nightly.yml | 1 + .github/workflows/crowdin-download.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml index aa1f916af..1790d5c84 100644 --- a/.github/workflows/build-nightly.yml +++ b/.github/workflows/build-nightly.yml @@ -11,6 +11,7 @@ permissions: jobs: compute-suffix: runs-on: ubuntu-latest + if: github.repository == 'mastodon/mastodon' steps: - id: version_vars env: diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml index e5385808d..8268788be 100644 --- a/.github/workflows/crowdin-download.yml +++ b/.github/workflows/crowdin-download.yml @@ -11,6 +11,7 @@ permissions: jobs: download-translations: runs-on: ubuntu-latest + if: github.repository == 'mastodon/mastodon' steps: - name: Checkout From c0cda1adaf8a3b14d45be2844ff1688cf0f7cd18 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Tue, 17 Oct 2023 22:24:45 +0900 Subject: [PATCH 58/69] Let use http_hidden_proxy on docker-compose (#27054) --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index d19f278f7..bcfa4c85f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -111,7 +111,7 @@ services: test: ['CMD-SHELL', "ps aux | grep '[s]idekiq\ 6' || false"] ## Uncomment to enable federation with tor instances along with adding the following ENV variables - ## http_proxy=http://privoxy:8118 + ## http_hidden_proxy=http://privoxy:8118 ## ALLOW_ACCESS_TO_HIDDEN_SERVICE=true # tor: # image: sirboops/tor From b34a2b1b334f94cb5491e2472f92f22f4b8a7683 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 17 Oct 2023 15:30:12 +0200 Subject: [PATCH 59/69] Fix errors in CLI specs (#27399) --- spec/lib/mastodon/cli/accounts_spec.rb | 87 ++++++++++++-------------- 1 file changed, 41 insertions(+), 46 deletions(-) diff --git a/spec/lib/mastodon/cli/accounts_spec.rb b/spec/lib/mastodon/cli/accounts_spec.rb index a263d673d..5ecea5ea1 100644 --- a/spec/lib/mastodon/cli/accounts_spec.rb +++ b/spec/lib/mastodon/cli/accounts_spec.rb @@ -6,6 +6,24 @@ require 'mastodon/cli/accounts' describe Mastodon::CLI::Accounts do let(:cli) { described_class.new } + # `parallelize_with_progress` cannot run in transactions, so instead, + # stub it with an alternative implementation that runs sequentially + # and can run in transactions. + def stub_parallelize_with_progress! + allow(cli).to receive(:parallelize_with_progress) do |scope, &block| + aggregate = 0 + total = 0 + + scope.reorder(nil).find_each do |record| + value = block.call(record) + aggregate += value if value.is_a?(Integer) + total += 1 + end + + [total, aggregate] + end + end + describe '.exit_on_failure?' do it 'returns true' do expect(described_class.exit_on_failure?).to be true @@ -551,20 +569,15 @@ describe Mastodon::CLI::Accounts do let!(:follower_rony) { Fabricate(:account, username: 'rony') } let!(:follower_charles) { Fabricate(:account, username: 'charles') } let(:follow_service) { instance_double(FollowService, call: nil) } - let(:scope) { Account.local.without_suspended } before do - allow(cli).to receive(:parallelize_with_progress).and_yield(follower_bob) - .and_yield(follower_rony) - .and_yield(follower_charles) - .and_return([3, nil]) allow(FollowService).to receive(:new).and_return(follow_service) + stub_parallelize_with_progress! end it 'makes all local accounts follow the target account' do cli.follow(target_account.username) - expect(cli).to have_received(:parallelize_with_progress).with(scope).once expect(follow_service).to have_received(:call).with(follower_bob, target_account, any_args).once expect(follow_service).to have_received(:call).with(follower_rony, target_account, any_args).once expect(follow_service).to have_received(:call).with(follower_charles, target_account, any_args).once @@ -572,7 +585,7 @@ describe Mastodon::CLI::Accounts do it 'displays a successful message' do expect { cli.follow(target_account.username) }.to output( - a_string_including('OK, followed target from 3 accounts') + a_string_including("OK, followed target from #{Account.local.count} accounts") ).to_stdout end end @@ -592,26 +605,21 @@ describe Mastodon::CLI::Accounts do context 'when the given username is found' do let!(:target_account) { Fabricate(:account) } - let!(:follower_chris) { Fabricate(:account, username: 'chris') } - let!(:follower_rambo) { Fabricate(:account, username: 'rambo') } - let!(:follower_ana) { Fabricate(:account, username: 'ana') } + let!(:follower_chris) { Fabricate(:account, username: 'chris', domain: nil) } + let!(:follower_rambo) { Fabricate(:account, username: 'rambo', domain: nil) } + let!(:follower_ana) { Fabricate(:account, username: 'ana', domain: nil) } let(:unfollow_service) { instance_double(UnfollowService, call: nil) } - let(:scope) { target_account.followers.local } before do accounts = [follower_chris, follower_rambo, follower_ana] - accounts.each { |account| target_account.follow!(account) } - allow(cli).to receive(:parallelize_with_progress).and_yield(follower_chris) - .and_yield(follower_rambo) - .and_yield(follower_ana) - .and_return([3, nil]) + accounts.each { |account| account.follow!(target_account) } allow(UnfollowService).to receive(:new).and_return(unfollow_service) + stub_parallelize_with_progress! end it 'makes all local accounts unfollow the target account' do cli.unfollow(target_account.username) - expect(cli).to have_received(:parallelize_with_progress).with(scope).once expect(unfollow_service).to have_received(:call).with(follower_chris, target_account).once expect(unfollow_service).to have_received(:call).with(follower_rambo, target_account).once expect(unfollow_service).to have_received(:call).with(follower_ana, target_account).once @@ -671,6 +679,8 @@ describe Mastodon::CLI::Accounts do let(:scope) { Account.remote } before do + # TODO: we should be using `stub_parallelize_with_progress!` but + # this makes the assertions harder to write allow(cli).to receive(:parallelize_with_progress).and_yield(remote_account_example_com) .and_yield(account_example_net) .and_return([2, nil]) @@ -1112,26 +1122,19 @@ describe Mastodon::CLI::Accounts do describe '#cull' do let(:delete_account_service) { instance_double(DeleteAccountService, call: nil) } - let!(:tom) { Fabricate(:account, updated_at: 30.days.ago, username: 'tom', uri: 'https://example.com/users/tom', domain: 'example.com') } - let!(:bob) { Fabricate(:account, updated_at: 30.days.ago, last_webfingered_at: nil, username: 'bob', uri: 'https://example.org/users/bob', domain: 'example.org') } - let!(:gon) { Fabricate(:account, updated_at: 15.days.ago, last_webfingered_at: 15.days.ago, username: 'gon', uri: 'https://example.net/users/gon', domain: 'example.net') } - let!(:ana) { Fabricate(:account, username: 'ana', uri: 'https://example.com/users/ana', domain: 'example.com') } - let!(:tales) { Fabricate(:account, updated_at: 10.days.ago, last_webfingered_at: nil, username: 'tales', uri: 'https://example.net/users/tales', domain: 'example.net') } + let!(:tom) { Fabricate(:account, updated_at: 30.days.ago, username: 'tom', uri: 'https://example.com/users/tom', domain: 'example.com', protocol: :activitypub) } + let!(:bob) { Fabricate(:account, updated_at: 30.days.ago, last_webfingered_at: nil, username: 'bob', uri: 'https://example.org/users/bob', domain: 'example.org', protocol: :activitypub) } + let!(:gon) { Fabricate(:account, updated_at: 15.days.ago, last_webfingered_at: 15.days.ago, username: 'gon', uri: 'https://example.net/users/gon', domain: 'example.net', protocol: :activitypub) } + let!(:ana) { Fabricate(:account, username: 'ana', uri: 'https://example.com/users/ana', domain: 'example.com', protocol: :activitypub) } + let!(:tales) { Fabricate(:account, updated_at: 10.days.ago, last_webfingered_at: nil, username: 'tales', uri: 'https://example.net/users/tales', domain: 'example.net', protocol: :activitypub) } before do allow(DeleteAccountService).to receive(:new).and_return(delete_account_service) end context 'when no domain is specified' do - let(:scope) { Account.remote.where(protocol: :activitypub).partitioned } - before do - allow(cli).to receive(:parallelize_with_progress).and_yield(tom) - .and_yield(bob) - .and_yield(gon) - .and_yield(ana) - .and_yield(tales) - .and_return([5, 3]) + stub_parallelize_with_progress! stub_request(:head, 'https://example.org/users/bob').to_return(status: 404) stub_request(:head, 'https://example.net/users/gon').to_return(status: 410) stub_request(:head, 'https://example.net/users/tales').to_return(status: 200) @@ -1140,7 +1143,6 @@ describe Mastodon::CLI::Accounts do it 'deletes all inactive remote accounts that longer exist in the origin server' do cli.cull - expect(cli).to have_received(:parallelize_with_progress).with(scope).once expect(delete_account_service).to have_received(:call).with(bob, reserve_username: false).once expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once end @@ -1148,35 +1150,27 @@ describe Mastodon::CLI::Accounts do it 'does not delete any active remote account that still exists in the origin server' do cli.cull - expect(cli).to have_received(:parallelize_with_progress).with(scope).once expect(delete_account_service).to_not have_received(:call).with(tom, reserve_username: false) expect(delete_account_service).to_not have_received(:call).with(ana, reserve_username: false) expect(delete_account_service).to_not have_received(:call).with(tales, reserve_username: false) end it 'touches inactive remote accounts that have not been deleted' do - allow(tales).to receive(:touch) - - cli.cull - - expect(tales).to have_received(:touch).once + expect { cli.cull }.to(change { tales.reload.updated_at }) end it 'displays the summary correctly' do expect { cli.cull }.to output( - a_string_including('Visited 5 accounts, removed 3') + a_string_including('Visited 5 accounts, removed 2') ).to_stdout end end context 'when a domain is specified' do let(:domain) { 'example.net' } - let(:scope) { Account.remote.where(protocol: :activitypub, domain: domain).partitioned } before do - allow(cli).to receive(:parallelize_with_progress).and_yield(gon) - .and_yield(tales) - .and_return([2, 2]) + stub_parallelize_with_progress! stub_request(:head, 'https://example.net/users/gon').to_return(status: 410) stub_request(:head, 'https://example.net/users/tales').to_return(status: 404) end @@ -1184,13 +1178,12 @@ describe Mastodon::CLI::Accounts do it 'deletes inactive remote accounts that longer exist in the specified domain' do cli.cull(domain) - expect(cli).to have_received(:parallelize_with_progress).with(scope).once expect(delete_account_service).to have_received(:call).with(gon, reserve_username: false).once expect(delete_account_service).to have_received(:call).with(tales, reserve_username: false).once end it 'displays the summary correctly' do - expect { cli.cull }.to output( + expect { cli.cull(domain) }.to output( a_string_including('Visited 2 accounts, removed 2') ).to_stdout end @@ -1199,7 +1192,9 @@ describe Mastodon::CLI::Accounts do context 'when a domain is unavailable' do shared_examples 'an unavailable domain' do before do - allow(cli).to receive(:parallelize_with_progress).and_yield(tales).and_return([1, 0]) + stub_parallelize_with_progress! + stub_request(:head, 'https://example.org/users/bob').to_return(status: 200) + stub_request(:head, 'https://example.net/users/gon').to_return(status: 200) end it 'skips accounts from the unavailable domain' do @@ -1210,7 +1205,7 @@ describe Mastodon::CLI::Accounts do it 'displays the summary correctly' do expect { cli.cull }.to output( - a_string_including("Visited 1 accounts, removed 0\nThe following domains were not available during the check:\n example.net") + a_string_including("Visited 5 accounts, removed 0\nThe following domains were not available during the check:\n example.net") ).to_stdout end end From 65cd0700e4cbc05372140d4fe33bfc6e2412a161 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 17 Oct 2023 09:58:42 -0400 Subject: [PATCH 60/69] Refactor domains cli to remove model call from regex (#25878) --- config/brakeman.ignore | 24 ------------------------ lib/mastodon/cli/domains.rb | 6 +++++- 2 files changed, 5 insertions(+), 25 deletions(-) diff --git a/config/brakeman.ignore b/config/brakeman.ignore index 02ce23a07..9f85ccb6a 100644 --- a/config/brakeman.ignore +++ b/config/brakeman.ignore @@ -33,30 +33,6 @@ ], "note": "" }, - { - "warning_type": "Denial of Service", - "warning_code": 76, - "fingerprint": "7b6abba5699755348e7ee82a4694bfbf574b41c7cce2d0db0f7c11ae3f983c72", - "check_name": "RegexDoS", - "message": "Model attribute used in regular expression", - "file": "lib/mastodon/cli/domains.rb", - "line": 128, - "link": "https://brakemanscanner.org/docs/warning_types/denial_of_service/", - "code": "/\\.?(#{DomainBlock.where(:severity => 1).pluck(:domain).map do\n Regexp.escape(domain)\n end.join(\"|\")})$/", - "render_path": null, - "location": { - "type": "method", - "class": "Mastodon::CLI::Domains", - "method": "crawl" - }, - "user_input": "DomainBlock.where(:severity => 1).pluck(:domain)", - "confidence": "Weak", - "cwe_id": [ - 20, - 185 - ], - "note": "" - }, { "warning_type": "Cross-Site Scripting", "warning_code": 4, diff --git a/lib/mastodon/cli/domains.rb b/lib/mastodon/cli/domains.rb index d17b25368..329f17167 100644 --- a/lib/mastodon/cli/domains.rb +++ b/lib/mastodon/cli/domains.rb @@ -125,7 +125,7 @@ module Mastodon::CLI failed = Concurrent::AtomicFixnum.new(0) start_at = Time.now.to_f seed = start ? [start] : Instance.pluck(:domain) - blocked_domains = /\.?(#{DomainBlock.where(severity: 1).pluck(:domain).map { |domain| Regexp.escape(domain) }.join('|')})$/ + blocked_domains = /\.?(#{Regexp.union(domain_block_suspended_domains).source})$/ progress = create_progress_bar pool = Concurrent::ThreadPoolExecutor.new(min_threads: 0, max_threads: options[:concurrency], idletime: 10, auto_terminate: true, max_queue: 0) @@ -189,6 +189,10 @@ module Mastodon::CLI private + def domain_block_suspended_domains + DomainBlock.suspend.pluck(:domain) + end + def stats_to_summary(stats, processed, failed, start_at) stats.compact! From 1996fb6fd6e5e44884c429d9c651825e0e72302f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:10:16 +0200 Subject: [PATCH 61/69] Update dependency puma to v6.4.0 (#27006) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index ce21bc0a2..4955e8283 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -513,7 +513,7 @@ GEM premailer (~> 1.7, >= 1.7.9) private_address_check (0.5.0) public_suffix (5.0.3) - puma (6.3.1) + puma (6.4.0) nio4r (~> 2.0) pundit (2.3.0) activesupport (>= 3.0.0) From 2528a0721f536f747fec9d60d7a3643bf33e7cdc Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 17 Oct 2023 10:10:50 -0400 Subject: [PATCH 62/69] Update `rubocop-capybara` to version 2.19.0 (#26982) --- Gemfile.lock | 2 +- spec/features/admin/domain_blocks_spec.rb | 18 +++++----- spec/features/admin/software_updates_spec.rb | 4 +-- spec/features/captcha_spec.rb | 2 +- spec/features/log_in_spec.rb | 6 ++-- spec/features/oauth_spec.rb | 36 ++++++++++---------- spec/support/stories/profile_stories.rb | 2 +- spec/system/new_statuses_spec.rb | 8 ++--- 8 files changed, 39 insertions(+), 39 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4955e8283..ba44065d0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -634,7 +634,7 @@ GEM unicode-display_width (>= 2.4.0, < 3.0) rubocop-ast (1.29.0) parser (>= 3.2.1.0) - rubocop-capybara (2.18.0) + rubocop-capybara (2.19.0) rubocop (~> 1.41) rubocop-factory_bot (2.23.1) rubocop (~> 1.33) diff --git a/spec/features/admin/domain_blocks_spec.rb b/spec/features/admin/domain_blocks_spec.rb index 4672c1e1a..0d7b90c21 100644 --- a/spec/features/admin/domain_blocks_spec.rb +++ b/spec/features/admin/domain_blocks_spec.rb @@ -13,7 +13,7 @@ describe 'blocking domains through the moderation interface' do fill_in 'domain_block_domain', with: 'example.com' select I18n.t('admin.domain_blocks.new.severity.silence'), from: 'domain_block_severity' - click_on I18n.t('admin.domain_blocks.new.create') + click_button I18n.t('admin.domain_blocks.new.create') expect(DomainBlock.exists?(domain: 'example.com', severity: 'silence')).to be true end @@ -25,13 +25,13 @@ describe 'blocking domains through the moderation interface' do fill_in 'domain_block_domain', with: 'example.com' select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity' - click_on I18n.t('admin.domain_blocks.new.create') + click_button I18n.t('admin.domain_blocks.new.create') # It presents a confirmation screen expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com')) # Confirming creates a block - click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm') + click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm') expect(DomainBlock.exists?(domain: 'example.com', severity: 'suspend')).to be true end @@ -45,13 +45,13 @@ describe 'blocking domains through the moderation interface' do fill_in 'domain_block_domain', with: 'example.com' select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity' - click_on I18n.t('admin.domain_blocks.new.create') + click_button I18n.t('admin.domain_blocks.new.create') # It presents a confirmation screen expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com')) # Confirming updates the block - click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm') + click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm') expect(domain_block.reload.severity).to eq 'suspend' end @@ -65,13 +65,13 @@ describe 'blocking domains through the moderation interface' do fill_in 'domain_block_domain', with: 'subdomain.example.com' select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity' - click_on I18n.t('admin.domain_blocks.new.create') + click_button I18n.t('admin.domain_blocks.new.create') # It presents a confirmation screen expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'subdomain.example.com')) # Confirming creates the block - click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm') + click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm') expect(DomainBlock.where(domain: 'subdomain.example.com', severity: 'suspend')).to exist @@ -88,13 +88,13 @@ describe 'blocking domains through the moderation interface' do visit edit_admin_domain_block_path(domain_block) select I18n.t('admin.domain_blocks.new.severity.suspend'), from: 'domain_block_severity' - click_on I18n.t('generic.save_changes') + click_button I18n.t('generic.save_changes') # It presents a confirmation screen expect(page).to have_title(I18n.t('admin.domain_blocks.confirm_suspension.title', domain: 'example.com')) # Confirming updates the block - click_on I18n.t('admin.domain_blocks.confirm_suspension.confirm') + click_button I18n.t('admin.domain_blocks.confirm_suspension.confirm') expect(domain_block.reload.severity).to eq 'suspend' end diff --git a/spec/features/admin/software_updates_spec.rb b/spec/features/admin/software_updates_spec.rb index 4a635d1a7..a2373d35a 100644 --- a/spec/features/admin/software_updates_spec.rb +++ b/spec/features/admin/software_updates_spec.rb @@ -11,13 +11,13 @@ describe 'finding software updates through the admin interface' do it 'shows a link to the software updates page, which links to release notes' do visit settings_profile_path - click_on I18n.t('admin.critical_update_pending') + click_link I18n.t('admin.critical_update_pending') expect(page).to have_title(I18n.t('admin.software_updates.title')) expect(page).to have_content('99.99.99') - click_on I18n.t('admin.software_updates.release_notes') + click_link I18n.t('admin.software_updates.release_notes') expect(page).to have_current_path('https://github.com/mastodon/mastodon/releases/v99', url: true) end end diff --git a/spec/features/captcha_spec.rb b/spec/features/captcha_spec.rb index db89ff3e6..6ccf06620 100644 --- a/spec/features/captcha_spec.rb +++ b/spec/features/captcha_spec.rb @@ -27,7 +27,7 @@ describe 'email confirmation flow when captcha is enabled' do expect(user.reload.confirmed?).to be false # It redirects to app and confirms user - click_on I18n.t('challenge.confirm') + click_button I18n.t('challenge.confirm') expect(user.reload.confirmed?).to be true expect(page).to have_current_path(/\A#{client_app.confirmation_redirect_uri}/, url: true) end diff --git a/spec/features/log_in_spec.rb b/spec/features/log_in_spec.rb index c64e19d2b..7e5196aba 100644 --- a/spec/features/log_in_spec.rb +++ b/spec/features/log_in_spec.rb @@ -19,7 +19,7 @@ describe 'Log in' do it 'A valid email and password user is able to log in' do fill_in 'user_email', with: email fill_in 'user_password', with: password - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(subject).to have_css('div.app-holder') end @@ -27,7 +27,7 @@ describe 'Log in' do it 'A invalid email and password user is not able to log in' do fill_in 'user_email', with: 'invalid_email' fill_in 'user_password', with: 'invalid_password' - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(subject).to have_css('.flash-message', text: failure_message('invalid')) end @@ -38,7 +38,7 @@ describe 'Log in' do it 'A unconfirmed user is able to log in' do fill_in 'user_email', with: email fill_in 'user_password', with: password - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(subject).to have_css('div.admin-wrapper') end diff --git a/spec/features/oauth_spec.rb b/spec/features/oauth_spec.rb index 967956cc8..0e612b56a 100644 --- a/spec/features/oauth_spec.rb +++ b/spec/features/oauth_spec.rb @@ -20,7 +20,7 @@ describe 'Using OAuth from an external app' do expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize')) # Upon authorizing, it redirects to the apps' callback URL - click_on I18n.t('doorkeeper.authorizations.buttons.authorize') + click_button I18n.t('doorkeeper.authorizations.buttons.authorize') expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) # It grants the app access to the account @@ -35,7 +35,7 @@ describe 'Using OAuth from an external app' do expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.deny')) # Upon denying, it redirects to the apps' callback URL - click_on I18n.t('doorkeeper.authorizations.buttons.deny') + click_button I18n.t('doorkeeper.authorizations.buttons.deny') expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) # It does not grant the app access to the account @@ -63,17 +63,17 @@ describe 'Using OAuth from an external app' do # Failing to log-in presents the form again fill_in 'user_email', with: email fill_in 'user_password', with: 'wrong password' - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('auth.login')) # Logging in redirects to an authorization page fill_in 'user_email', with: email fill_in 'user_password', with: password - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize')) # Upon authorizing, it redirects to the apps' callback URL - click_on I18n.t('doorkeeper.authorizations.buttons.authorize') + click_button I18n.t('doorkeeper.authorizations.buttons.authorize') expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) # It grants the app access to the account @@ -90,17 +90,17 @@ describe 'Using OAuth from an external app' do # Failing to log-in presents the form again fill_in 'user_email', with: email fill_in 'user_password', with: 'wrong password' - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('auth.login')) # Logging in redirects to an authorization page fill_in 'user_email', with: email fill_in 'user_password', with: password - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize')) # Upon denying, it redirects to the apps' callback URL - click_on I18n.t('doorkeeper.authorizations.buttons.deny') + click_button I18n.t('doorkeeper.authorizations.buttons.deny') expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) # It does not grant the app access to the account @@ -120,27 +120,27 @@ describe 'Using OAuth from an external app' do # Failing to log-in presents the form again fill_in 'user_email', with: email fill_in 'user_password', with: 'wrong password' - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('auth.login')) # Logging in redirects to a two-factor authentication page fill_in 'user_email', with: email fill_in 'user_password', with: password - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp')) # Filling in an incorrect two-factor authentication code presents the form again fill_in 'user_otp_attempt', with: 'wrong' - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp')) # Filling in the correct TOTP code redirects to an app authorization page fill_in 'user_otp_attempt', with: user.current_otp - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize')) # Upon authorizing, it redirects to the apps' callback URL - click_on I18n.t('doorkeeper.authorizations.buttons.authorize') + click_button I18n.t('doorkeeper.authorizations.buttons.authorize') expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) # It grants the app access to the account @@ -157,27 +157,27 @@ describe 'Using OAuth from an external app' do # Failing to log-in presents the form again fill_in 'user_email', with: email fill_in 'user_password', with: 'wrong password' - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('auth.login')) # Logging in redirects to a two-factor authentication page fill_in 'user_email', with: email fill_in 'user_password', with: password - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp')) # Filling in an incorrect two-factor authentication code presents the form again fill_in 'user_otp_attempt', with: 'wrong' - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('simple_form.hints.sessions.otp')) # Filling in the correct TOTP code redirects to an app authorization page fill_in 'user_otp_attempt', with: user.current_otp - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') expect(page).to have_content(I18n.t('doorkeeper.authorizations.buttons.authorize')) # Upon denying, it redirects to the apps' callback URL - click_on I18n.t('doorkeeper.authorizations.buttons.deny') + click_button I18n.t('doorkeeper.authorizations.buttons.deny') expect(page).to have_current_path(/\A#{client_app.redirect_uri}/, url: true) # It does not grant the app access to the account diff --git a/spec/support/stories/profile_stories.rb b/spec/support/stories/profile_stories.rb index 2b345ddef..82667ca08 100644 --- a/spec/support/stories/profile_stories.rb +++ b/spec/support/stories/profile_stories.rb @@ -18,7 +18,7 @@ module ProfileStories visit new_user_session_path fill_in 'user_email', with: email fill_in 'user_password', with: password - click_on I18n.t('auth.login') + click_button I18n.t('auth.login') end def with_alice_as_local_user diff --git a/spec/system/new_statuses_spec.rb b/spec/system/new_statuses_spec.rb index 6faed6c80..244101f4d 100644 --- a/spec/system/new_statuses_spec.rb +++ b/spec/system/new_statuses_spec.rb @@ -24,10 +24,10 @@ describe 'NewStatuses' do within('.compose-form') do fill_in "What's on your mind?", with: status_text - click_on 'Publish!' + click_button 'Publish!' end - expect(subject).to have_selector('.status__content__text', text: status_text) + expect(subject).to have_css('.status__content__text', text: status_text) end it 'can be posted again' do @@ -37,9 +37,9 @@ describe 'NewStatuses' do within('.compose-form') do fill_in "What's on your mind?", with: status_text - click_on 'Publish!' + click_button 'Publish!' end - expect(subject).to have_selector('.status__content__text', text: status_text) + expect(subject).to have_css('.status__content__text', text: status_text) end end From ad44d832b8eaf42526b7bb26c94b39bfa6795457 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Tue, 17 Oct 2023 23:12:03 +0900 Subject: [PATCH 63/69] Support "in:public" search filter (#26976) --- app/javascript/mastodon/features/compose/components/search.jsx | 2 +- app/lib/search_query_transformer.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/compose/components/search.jsx b/app/javascript/mastodon/features/compose/components/search.jsx index dfbfa98f5..2525df779 100644 --- a/app/javascript/mastodon/features/compose/components/search.jsx +++ b/app/javascript/mastodon/features/compose/components/search.jsx @@ -64,7 +64,7 @@ class Search extends PureComponent { { label: <>before: , action: e => { e.preventDefault(); this._insertText('before:'); } }, { label: <>during: , action: e => { e.preventDefault(); this._insertText('during:'); } }, { label: <>after: , action: e => { e.preventDefault(); this._insertText('after:'); } }, - { label: <>in: , action: e => { e.preventDefault(); this._insertText('in:'); } } + { label: <>in: , action: e => { e.preventDefault(); this._insertText('in:'); } } ]; setRef = c => { diff --git a/app/lib/search_query_transformer.rb b/app/lib/search_query_transformer.rb index a45ae3d09..927495eac 100644 --- a/app/lib/search_query_transformer.rb +++ b/app/lib/search_query_transformer.rb @@ -58,6 +58,8 @@ class SearchQueryTransformer < Parslet::Transform case @flags['in'] when 'library' [StatusesIndex] + when 'public' + [PublicStatusesIndex] else [PublicStatusesIndex, StatusesIndex] end From 0036de46bc2d00f7ed549e69b0b8f72165e4b2a7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:16:12 +0200 Subject: [PATCH 64/69] Update dependency rubocop-performance to v1.19.1 (#26949) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index ba44065d0..c3d1ca209 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -638,7 +638,7 @@ GEM rubocop (~> 1.41) rubocop-factory_bot (2.23.1) rubocop (~> 1.33) - rubocop-performance (1.19.0) + rubocop-performance (1.19.1) rubocop (>= 1.7.0, < 2.0) rubocop-ast (>= 0.4.0) rubocop-rails (2.20.2) From 71447ab19fce03c8a663a89c1b84816b209f192f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Tue, 17 Oct 2023 10:57:19 -0400 Subject: [PATCH 65/69] Fix `Capybara/ClickLinkOrButtonStyle` cop in spec/features/admin area (#27445) --- spec/features/admin/accounts_spec.rb | 8 ++++---- spec/features/admin/custom_emojis_spec.rb | 2 +- spec/features/admin/email_domain_blocks_spec.rb | 2 +- spec/features/admin/ip_blocks_spec.rb | 2 +- spec/features/admin/statuses_spec.rb | 2 +- .../admin/trends/links/preview_card_providers_spec.rb | 2 +- spec/features/admin/trends/links_spec.rb | 2 +- spec/features/admin/trends/statuses_spec.rb | 2 +- spec/features/admin/trends/tags_spec.rb | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/features/admin/accounts_spec.rb b/spec/features/admin/accounts_spec.rb index 6d7bab184..ad9c51485 100644 --- a/spec/features/admin/accounts_spec.rb +++ b/spec/features/admin/accounts_spec.rb @@ -22,7 +22,7 @@ describe 'Admin::Accounts' do context 'without selecting any accounts' do it 'displays a notice about account selection' do - click_on button_for_suspend + click_button button_for_suspend expect(page).to have_content(selection_error_text) end @@ -32,7 +32,7 @@ describe 'Admin::Accounts' do it 'suspends the account' do batch_checkbox_for(approved_user_account).check - click_on button_for_suspend + click_button button_for_suspend expect(approved_user_account.reload).to be_suspended end @@ -42,7 +42,7 @@ describe 'Admin::Accounts' do it 'approves the account user' do batch_checkbox_for(unapproved_user_account).check - click_on button_for_approve + click_button button_for_approve expect(unapproved_user_account.reload.user).to be_approved end @@ -52,7 +52,7 @@ describe 'Admin::Accounts' do it 'rejects and removes the account' do batch_checkbox_for(unapproved_user_account).check - click_on button_for_reject + click_button button_for_reject expect { unapproved_user_account.reload }.to raise_error(ActiveRecord::RecordNotFound) end diff --git a/spec/features/admin/custom_emojis_spec.rb b/spec/features/admin/custom_emojis_spec.rb index 8a8b6efcd..3fea8f06f 100644 --- a/spec/features/admin/custom_emojis_spec.rb +++ b/spec/features/admin/custom_emojis_spec.rb @@ -16,7 +16,7 @@ describe 'Admin::CustomEmojis' do context 'without selecting any records' do it 'displays a notice about selection' do - click_on button_for_enable + click_button button_for_enable expect(page).to have_content(selection_error_text) end diff --git a/spec/features/admin/email_domain_blocks_spec.rb b/spec/features/admin/email_domain_blocks_spec.rb index 14959cbe7..80efe72e9 100644 --- a/spec/features/admin/email_domain_blocks_spec.rb +++ b/spec/features/admin/email_domain_blocks_spec.rb @@ -16,7 +16,7 @@ describe 'Admin::EmailDomainBlocks' do context 'without selecting any records' do it 'displays a notice about selection' do - click_on button_for_delete + click_button button_for_delete expect(page).to have_content(selection_error_text) end diff --git a/spec/features/admin/ip_blocks_spec.rb b/spec/features/admin/ip_blocks_spec.rb index c9b16f6f7..465c88919 100644 --- a/spec/features/admin/ip_blocks_spec.rb +++ b/spec/features/admin/ip_blocks_spec.rb @@ -16,7 +16,7 @@ describe 'Admin::IpBlocks' do context 'without selecting any records' do it 'displays a notice about selection' do - click_on button_for_delete + click_button button_for_delete expect(page).to have_content(selection_error_text) end diff --git a/spec/features/admin/statuses_spec.rb b/spec/features/admin/statuses_spec.rb index 531d0de95..a21c901a9 100644 --- a/spec/features/admin/statuses_spec.rb +++ b/spec/features/admin/statuses_spec.rb @@ -17,7 +17,7 @@ describe 'Admin::Statuses' do context 'without selecting any records' do it 'displays a notice about selection' do - click_on button_for_report + click_button button_for_report expect(page).to have_content(selection_error_text) end diff --git a/spec/features/admin/trends/links/preview_card_providers_spec.rb b/spec/features/admin/trends/links/preview_card_providers_spec.rb index dca89117b..cf9796abf 100644 --- a/spec/features/admin/trends/links/preview_card_providers_spec.rb +++ b/spec/features/admin/trends/links/preview_card_providers_spec.rb @@ -16,7 +16,7 @@ describe 'Admin::Trends::Links::PreviewCardProviders' do context 'without selecting any records' do it 'displays a notice about selection' do - click_on button_for_allow + click_button button_for_allow expect(page).to have_content(selection_error_text) end diff --git a/spec/features/admin/trends/links_spec.rb b/spec/features/admin/trends/links_spec.rb index 99638bc06..8b1b991a5 100644 --- a/spec/features/admin/trends/links_spec.rb +++ b/spec/features/admin/trends/links_spec.rb @@ -16,7 +16,7 @@ describe 'Admin::Trends::Links' do context 'without selecting any records' do it 'displays a notice about selection' do - click_on button_for_allow + click_button button_for_allow expect(page).to have_content(selection_error_text) end diff --git a/spec/features/admin/trends/statuses_spec.rb b/spec/features/admin/trends/statuses_spec.rb index 779a15d38..a578ab055 100644 --- a/spec/features/admin/trends/statuses_spec.rb +++ b/spec/features/admin/trends/statuses_spec.rb @@ -16,7 +16,7 @@ describe 'Admin::Trends::Statuses' do context 'without selecting any records' do it 'displays a notice about selection' do - click_on button_for_allow + click_button button_for_allow expect(page).to have_content(selection_error_text) end diff --git a/spec/features/admin/trends/tags_spec.rb b/spec/features/admin/trends/tags_spec.rb index 52e49c3a5..7502bc8c6 100644 --- a/spec/features/admin/trends/tags_spec.rb +++ b/spec/features/admin/trends/tags_spec.rb @@ -16,7 +16,7 @@ describe 'Admin::Trends::Tags' do context 'without selecting any records' do it 'displays a notice about selection' do - click_on button_for_allow + click_button button_for_allow expect(page).to have_content(selection_error_text) end From 5ef26d8fd50081c642b858a82bf0c5431b1c7e83 Mon Sep 17 00:00:00 2001 From: Nick Schonning Date: Tue, 17 Oct 2023 12:28:09 -0400 Subject: [PATCH 66/69] Add RSpec GitHub annotations gem (#24040) --- Gemfile | 3 +++ Gemfile.lock | 3 +++ spec/spec_helper.rb | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/Gemfile b/Gemfile index 17cbabd3b..98897536a 100644 --- a/Gemfile +++ b/Gemfile @@ -106,6 +106,9 @@ group :test do # Used to split testing into chunks in CI gem 'rspec_chunked', '~> 0.6' + # Adds RSpec Error/Warning annotations to GitHub PRs on the Files tab + gem 'rspec-github', '~> 2.4', require: false + # RSpec progress bar formatter gem 'fuubar', '~> 2.5' diff --git a/Gemfile.lock b/Gemfile.lock index c3d1ca209..29cdc8aa4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -602,6 +602,8 @@ GEM rspec-expectations (3.12.3) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) + rspec-github (2.4.0) + rspec-core (~> 3.0) rspec-mocks (3.12.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) @@ -885,6 +887,7 @@ DEPENDENCIES redis (~> 4.5) redis-namespace (~> 1.10) rqrcode (~> 2.2) + rspec-github (~> 2.4) rspec-rails (~> 6.0) rspec-sidekiq (~> 4.0) rspec_chunked (~> 0.6) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b4c20545f..4d3c234a0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -36,6 +36,12 @@ RSpec.configure do |config| config.after :suite do FileUtils.rm_rf(Dir[Rails.root.join('spec', 'test_files')]) end + + # Use the GitHub Annotations formatter for CI + if ENV['GITHUB_ACTIONS'] == 'true' + require 'rspec/github' + config.add_formatter RSpec::Github::Formatter + end end def body_as_json From 8bbbd012d4da638a2bf71808d672718d1e105671 Mon Sep 17 00:00:00 2001 From: Stanislas Signoud Date: Tue, 17 Oct 2023 14:59:07 +0200 Subject: [PATCH 67/69] [Glitch] Fix missing redirections to make sure /home redirect to the advanced UI Port 935d54124e80e9fe5365c724e5c8827a2b3ed5b3 to glitch-soc Signed-off-by: Claire --- app/javascript/flavours/glitch/features/ui/index.jsx | 2 ++ app/javascript/flavours/glitch/initial_state.js | 1 + 2 files changed, 3 insertions(+) diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index 6f8050957..44f394657 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -191,7 +191,9 @@ class SwitchingColumnsArea extends PureComponent { {singleColumn ? : null} {singleColumn && pathName.startsWith('/deck/') ? : null} + {/* Redirect old bookmarks (without /deck) with home-like routes to the advanced interface */} {!singleColumn && pathName === '/getting-started' ? : null} + {!singleColumn && pathName === '/home' ? : null} diff --git a/app/javascript/flavours/glitch/initial_state.js b/app/javascript/flavours/glitch/initial_state.js index 8d277f8c1..6b16384ab 100644 --- a/app/javascript/flavours/glitch/initial_state.js +++ b/app/javascript/flavours/glitch/initial_state.js @@ -94,6 +94,7 @@ const initialPath = document.querySelector("head meta[name=initialPath]")?.getAt /** @type {boolean} */ export const hasMultiColumnPath = initialPath === '/' || initialPath === '/getting-started' + || initialPath === '/home' || initialPath.startsWith('/deck'); /** From 0d18c47c18ade5180bb1238ed506c4b762730eae Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 17 Oct 2023 22:03:54 +0200 Subject: [PATCH 68/69] Enable scheduled actions only on glitch-soc's repo --- .github/workflows/build-nightly.yml | 2 +- .github/workflows/crowdin-download.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml index 8adfcda84..b7fb638b9 100644 --- a/.github/workflows/build-nightly.yml +++ b/.github/workflows/build-nightly.yml @@ -11,7 +11,7 @@ permissions: jobs: compute-suffix: runs-on: ubuntu-latest - if: github.repository == 'mastodon/mastodon' + if: github.repository == 'glitch-soc/mastodon' steps: - id: version_vars env: diff --git a/.github/workflows/crowdin-download.yml b/.github/workflows/crowdin-download.yml index dbd19a8d0..ef85a31ed 100644 --- a/.github/workflows/crowdin-download.yml +++ b/.github/workflows/crowdin-download.yml @@ -11,7 +11,7 @@ permissions: jobs: download-translations: runs-on: ubuntu-latest - if: github.repository == 'mastodon/mastodon' + if: github.repository == 'glitch-soc/mastodon' steps: - name: Checkout From 68401fc42676190c4d598820e15bc453af595582 Mon Sep 17 00:00:00 2001 From: Jeong Arm Date: Tue, 17 Oct 2023 23:12:03 +0900 Subject: [PATCH 69/69] [Glitch] Support "in:public" search filter Port ad44d832b8eaf42526b7bb26c94b39bfa6795457 to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/features/compose/components/search.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/flavours/glitch/features/compose/components/search.jsx b/app/javascript/flavours/glitch/features/compose/components/search.jsx index 0b659f99e..a06b83f6f 100644 --- a/app/javascript/flavours/glitch/features/compose/components/search.jsx +++ b/app/javascript/flavours/glitch/features/compose/components/search.jsx @@ -66,7 +66,7 @@ class Search extends PureComponent { { label: <>before: , action: e => { e.preventDefault(); this._insertText('before:'); } }, { label: <>during: , action: e => { e.preventDefault(); this._insertText('during:'); } }, { label: <>after: , action: e => { e.preventDefault(); this._insertText('after:'); } }, - { label: <>in: , action: e => { e.preventDefault(); this._insertText('in:'); } } + { label: <>in: , action: e => { e.preventDefault(); this._insertText('in:'); } } ]; setRef = c => {