From 7e22287caaba9b8cb9ae403cf988d98fb1feab5b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 Jun 2023 11:44:09 +0200 Subject: [PATCH 01/27] Update dependency sass-loader to v10.4.1 (#25315) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index 76414138af..12d5e47e13 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2275,11 +2275,16 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": +"@types/json-schema@^7.0.5": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== +"@types/json-schema@^7.0.6", "@types/json-schema@^7.0.8": + version "7.0.12" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" + integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== + "@types/json-stable-stringify@^1.0.32": version "1.0.34" resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.34.tgz#c0fb25e4d957e0ee2e497c1f553d7f8bb668fd75" @@ -7769,9 +7774,9 @@ kleur@^3.0.3: integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== klona@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" - integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== + version "2.0.6" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" + integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== known-css-properties@^0.27.0: version "0.27.0" @@ -10377,9 +10382,9 @@ safe-regex@^1.1.0: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sass-loader@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.2.0.tgz#3d64c1590f911013b3fa48a0b22a83d5e1494716" - integrity sha512-kUceLzC1gIHz0zNJPpqRsJyisWatGYNFRmv2CKZK2/ngMJgLqxTbXwe/hJ85luyvZkgqU3VlJ33UVF2T/0g6mw== + version "10.4.1" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-10.4.1.tgz#bea4e173ddf512c9d7f53e9ec686186146807cbf" + integrity sha512-aX/iJZTTpNUNx/OSYzo2KsjIUQHqvWsAhhUijFjAPdZTEhstjZI9zTNvkTTwsx+uNUJqUwOw5gacxQMx4hJxGQ== dependencies: klona "^2.0.4" loader-utils "^2.0.0" @@ -10428,7 +10433,7 @@ schema-utils@^2.6.5: ajv "^6.12.4" ajv-keywords "^3.5.2" -schema-utils@^3.0, schema-utils@^3.0.0: +schema-utils@^3.0: version "3.0.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.0.0.tgz#67502f6aa2b66a2d4032b4279a2944978a0913ef" integrity sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA== @@ -10437,6 +10442,15 @@ schema-utils@^3.0, schema-utils@^3.0.0: ajv "^6.12.5" ajv-keywords "^3.5.2" +schema-utils@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.2.tgz#36c10abca6f7577aeae136c804b0c741edeadc99" + integrity sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + scroll-behavior@^0.9.1: version "0.9.12" resolved "https://registry.yarnpkg.com/scroll-behavior/-/scroll-behavior-0.9.12.tgz#1c22d273ec4ce6cd4714a443fead50227da9424c" @@ -11787,9 +11801,9 @@ update-browserslist-db@^1.0.10: picocolors "^1.0.0" uri-js@^4.2.2: - version "4.4.0" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602" - integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g== + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== dependencies: punycode "^2.1.0" From 5265655549f705c535039341eb2b48bd837b640f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 Jun 2023 11:46:40 +0200 Subject: [PATCH 02/27] Update dependency dotenv to v16.1.3 (#25302) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 12d5e47e13..36d4008803 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4979,9 +4979,9 @@ domutils@^3.0.1: domhandler "^5.0.1" dotenv@^16.0.3: - version "16.0.3" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" - integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== + version "16.1.4" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.1.4.tgz#67ac1a10cd9c25f5ba604e4e08bc77c0ebe0ca8c" + integrity sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw== duplexer@^0.1.2: version "0.1.2" From db047e323b2746f56d87f31b90c4b0eec2dfd76a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 7 Jun 2023 12:05:05 +0200 Subject: [PATCH 03/27] Update dependency webpack-bundle-analyzer to v4.9.0 (#25327) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/yarn.lock b/yarn.lock index 36d4008803..22be8dd1cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1860,10 +1860,10 @@ picocolors "^1.0.0" tslib "^2.5.0" -"@polka/url@^1.0.0-next.9": - version "1.0.0-next.11" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71" - integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA== +"@polka/url@^1.0.0-next.20": + version "1.0.0-next.21" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" + integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== "@popperjs/core@^2.11.6": version "2.11.6" @@ -8217,7 +8217,7 @@ mime@1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^2.3.1, mime@^2.4.4: +mime@^2.4.4: version "2.4.7" resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.7.tgz#962aed9be0ed19c91fd7dc2ece5d7f4e89a90d74" integrity sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA== @@ -8367,6 +8367,11 @@ mousetrap@^1.5.2: resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9" integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA== +mrmime@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" + integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -10638,12 +10643,12 @@ signal-exit@^4.0.1: integrity sha512-uUWsN4aOxJAS8KOuf3QMyFtgm1pkb6I+KRZbRF/ghdf5T7sM+B1lLLzPDxswUjkmHyxQAVzEgG35E3NzDM9GVw== sirv@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.10.tgz#3e591f5a9ae2520f50d5830f5fae38d97e7be194" - integrity sha512-H5EZCoZaggEUQy8ocKsF7WAToGuZhjJlLvM3XOef46CbdIgbNeQ1p32N1PCuCjkVYwrAVOSMacN6CXXgIzuspg== + version "1.0.19" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" + integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ== dependencies: - "@polka/url" "^1.0.0-next.9" - mime "^2.3.1" + "@polka/url" "^1.0.0-next.20" + mrmime "^1.0.0" totalist "^1.0.0" sisteransi@^1.0.4: @@ -12017,9 +12022,9 @@ webpack-assets-manifest@^4.0.6: webpack-sources "^1.0" webpack-bundle-analyzer@^4.8.0: - version "4.8.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.8.0.tgz#951b8aaf491f665d2ae325d8b84da229157b1d04" - integrity sha512-ZzoSBePshOKhr+hd8u6oCkZVwpVaXgpw23ScGLFpR6SjYI7+7iIWYarjN6OEYOfRt8o7ZyZZQk0DuMizJ+LEIg== + version "4.9.0" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.0.tgz#fc093c4ab174fd3dcbd1c30b763f56d10141209d" + integrity sha512-+bXGmO1LyiNx0i9enBu3H8mv42sj/BJWhZNFwjz92tVnBa9J3JMGo2an2IXlEleoDOPn/Hofl5hr/xCpObUDtw== dependencies: "@discoveryjs/json-ext" "0.5.7" acorn "^8.0.4" @@ -12510,9 +12515,9 @@ ws@^6.2.1: async-limiter "~1.0.0" ws@^7.3.1: - version "7.4.6" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" - integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== ws@^8.11.0, ws@^8.12.1, ws@^8.13.0: version "8.13.0" From b85c387c5c0527b0ad31c27031a09d361826c5fc Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Wed, 7 Jun 2023 16:46:53 -0400 Subject: [PATCH 04/27] Remove reference to deleted statsd config file (#25336) --- .rubocop_todo.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 02356fdbe1..d1996ca74d 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -1236,11 +1236,6 @@ Style/GlobalStdStream: - 'config/environments/development.rb' - 'config/environments/production.rb' -# Configuration parameters: AllowedVariables. -Style/GlobalVars: - Exclude: - - 'config/initializers/statsd.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: @@ -1374,7 +1369,6 @@ Style/RedundantConstantBase: Exclude: - 'config/environments/production.rb' - 'config/initializers/sidekiq.rb' - - 'config/initializers/statsd.rb' - 'config/locales/sr-Latn.rb' - 'config/locales/sr.rb' From 18f092d9275c24f26a70c9cb74f5f5ec437de8e9 Mon Sep 17 00:00:00 2001 From: jsgoldstein Date: Thu, 8 Jun 2023 11:12:41 -0400 Subject: [PATCH 05/27] Fix translations for changing theme (#25340) --- app/views/settings/preferences/appearance/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index 5358310e5b..af61df71b8 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -10,7 +10,7 @@ = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| native_locale_name(locale) }, selected: I18n.locale, hint: false .fields-group.fields-row__column.fields-row__column-6 = f.simple_fields_for :settings, current_user.settings do |ff| - = ff.input :theme, collection: Themes.instance.names, label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, include_blank: false, hint: false + = ff.input :theme, collection: Themes.instance.names, label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_theme'), include_blank: false, hint: false - unless I18n.locale == :en .flash-message.translation-prompt From 4aff1d2974b47cfb0aedfc7be7c9b8fdd5f1b33a Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Fri, 9 Jun 2023 09:00:14 -0300 Subject: [PATCH 06/27] Migrate to request specs in `/api/v1/admin/email_domain_blocks` (#25337) --- .../api/v1/admin/email_domain_blocks_spec.rb} | 183 +++++++----------- 1 file changed, 65 insertions(+), 118 deletions(-) rename spec/{controllers/api/v1/admin/email_domain_blocks_controller_spec.rb => requests/api/v1/admin/email_domain_blocks_spec.rb} (52%) diff --git a/spec/controllers/api/v1/admin/email_domain_blocks_controller_spec.rb b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb similarity index 52% rename from spec/controllers/api/v1/admin/email_domain_blocks_controller_spec.rb rename to spec/requests/api/v1/admin/email_domain_blocks_spec.rb index 3643eb0f3d..a24f22be21 100644 --- a/spec/controllers/api/v1/admin/email_domain_blocks_controller_spec.rb +++ b/spec/requests/api/v1/admin/email_domain_blocks_spec.rb @@ -2,23 +2,20 @@ require 'rails_helper' -describe Api::V1::Admin::EmailDomainBlocksController do - render_views - +RSpec.describe 'Email Domain Blocks' do let(:role) { UserRole.find_by(name: 'Admin') } let(:user) { Fabricate(:user, role: role) } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:account) { Fabricate(:account) } let(:scopes) { 'admin:read:email_domain_blocks admin:write:email_domain_blocks' } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } shared_examples 'forbidden for wrong scope' do |wrong_scope| let(:scopes) { wrong_scope } it 'returns http forbidden' do + subject + expect(response).to have_http_status(403) end end @@ -27,65 +24,54 @@ describe Api::V1::Admin::EmailDomainBlocksController do let(:role) { UserRole.find_by(name: wrong_role) } it 'returns http forbidden' do + subject + expect(response).to have_http_status(403) end end - describe 'GET #index' do - context 'with wrong scope' do - before do - get :index - end - - it_behaves_like 'forbidden for wrong scope', 'read:statuses' + describe 'GET /api/v1/admin/email_domain_blocks' do + subject do + get '/api/v1/admin/email_domain_blocks', headers: headers, params: params end - context 'with wrong role' do - before do - get :index - end + let(:params) { {} } - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' it 'returns http success' do - get :index + subject expect(response).to have_http_status(200) end context 'when there is no email domain block' do it 'returns an empty list' do - get :index + subject - json = body_as_json - - expect(json).to be_empty + expect(body_as_json).to be_empty end end context 'when there are email domain blocks' do - let!(:email_domain_blocks) { Fabricate.times(5, :email_domain_block) } + let!(:email_domain_blocks) { Fabricate.times(5, :email_domain_block) } let(:blocked_email_domains) { email_domain_blocks.pluck(:domain) } it 'return the correct blocked email domains' do - get :index + subject - json = body_as_json - - expect(json.pluck(:domain)).to match_array(blocked_email_domains) + expect(body_as_json.pluck(:domain)).to match_array(blocked_email_domains) end context 'with limit param' do let(:params) { { limit: 2 } } it 'returns only the requested number of email domain blocks' do - get :index, params: params + subject - json = body_as_json - - expect(json.size).to eq(params[:limit]) + expect(body_as_json.size).to eq(params[:limit]) end end @@ -93,12 +79,11 @@ describe Api::V1::Admin::EmailDomainBlocksController do let(:params) { { since_id: email_domain_blocks[1].id } } it 'returns only the email domain blocks after since_id' do - get :index, params: params + subject email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s) - json = body_as_json - expect(json.pluck(:id)).to match_array(email_domain_blocks_ids[2..]) + expect(body_as_json.pluck(:id)).to match_array(email_domain_blocks_ids[2..]) end end @@ -106,102 +91,78 @@ describe Api::V1::Admin::EmailDomainBlocksController do let(:params) { { max_id: email_domain_blocks[3].id } } it 'returns only the email domain blocks before max_id' do - get :index, params: params + subject email_domain_blocks_ids = email_domain_blocks.pluck(:id).map(&:to_s) - json = body_as_json - expect(json.pluck(:id)).to match_array(email_domain_blocks_ids[..2]) + expect(body_as_json.pluck(:id)).to match_array(email_domain_blocks_ids[..2]) end end end end - describe 'GET #show' do + describe 'GET /api/v1/admin/email_domain_blocks/:id' do + subject do + get "/api/v1/admin/email_domain_blocks/#{email_domain_block.id}", headers: headers + end + let!(:email_domain_block) { Fabricate(:email_domain_block) } - let(:params) { { id: email_domain_block.id } } - context 'with wrong scope' do - before do - get :show, params: params - end - - it_behaves_like 'forbidden for wrong scope', 'read:statuses' - end - - context 'with wrong role' do - before do - get :show, params: params - end - - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' context 'when email domain block exists' do it 'returns http success' do - get :show, params: params + subject expect(response).to have_http_status(200) end it 'returns the correct blocked domain' do - get :show, params: params + subject - json = body_as_json - - expect(json[:domain]).to eq(email_domain_block.domain) + expect(body_as_json[:domain]).to eq(email_domain_block.domain) end end context 'when email domain block does not exist' do it 'returns http not found' do - get :show, params: { id: 0 } + get '/api/v1/admin/email_domain_blocks/-1', headers: headers expect(response).to have_http_status(404) end end end - describe 'POST #create' do + describe 'POST /api/v1/admin/email_domain_blocks' do + subject do + post '/api/v1/admin/email_domain_blocks', headers: headers, params: params + end + let(:params) { { domain: 'example.com' } } - context 'with wrong scope' do - before do - post :create, params: params - end - - it_behaves_like 'forbidden for wrong scope', 'read:statuses' - end - - context 'with wrong role' do - before do - post :create, params: params - end - - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' it 'returns http success' do - post :create, params: params + subject expect(response).to have_http_status(200) end it 'returns the correct blocked email domain' do - post :create, params: params + subject - json = body_as_json - - expect(json[:domain]).to eq(params[:domain]) + expect(body_as_json[:domain]).to eq(params[:domain]) end context 'when domain param is not provided' do let(:params) { { domain: '' } } it 'returns http unprocessable entity' do - post :create, params: params + subject expect(response).to have_http_status(422) end @@ -211,7 +172,7 @@ describe Api::V1::Admin::EmailDomainBlocksController do let(:params) { { domain: 'do\uD800.com' } } it 'returns http unprocessable entity' do - post :create, params: params + subject expect(response).to have_http_status(422) end @@ -223,59 +184,45 @@ describe Api::V1::Admin::EmailDomainBlocksController do end it 'returns http unprocessable entity' do - post :create, params: params + subject expect(response).to have_http_status(422) end end end - describe 'DELETE #destroy' do + describe 'DELETE /api/v1/admin/email_domain_blocks' do + subject do + delete "/api/v1/admin/email_domain_blocks/#{email_domain_block.id}", headers: headers + end + let!(:email_domain_block) { Fabricate(:email_domain_block) } - let(:params) { { id: email_domain_block.id } } - context 'with wrong scope' do - before do - delete :destroy, params: params - end - - it_behaves_like 'forbidden for wrong scope', 'read:statuses' - end - - context 'with wrong role' do - before do - delete :destroy, params: params - end - - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' it 'returns http success' do - delete :destroy, params: params + subject expect(response).to have_http_status(200) end it 'returns an empty body' do - delete :destroy, params: params + subject - json = body_as_json - - expect(json).to be_empty + expect(body_as_json).to be_empty end it 'deletes email domain block' do - delete :destroy, params: params + subject - email_domain_block = EmailDomainBlock.find_by(id: params[:id]) - - expect(email_domain_block).to be_nil + expect(EmailDomainBlock.find_by(id: email_domain_block.id)).to be_nil end context 'when email domain block does not exist' do it 'returns http not found' do - delete :destroy, params: { id: 0 } + delete '/api/v1/admin/email_domain_blocks/-1', headers: headers expect(response).to have_http_status(404) end From 75e299f44013d8ad9f1e90992e6b07ddb63d37a5 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Fri, 9 Jun 2023 08:03:35 -0400 Subject: [PATCH 07/27] Remove unused `redis_info` method Admin::Dashboard (#25345) --- app/controllers/admin/dashboard_controller.rb | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index 099512248f..3a6df662ea 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -14,15 +14,5 @@ module Admin @pending_tags_count = Tag.pending_review.count @pending_appeals_count = Appeal.pending.count end - - private - - def redis_info - @redis_info ||= if redis.is_a?(Redis::Namespace) - redis.redis.info - else - redis.info - end - end end end From e34142782f30be3b82768609c77ecf9464ae54e0 Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Fri, 9 Jun 2023 16:34:36 +0200 Subject: [PATCH 08/27] Add Ruby & Bundler versions to Gemfile.lock (#25317) --- Gemfile.lock | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Gemfile.lock b/Gemfile.lock index a9919bd3a2..c4fa1822ea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -882,3 +882,9 @@ DEPENDENCIES webpacker (~> 5.4) webpush! xorcist (~> 1.1) + +RUBY VERSION + ruby 3.2.2p53 + +BUNDLED WITH + 2.4.13 From 16dd3f08c1e5396d5f9ff3f13417901bc4e4b8b9 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Fri, 9 Jun 2023 19:29:16 +0200 Subject: [PATCH 09/27] Fix performance of streaming by parsing message JSON once (#25278) --- streaming/index.js | 60 +++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/streaming/index.js b/streaming/index.js index 279ebbad83..4b2607ed92 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -52,18 +52,31 @@ const redisUrlToClient = async (defaultConfig, redisUrl) => { }; /** + * Attempts to safely parse a string as JSON, used when both receiving a message + * from redis and when receiving a message from a client over a websocket + * connection, this is why it accepts a `req` argument. * @param {string} json - * @param {any} req + * @param {any?} req * @returns {Object.|null} */ const parseJSON = (json, req) => { try { return JSON.parse(json); } catch (err) { - if (req.accountId) { - log.warn(req.requestId, `Error parsing message from user ${req.accountId}: ${err}`); + /* FIXME: This logging isn't great, and should probably be done at the + * call-site of parseJSON, not in the method, but this would require changing + * the signature of parseJSON to return something akin to a Result type: + * [Error|null, null|Object { const { redisParams, redisUrl, redisPrefix } = redisConfigFromEnv(process.env); /** - * @type {Object.>} + * @type {Object.): void>>} */ const subs = {}; @@ -203,7 +216,10 @@ const startServer = async () => { return; } - callbacks.forEach(callback => callback(message)); + const json = parseJSON(message, null); + if (!json) return; + + callbacks.forEach(callback => callback(json)); }; /** @@ -225,7 +241,7 @@ const startServer = async () => { /** * @param {string} channel - * @param {function(string): void} callback + * @param {function(Object): void} callback */ const unsubscribe = (channel, callback) => { log.silly(`Removing listener for ${channel}`); @@ -369,7 +385,7 @@ const startServer = async () => { /** * @param {any} req - * @returns {string} + * @returns {string|undefined} */ const channelNameFromPath = req => { const { path, query } = req; @@ -478,15 +494,11 @@ const startServer = async () => { /** * @param {any} req * @param {SystemMessageHandlers} eventHandlers - * @returns {function(string): void} + * @returns {function(object): void} */ const createSystemMessageListener = (req, eventHandlers) => { return message => { - const json = parseJSON(message, req); - - if (!json) return; - - const { event } = json; + const { event } = message; log.silly(req.requestId, `System message for ${req.accountId}: ${event}`); @@ -603,19 +615,16 @@ const startServer = async () => { * @param {function(string, string): void} output * @param {function(string[], function(string): void): void} attachCloseHandler * @param {boolean=} needsFiltering - * @returns {function(string): void} + * @returns {function(object): void} */ const streamFrom = (ids, req, output, attachCloseHandler, needsFiltering = false) => { const accountId = req.accountId || req.remoteAddress; log.verbose(req.requestId, `Starting stream from ${ids.join(', ')} for ${accountId}`); + // Currently message is of type string, soon it'll be Record const listener = message => { - const json = parseJSON(message, req); - - if (!json) return; - - const { event, payload, queued_at } = json; + const { event, payload, queued_at } = message; const transmit = () => { const now = new Date().getTime(); @@ -1198,8 +1207,15 @@ const startServer = async () => { ws.on('close', onEnd); ws.on('error', onEnd); - ws.on('message', data => { - const json = parseJSON(data, session.request); + ws.on('message', (data, isBinary) => { + if (isBinary) { + log.debug('Received binary data, closing connection'); + ws.close(1003, 'The mastodon streaming server does not support binary messages'); + return; + } + const message = data.toString('utf8'); + + const json = parseJSON(message, session.request); if (!json) return; From 4c9406bdb0938367615a12083199705304d29e27 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sat, 10 Jun 2023 03:29:37 +0200 Subject: [PATCH 10/27] Add time zone preference (#25342) --- app/controllers/api/v1/accounts_controller.rb | 2 +- app/controllers/settings/preferences/base_controller.rb | 2 +- app/models/account.rb | 1 + app/models/user.rb | 2 ++ app/services/app_sign_up_service.rb | 2 +- app/views/notification_mailer/_status.html.haml | 2 +- app/views/notification_mailer/favourite.html.haml | 2 +- app/views/notification_mailer/mention.html.haml | 2 +- app/views/notification_mailer/reblog.html.haml | 2 +- app/views/settings/preferences/appearance/show.html.haml | 7 +++++-- app/views/user_mailer/appeal_approved.html.haml | 2 +- app/views/user_mailer/appeal_approved.text.erb | 2 +- app/views/user_mailer/appeal_rejected.html.haml | 2 +- app/views/user_mailer/appeal_rejected.text.erb | 2 +- app/views/user_mailer/suspicious_sign_in.html.haml | 2 +- app/views/user_mailer/suspicious_sign_in.text.erb | 2 +- app/views/user_mailer/warning.html.haml | 2 +- config/locales/simple_form.en.yml | 1 + db/migrate/20230605085711_add_time_zone_to_users.rb | 7 +++++++ db/schema.rb | 3 ++- 20 files changed, 32 insertions(+), 17 deletions(-) create mode 100644 db/migrate/20230605085711_add_time_zone_to_users.rb diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 8af4242ba3..ddb94d5ca4 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -90,7 +90,7 @@ class Api::V1::AccountsController < Api::BaseController end def account_params - params.permit(:username, :email, :password, :agreement, :locale, :reason) + params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone) end def check_enabled_registrations diff --git a/app/controllers/settings/preferences/base_controller.rb b/app/controllers/settings/preferences/base_controller.rb index faf778a7e5..c1f8b49898 100644 --- a/app/controllers/settings/preferences/base_controller.rb +++ b/app/controllers/settings/preferences/base_controller.rb @@ -19,6 +19,6 @@ class Settings::Preferences::BaseController < Settings::BaseController end def user_params - params.require(:user).permit(:locale, chosen_languages: [], settings_attributes: UserSettings.keys) + params.require(:user).permit(:locale, :time_zone, chosen_languages: [], settings_attributes: UserSettings.keys) end end diff --git a/app/models/account.rb b/app/models/account.rb index 1d24a7ec81..8a606fd2a2 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -140,6 +140,7 @@ class Account < ApplicationRecord :locale, :shows_application?, :prefers_noindex?, + :time_zone, to: :user, prefix: true, allow_nil: true diff --git a/app/models/user.rb b/app/models/user.rb index b903344be9..5ee14bbdaf 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -40,6 +40,7 @@ # sign_up_ip :inet # role_id :bigint(8) # settings :text +# time_zone :string # class User < ApplicationRecord @@ -99,6 +100,7 @@ class User < ApplicationRecord validates_with BlacklistedEmailValidator, if: -> { ENV['EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION'] == 'true' || !confirmed? } validates_with EmailMxValidator, if: :validate_email_dns? validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create + validates :time_zone, inclusion: { in: ActiveSupport::TimeZone.all.map { |tz| tz.tzinfo.name } }, allow_blank: true # Honeypot/anti-spam fields attr_accessor :registration_form_time, :website, :confirm_password diff --git a/app/services/app_sign_up_service.rb b/app/services/app_sign_up_service.rb index 3833327bbc..94547b61b2 100644 --- a/app/services/app_sign_up_service.rb +++ b/app/services/app_sign_up_service.rb @@ -35,7 +35,7 @@ class AppSignUpService < BaseService end def user_params - @params.slice(:email, :password, :agreement, :locale) + @params.slice(:email, :password, :agreement, :locale, :time_zone) end def account_params diff --git a/app/views/notification_mailer/_status.html.haml b/app/views/notification_mailer/_status.html.haml index fd65039ae9..d8aa38b112 100644 --- a/app/views/notification_mailer/_status.html.haml +++ b/app/views/notification_mailer/_status.html.haml @@ -42,4 +42,4 @@ = link_to a.remote_url, a.remote_url %p.status-footer - = link_to l(status.created_at), web_url("@#{status.account.pretty_acct}/#{status.id}") + = link_to l(status.created_at.in_time_zone(time_zone)), web_url("@#{status.account.pretty_acct}/#{status.id}") diff --git a/app/views/notification_mailer/favourite.html.haml b/app/views/notification_mailer/favourite.html.haml index 4ec89172d9..325f0aff5f 100644 --- a/app/views/notification_mailer/favourite.html.haml +++ b/app/views/notification_mailer/favourite.html.haml @@ -22,7 +22,7 @@ %h1= t 'notification_mailer.favourite.title' %p.lead= t('notification_mailer.favourite.body', name: @account.pretty_acct) -= render 'status', status: @status += render 'status', status: @status, time_zone: @me.user_time_zone %table.email-table{ cellspacing: 0, cellpadding: 0 } %tbody diff --git a/app/views/notification_mailer/mention.html.haml b/app/views/notification_mailer/mention.html.haml index 4ae9bb7b0b..e830644c3f 100644 --- a/app/views/notification_mailer/mention.html.haml +++ b/app/views/notification_mailer/mention.html.haml @@ -22,7 +22,7 @@ %h1= t 'notification_mailer.mention.title' %p.lead= t('notification_mailer.mention.body', name: @status.account.pretty_acct) -= render 'status', status: @status += render 'status', status: @status, time_zone: @me.user_time_zone %table.email-table{ cellspacing: 0, cellpadding: 0 } %tbody diff --git a/app/views/notification_mailer/reblog.html.haml b/app/views/notification_mailer/reblog.html.haml index f805c79f0d..e4f9441236 100644 --- a/app/views/notification_mailer/reblog.html.haml +++ b/app/views/notification_mailer/reblog.html.haml @@ -22,7 +22,7 @@ %h1= t 'notification_mailer.reblog.title' %p.lead= t('notification_mailer.reblog.body', name: @account.pretty_acct) -= render 'status', status: @status += render 'status', status: @status, time_zone: @me.user_time_zone %table.email-table{ cellspacing: 0, cellpadding: 0 } %tbody diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index af61df71b8..ce3a30c5ee 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -9,8 +9,11 @@ .fields-group.fields-row__column.fields-row__column-6 = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| native_locale_name(locale) }, selected: I18n.locale, hint: false .fields-group.fields-row__column.fields-row__column-6 - = f.simple_fields_for :settings, current_user.settings do |ff| - = ff.input :theme, collection: Themes.instance.names, label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_theme'), include_blank: false, hint: false + = f.input :time_zone, wrapper: :with_label, collection: ActiveSupport::TimeZone.all.map { |tz| ["(GMT#{tz.formatted_offset}) #{tz.name}", tz.tzinfo.name] }, hint: false + + .fields-group + = f.simple_fields_for :settings, current_user.settings do |ff| + = ff.input :theme, collection: Themes.instance.names, label_method: lambda { |theme| I18n.t("themes.#{theme}", default: theme) }, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_theme'), include_blank: false, hint: false - unless I18n.locale == :en .flash-message.translation-prompt diff --git a/app/views/user_mailer/appeal_approved.html.haml b/app/views/user_mailer/appeal_approved.html.haml index 962cab2e2c..d62789a067 100644 --- a/app/views/user_mailer/appeal_approved.html.haml +++ b/app/views/user_mailer/appeal_approved.html.haml @@ -36,7 +36,7 @@ %tbody %tr %td.column-cell.text-center - %p= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) + %p= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone)), strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone)) %table.email-table{ cellspacing: 0, cellpadding: 0 } %tbody diff --git a/app/views/user_mailer/appeal_approved.text.erb b/app/views/user_mailer/appeal_approved.text.erb index 290fa24c36..99596605aa 100644 --- a/app/views/user_mailer/appeal_approved.text.erb +++ b/app/views/user_mailer/appeal_approved.text.erb @@ -2,6 +2,6 @@ === -<%= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) %> +<%= t 'user_mailer.appeal_approved.explanation', appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone)), strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone)) %> => <%= root_url %> diff --git a/app/views/user_mailer/appeal_rejected.html.haml b/app/views/user_mailer/appeal_rejected.html.haml index c316a73fb5..ae60775b01 100644 --- a/app/views/user_mailer/appeal_rejected.html.haml +++ b/app/views/user_mailer/appeal_rejected.html.haml @@ -36,7 +36,7 @@ %tbody %tr %td.column-cell.text-center - %p= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) + %p= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone)), strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone)) %table.email-table{ cellspacing: 0, cellpadding: 0 } %tbody diff --git a/app/views/user_mailer/appeal_rejected.text.erb b/app/views/user_mailer/appeal_rejected.text.erb index f47a768181..3c93777180 100644 --- a/app/views/user_mailer/appeal_rejected.text.erb +++ b/app/views/user_mailer/appeal_rejected.text.erb @@ -2,6 +2,6 @@ === -<%= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at), strike_date: l(@appeal.strike.created_at) %> +<%= t 'user_mailer.appeal_rejected.explanation', appeal_date: l(@appeal.created_at.in_time_zone(@resource.time_zone)), strike_date: l(@appeal.strike.created_at.in_time_zone(@resource.time_zone)) %> => <%= root_url %> diff --git a/app/views/user_mailer/suspicious_sign_in.html.haml b/app/views/user_mailer/suspicious_sign_in.html.haml index e4ad500c3d..6ebba3fa55 100644 --- a/app/views/user_mailer/suspicious_sign_in.html.haml +++ b/app/views/user_mailer/suspicious_sign_in.html.haml @@ -47,7 +47,7 @@ %strong= "#{t('sessions.browser')}:" %span{ title: @user_agent }= t 'sessions.description', browser: t("sessions.browsers.#{@detection.id}", default: @detection.id.to_s), platform: t("sessions.platforms.#{@detection.platform.id}", default: @detection.platform.id.to_s) %br/ - = l(@timestamp) + = l(@timestamp.in_time_zone(@resource.time_zone)) %table.email-table{ cellspacing: 0, cellpadding: 0 } %tbody diff --git a/app/views/user_mailer/suspicious_sign_in.text.erb b/app/views/user_mailer/suspicious_sign_in.text.erb index 7d2ca28e84..956071e774 100644 --- a/app/views/user_mailer/suspicious_sign_in.text.erb +++ b/app/views/user_mailer/suspicious_sign_in.text.erb @@ -8,7 +8,7 @@ <%= t('sessions.ip') %>: <%= @remote_ip %> <%= t('sessions.browser') %>: <%= t('sessions.description', browser: t("sessions.browsers.#{@detection.id}", default: "#{@detection.id}"), platform: t("sessions.platforms.#{@detection.platform.id}", default: "#{@detection.platform.id}")) %> -<%= l(@timestamp) %> +<%= l(@timestamp.in_time_zone(@resource.time_zone)) %> <%= t 'user_mailer.suspicious_sign_in.further_actions_html', action: t('user_mailer.suspicious_sign_in.change_password') %> diff --git a/app/views/user_mailer/warning.html.haml b/app/views/user_mailer/warning.html.haml index b9422e9502..9cb73b0fee 100644 --- a/app/views/user_mailer/warning.html.haml +++ b/app/views/user_mailer/warning.html.haml @@ -58,7 +58,7 @@ - unless @statuses.empty? - @statuses.each_with_index do |status, i| - = render 'notification_mailer/status', status: status, i: i + 1, highlighted: true + = render 'notification_mailer/status', status: status, i: i + 1, highlighted: true, time_zone: @resource.time_zone %table.email-table{ cellspacing: 0, cellpadding: 0 } %tbody diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index f92e66e55a..330c8732ff 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -297,6 +297,7 @@ en: usable: Allow posts to use this hashtag user: role: Role + time_zone: Time zone user_role: color: Badge color highlighted: Display role as badge on user profiles diff --git a/db/migrate/20230605085711_add_time_zone_to_users.rb b/db/migrate/20230605085711_add_time_zone_to_users.rb new file mode 100644 index 0000000000..fc6c0b091c --- /dev/null +++ b/db/migrate/20230605085711_add_time_zone_to_users.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddTimeZoneToUsers < ActiveRecord::Migration[6.1] + def change + add_column :users, :time_zone, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 28d8d83902..9866b10149 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_06_05_085710) do +ActiveRecord::Schema.define(version: 2023_06_05_085711) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1088,6 +1088,7 @@ ActiveRecord::Schema.define(version: 2023_06_05_085710) do t.boolean "skip_sign_in_token" t.bigint "role_id" t.text "settings" + t.string "time_zone" t.index ["account_id"], name: "index_users_on_account_id" t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true t.index ["created_by_application_id"], name: "index_users_on_created_by_application_id", where: "(created_by_application_id IS NOT NULL)" From c81f59583cf35dabbe58621042b334b9d8cf40b0 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Sat, 10 Jun 2023 18:24:37 +0200 Subject: [PATCH 11/27] Fix logging of messages that are binary before closing their connection (#25361) --- streaming/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/streaming/index.js b/streaming/index.js index 4b2607ed92..9b43112d22 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -1209,7 +1209,7 @@ const startServer = async () => { ws.on('message', (data, isBinary) => { if (isBinary) { - log.debug('Received binary data, closing connection'); + log.warn('socket', 'Received binary data, closing connection'); ws.close(1003, 'The mastodon streaming server does not support binary messages'); return; } From c94bb9ba9a1df2e060e6f3debff57a0b7539be9f Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Sat, 10 Jun 2023 12:27:35 -0400 Subject: [PATCH 12/27] Disable paperclip processing in specs (#25359) --- spec/models/media_attachment_spec.rb | 2 +- spec/rails_helper.rb | 6 ++++++ spec/support/examples/models/concerns/account_avatar.rb | 2 +- spec/support/examples/models/concerns/account_header.rb | 2 +- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/spec/models/media_attachment_spec.rb b/spec/models/media_attachment_spec.rb index 4d4bc748f7..2dfc6cf925 100644 --- a/spec/models/media_attachment_spec.rb +++ b/spec/models/media_attachment_spec.rb @@ -2,7 +2,7 @@ require 'rails_helper' -RSpec.describe MediaAttachment do +RSpec.describe MediaAttachment, paperclip_processing: true do describe 'local?' do subject { media_attachment.local? } diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index d7e2b5c185..f4113d565f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -94,6 +94,12 @@ RSpec.configure do |config| stub_jsonld_contexts! end + config.before(:each) do |example| + unless example.metadata[:paperclip_processing] + allow_any_instance_of(Paperclip::Attachment).to receive(:post_process).and_return(true) # rubocop:disable RSpec/AnyInstance + end + end + config.after :each do Rails.cache.clear redis.del(redis.keys) diff --git a/spec/support/examples/models/concerns/account_avatar.rb b/spec/support/examples/models/concerns/account_avatar.rb index 2180f52739..8f5b81f3a5 100644 --- a/spec/support/examples/models/concerns/account_avatar.rb +++ b/spec/support/examples/models/concerns/account_avatar.rb @@ -17,7 +17,7 @@ shared_examples 'AccountAvatar' do |fabricator| end end - describe 'base64-encoded files' do + describe 'base64-encoded files', paperclip_processing: true do let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" } let(:account) { Fabricate(fabricator, avatar: base64_attachment) } diff --git a/spec/support/examples/models/concerns/account_header.rb b/spec/support/examples/models/concerns/account_header.rb index 77ee0e6290..d65f54f00f 100644 --- a/spec/support/examples/models/concerns/account_header.rb +++ b/spec/support/examples/models/concerns/account_header.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true shared_examples 'AccountHeader' do |fabricator| - describe 'base64-encoded files' do + describe 'base64-encoded files', paperclip_processing: true do let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" } let(:account) { Fabricate(fabricator, header: base64_attachment) } From 3a2a15c6ea4d4603469861ed9be09da12a122e45 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Sat, 10 Jun 2023 12:29:01 -0400 Subject: [PATCH 13/27] Use `build` where possible in fabricators (#25360) --- spec/fabricators/account_domain_block_fabricator.rb | 2 +- spec/fabricators/account_moderation_note_fabricator.rb | 4 ++-- spec/fabricators/account_note_fabricator.rb | 4 ++-- spec/fabricators/account_stat_fabricator.rb | 2 +- .../fabricators/account_statuses_cleanup_policy_fabricator.rb | 2 +- spec/fabricators/account_warning_fabricator.rb | 2 +- spec/fabricators/admin_action_log_fabricator.rb | 2 +- spec/fabricators/backup_fabricator.rb | 2 +- spec/fabricators/block_fabricator.rb | 4 ++-- spec/fabricators/bookmark_fabricator.rb | 4 ++-- spec/fabricators/bulk_import_fabricator.rb | 2 +- spec/fabricators/bulk_import_row_fabricator.rb | 2 +- spec/fabricators/canonical_email_block_fabricator.rb | 2 +- spec/fabricators/custom_filter_fabricator.rb | 2 +- spec/fabricators/custom_filter_keyword_fabricator.rb | 2 +- spec/fabricators/custom_filter_status_fabricator.rb | 4 ++-- spec/fabricators/device_fabricator.rb | 4 ++-- spec/fabricators/encrypted_message_fabricator.rb | 4 ++-- spec/fabricators/favourite_fabricator.rb | 4 ++-- spec/fabricators/featured_tag_fabricator.rb | 4 ++-- spec/fabricators/follow_fabricator.rb | 4 ++-- spec/fabricators/follow_request_fabricator.rb | 4 ++-- spec/fabricators/identity_fabricator.rb | 2 +- spec/fabricators/invite_fabricator.rb | 2 +- spec/fabricators/list_fabricator.rb | 2 +- spec/fabricators/login_activity_fabricator.rb | 2 +- spec/fabricators/marker_fabricator.rb | 2 +- spec/fabricators/media_attachment_fabricator.rb | 2 +- spec/fabricators/mention_fabricator.rb | 4 ++-- spec/fabricators/mute_fabricator.rb | 4 ++-- spec/fabricators/notification_fabricator.rb | 2 +- spec/fabricators/one_time_key_fabricator.rb | 2 +- spec/fabricators/poll_fabricator.rb | 4 ++-- spec/fabricators/poll_vote_fabricator.rb | 2 +- spec/fabricators/report_fabricator.rb | 4 ++-- spec/fabricators/report_note_fabricator.rb | 4 ++-- spec/fabricators/scheduled_status_fabricator.rb | 2 +- spec/fabricators/session_activation_fabricator.rb | 2 +- spec/fabricators/status_fabricator.rb | 2 +- spec/fabricators/status_pin_fabricator.rb | 4 ++-- spec/fabricators/tag_follow_fabricator.rb | 2 +- 41 files changed, 58 insertions(+), 58 deletions(-) diff --git a/spec/fabricators/account_domain_block_fabricator.rb b/spec/fabricators/account_domain_block_fabricator.rb index ff85e17f3f..83df509da2 100644 --- a/spec/fabricators/account_domain_block_fabricator.rb +++ b/spec/fabricators/account_domain_block_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:account_domain_block) do - account + account { Fabricate.build(:account) } domain 'example.com' end diff --git a/spec/fabricators/account_moderation_note_fabricator.rb b/spec/fabricators/account_moderation_note_fabricator.rb index 341a24dea0..05a687bf4e 100644 --- a/spec/fabricators/account_moderation_note_fabricator.rb +++ b/spec/fabricators/account_moderation_note_fabricator.rb @@ -2,6 +2,6 @@ Fabricator(:account_moderation_note) do content 'MyText' - account - target_account { Fabricate(:account) } + account { Fabricate.build(:account) } + target_account { Fabricate.build(:account) } end diff --git a/spec/fabricators/account_note_fabricator.rb b/spec/fabricators/account_note_fabricator.rb index bb4ed8b24d..241362c144 100644 --- a/spec/fabricators/account_note_fabricator.rb +++ b/spec/fabricators/account_note_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:account_note) do - account - target_account { Fabricate(:account) } + account { Fabricate.build(:account) } + target_account { Fabricate.build(:account) } comment 'User note text' end diff --git a/spec/fabricators/account_stat_fabricator.rb b/spec/fabricators/account_stat_fabricator.rb index e6085c5f2b..20272fb22f 100644 --- a/spec/fabricators/account_stat_fabricator.rb +++ b/spec/fabricators/account_stat_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:account_stat) do - account + account { Fabricate.build(:account) } statuses_count '123' following_count '456' followers_count '789' diff --git a/spec/fabricators/account_statuses_cleanup_policy_fabricator.rb b/spec/fabricators/account_statuses_cleanup_policy_fabricator.rb index 0e756ddbaa..fcf7a53475 100644 --- a/spec/fabricators/account_statuses_cleanup_policy_fabricator.rb +++ b/spec/fabricators/account_statuses_cleanup_policy_fabricator.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true Fabricator(:account_statuses_cleanup_policy) do - account + account { Fabricate.build(:account) } end diff --git a/spec/fabricators/account_warning_fabricator.rb b/spec/fabricators/account_warning_fabricator.rb index e5059e37f5..70005a927b 100644 --- a/spec/fabricators/account_warning_fabricator.rb +++ b/spec/fabricators/account_warning_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:account_warning) do - account + account { Fabricate.build(:account) } target_account(fabricator: :account) text { Faker::Lorem.paragraph } action 'suspend' diff --git a/spec/fabricators/admin_action_log_fabricator.rb b/spec/fabricators/admin_action_log_fabricator.rb index a259644bdc..3acedbffd3 100644 --- a/spec/fabricators/admin_action_log_fabricator.rb +++ b/spec/fabricators/admin_action_log_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator('Admin::ActionLog') do - account + account { Fabricate.build(:account) } action 'MyString' target nil end diff --git a/spec/fabricators/backup_fabricator.rb b/spec/fabricators/backup_fabricator.rb index c73ae54bed..58e37c9875 100644 --- a/spec/fabricators/backup_fabricator.rb +++ b/spec/fabricators/backup_fabricator.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true Fabricator(:backup) do - user + user { Fabricate.build(:user) } end diff --git a/spec/fabricators/block_fabricator.rb b/spec/fabricators/block_fabricator.rb index c2e9e9628d..c4087e46d2 100644 --- a/spec/fabricators/block_fabricator.rb +++ b/spec/fabricators/block_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:block) do - account - target_account { Fabricate(:account) } + account { Fabricate.build(:account) } + target_account { Fabricate.build(:account) } end diff --git a/spec/fabricators/bookmark_fabricator.rb b/spec/fabricators/bookmark_fabricator.rb index e21046fc25..994ac6e687 100644 --- a/spec/fabricators/bookmark_fabricator.rb +++ b/spec/fabricators/bookmark_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:bookmark) do - account - status + account { Fabricate.build(:account) } + status { Fabricate.build(:status) } end diff --git a/spec/fabricators/bulk_import_fabricator.rb b/spec/fabricators/bulk_import_fabricator.rb index 673b7960d9..d30758dfe0 100644 --- a/spec/fabricators/bulk_import_fabricator.rb +++ b/spec/fabricators/bulk_import_fabricator.rb @@ -8,5 +8,5 @@ Fabricator(:bulk_import) do imported_items 1 finished_at '2022-11-18 14:55:07' overwrite false - account + account { Fabricate.build(:account) } end diff --git a/spec/fabricators/bulk_import_row_fabricator.rb b/spec/fabricators/bulk_import_row_fabricator.rb index f8358e734d..10a4bf1608 100644 --- a/spec/fabricators/bulk_import_row_fabricator.rb +++ b/spec/fabricators/bulk_import_row_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:bulk_import_row) do - bulk_import + bulk_import { Fabricate.build(:bulk_import) } data '' end diff --git a/spec/fabricators/canonical_email_block_fabricator.rb b/spec/fabricators/canonical_email_block_fabricator.rb index 3a018059fc..1ef53ff4a4 100644 --- a/spec/fabricators/canonical_email_block_fabricator.rb +++ b/spec/fabricators/canonical_email_block_fabricator.rb @@ -2,5 +2,5 @@ Fabricator(:canonical_email_block) do email { sequence(:email) { |i| "#{i}#{Faker::Internet.email}" } } - reference_account { Fabricate(:account) } + reference_account { Fabricate.build(:account) } end diff --git a/spec/fabricators/custom_filter_fabricator.rb b/spec/fabricators/custom_filter_fabricator.rb index 5fee4f01af..766cc3b115 100644 --- a/spec/fabricators/custom_filter_fabricator.rb +++ b/spec/fabricators/custom_filter_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:custom_filter) do - account + account { Fabricate.build(:account) } expires_at nil phrase 'discourse' context %w(home notifications) diff --git a/spec/fabricators/custom_filter_keyword_fabricator.rb b/spec/fabricators/custom_filter_keyword_fabricator.rb index f1fb440dc5..aa4bf84739 100644 --- a/spec/fabricators/custom_filter_keyword_fabricator.rb +++ b/spec/fabricators/custom_filter_keyword_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:custom_filter_keyword) do - custom_filter + custom_filter { Fabricate.build(:custom_filter) } keyword 'discourse' end diff --git a/spec/fabricators/custom_filter_status_fabricator.rb b/spec/fabricators/custom_filter_status_fabricator.rb index 3ef1d0ec83..f66f62e561 100644 --- a/spec/fabricators/custom_filter_status_fabricator.rb +++ b/spec/fabricators/custom_filter_status_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:custom_filter_status) do - custom_filter - status + custom_filter { Fabricate.build(:custom_filter) } + status { Fabricate.build(:status) } end diff --git a/spec/fabricators/device_fabricator.rb b/spec/fabricators/device_fabricator.rb index 26c71b4fdd..37a2e8977d 100644 --- a/spec/fabricators/device_fabricator.rb +++ b/spec/fabricators/device_fabricator.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true Fabricator(:device) do - access_token - account + access_token { Fabricate.build(:access_token) } + account { Fabricate.build(:account) } device_id { Faker::Number.number(digits: 5) } name { Faker::App.name } fingerprint_key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) } diff --git a/spec/fabricators/encrypted_message_fabricator.rb b/spec/fabricators/encrypted_message_fabricator.rb index 43b3105146..349b659c2f 100644 --- a/spec/fabricators/encrypted_message_fabricator.rb +++ b/spec/fabricators/encrypted_message_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:encrypted_message) do - device - from_account { Fabricate(:account) } + device { Fabricate.build(:device) } + from_account { Fabricate.build(:account) } from_device_id { Faker::Number.number(digits: 5) } end diff --git a/spec/fabricators/favourite_fabricator.rb b/spec/fabricators/favourite_fabricator.rb index 005947e6f8..639416987e 100644 --- a/spec/fabricators/favourite_fabricator.rb +++ b/spec/fabricators/favourite_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:favourite) do - account - status + account { Fabricate.build(:account) } + status { Fabricate.build(:status) } end diff --git a/spec/fabricators/featured_tag_fabricator.rb b/spec/fabricators/featured_tag_fabricator.rb index 838364056b..0803dc43a7 100644 --- a/spec/fabricators/featured_tag_fabricator.rb +++ b/spec/fabricators/featured_tag_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:featured_tag) do - account - tag + account { Fabricate.build(:account) } + tag { Fabricate.build(:tag) } name { sequence(:name) { |i| "Tag#{i}" } } end diff --git a/spec/fabricators/follow_fabricator.rb b/spec/fabricators/follow_fabricator.rb index 41b5305d55..29886b4301 100644 --- a/spec/fabricators/follow_fabricator.rb +++ b/spec/fabricators/follow_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:follow) do - account - target_account { Fabricate(:account) } + account { Fabricate.build(:account) } + target_account { Fabricate.build(:account) } end diff --git a/spec/fabricators/follow_request_fabricator.rb b/spec/fabricators/follow_request_fabricator.rb index 86b82611f7..6b2d658a37 100644 --- a/spec/fabricators/follow_request_fabricator.rb +++ b/spec/fabricators/follow_request_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:follow_request) do - account - target_account { Fabricate(:account, locked: true) } + account { Fabricate.build(:account) } + target_account { Fabricate.build(:account, locked: true) } end diff --git a/spec/fabricators/identity_fabricator.rb b/spec/fabricators/identity_fabricator.rb index 58072c0d65..83655ee839 100644 --- a/spec/fabricators/identity_fabricator.rb +++ b/spec/fabricators/identity_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:identity) do - user + user { Fabricate.build(:user) } provider 'MyString' uid 'MyString' end diff --git a/spec/fabricators/invite_fabricator.rb b/spec/fabricators/invite_fabricator.rb index 4f47d6ce2f..8fdf5f9185 100644 --- a/spec/fabricators/invite_fabricator.rb +++ b/spec/fabricators/invite_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:invite) do - user + user { Fabricate.build(:user) } expires_at nil max_uses nil uses 0 diff --git a/spec/fabricators/list_fabricator.rb b/spec/fabricators/list_fabricator.rb index 47af752b8c..d2bdc10129 100644 --- a/spec/fabricators/list_fabricator.rb +++ b/spec/fabricators/list_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:list) do - account + account { Fabricate.build(:account) } title 'MyString' end diff --git a/spec/fabricators/login_activity_fabricator.rb b/spec/fabricators/login_activity_fabricator.rb index 2b30658ff5..3309a303db 100644 --- a/spec/fabricators/login_activity_fabricator.rb +++ b/spec/fabricators/login_activity_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:login_activity) do - user + user { Fabricate.build(:user) } authentication_method 'password' success true failure_reason nil diff --git a/spec/fabricators/marker_fabricator.rb b/spec/fabricators/marker_fabricator.rb index 561c2553ae..641db6b9ec 100644 --- a/spec/fabricators/marker_fabricator.rb +++ b/spec/fabricators/marker_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:marker) do - user + user { Fabricate.build(:user) } timeline 'home' last_read_id 0 lock_version 0 diff --git a/spec/fabricators/media_attachment_fabricator.rb b/spec/fabricators/media_attachment_fabricator.rb index 4a081dccbe..062d3cbfec 100644 --- a/spec/fabricators/media_attachment_fabricator.rb +++ b/spec/fabricators/media_attachment_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:media_attachment) do - account + account { Fabricate.build(:account) } file do |attrs| case attrs[:type] diff --git a/spec/fabricators/mention_fabricator.rb b/spec/fabricators/mention_fabricator.rb index 5a83928275..ee8160aeb3 100644 --- a/spec/fabricators/mention_fabricator.rb +++ b/spec/fabricators/mention_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:mention) do - account - status + account { Fabricate.build(:account) } + status { Fabricate.build(:status) } end diff --git a/spec/fabricators/mute_fabricator.rb b/spec/fabricators/mute_fabricator.rb index 242ae2b08e..a70d3ff26d 100644 --- a/spec/fabricators/mute_fabricator.rb +++ b/spec/fabricators/mute_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:mute) do - account - target_account { Fabricate(:account) } + account { Fabricate.build(:account) } + target_account { Fabricate.build(:account) } end diff --git a/spec/fabricators/notification_fabricator.rb b/spec/fabricators/notification_fabricator.rb index 1e0c809874..fdfd7673ef 100644 --- a/spec/fabricators/notification_fabricator.rb +++ b/spec/fabricators/notification_fabricator.rb @@ -2,5 +2,5 @@ Fabricator(:notification) do activity fabricator: :status - account + account { Fabricate.build(:account) } end diff --git a/spec/fabricators/one_time_key_fabricator.rb b/spec/fabricators/one_time_key_fabricator.rb index cfb365cabb..505282e05d 100644 --- a/spec/fabricators/one_time_key_fabricator.rb +++ b/spec/fabricators/one_time_key_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:one_time_key) do - device + device { Fabricate.build(:device) } key_id { Faker::Alphanumeric.alphanumeric(number: 10) } key { Base64.strict_encode64(Ed25519::SigningKey.generate.verify_key.to_bytes) } diff --git a/spec/fabricators/poll_fabricator.rb b/spec/fabricators/poll_fabricator.rb index 19c3b1d164..0203609ce7 100644 --- a/spec/fabricators/poll_fabricator.rb +++ b/spec/fabricators/poll_fabricator.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true Fabricator(:poll) do - account - status + account { Fabricate.build(:account) } + status { Fabricate.build(:status) } expires_at { 7.days.from_now } options %w(Foo Bar) multiple false diff --git a/spec/fabricators/poll_vote_fabricator.rb b/spec/fabricators/poll_vote_fabricator.rb index 9099ae96fe..47813cdb71 100644 --- a/spec/fabricators/poll_vote_fabricator.rb +++ b/spec/fabricators/poll_vote_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:poll_vote) do - account + account { Fabricate.build(:account) } poll choice 0 end diff --git a/spec/fabricators/report_fabricator.rb b/spec/fabricators/report_fabricator.rb index 7124773ad0..ed890230a7 100644 --- a/spec/fabricators/report_fabricator.rb +++ b/spec/fabricators/report_fabricator.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true Fabricator(:report) do - account - target_account { Fabricate(:account) } + account { Fabricate.build(:account) } + target_account { Fabricate.build(:account) } comment 'You nasty' action_taken_at nil end diff --git a/spec/fabricators/report_note_fabricator.rb b/spec/fabricators/report_note_fabricator.rb index f257fe2b7d..080fad51ac 100644 --- a/spec/fabricators/report_note_fabricator.rb +++ b/spec/fabricators/report_note_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:report_note) do - report - account { Fabricate(:account) } + report { Fabricate.build(:report) } + account { Fabricate.build(:account) } content 'Test Content' end diff --git a/spec/fabricators/scheduled_status_fabricator.rb b/spec/fabricators/scheduled_status_fabricator.rb index e517f258a2..eed275ab92 100644 --- a/spec/fabricators/scheduled_status_fabricator.rb +++ b/spec/fabricators/scheduled_status_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:scheduled_status) do - account + account { Fabricate.build(:account) } scheduled_at { 20.hours.from_now } end diff --git a/spec/fabricators/session_activation_fabricator.rb b/spec/fabricators/session_activation_fabricator.rb index b28d5e41d7..4b5244cec6 100644 --- a/spec/fabricators/session_activation_fabricator.rb +++ b/spec/fabricators/session_activation_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:session_activation) do - user + user { Fabricate.build(:user) } session_id 'MyString' end diff --git a/spec/fabricators/status_fabricator.rb b/spec/fabricators/status_fabricator.rb index 17ac9ccd8a..32a2cbf6ac 100644 --- a/spec/fabricators/status_fabricator.rb +++ b/spec/fabricators/status_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:status) do - account + account { Fabricate.build(:account) } text 'Lorem ipsum dolor sit amet' after_build do |status| diff --git a/spec/fabricators/status_pin_fabricator.rb b/spec/fabricators/status_pin_fabricator.rb index 9ad0ac9de3..ceaaa34a79 100644 --- a/spec/fabricators/status_pin_fabricator.rb +++ b/spec/fabricators/status_pin_fabricator.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Fabricator(:status_pin) do - account - status { |attrs| Fabricate(:status, account: attrs[:account], visibility: :public) } + account { Fabricate.build(:account) } + status { |attrs| Fabricate.build(:status, account: attrs[:account], visibility: :public) } end diff --git a/spec/fabricators/tag_follow_fabricator.rb b/spec/fabricators/tag_follow_fabricator.rb index cbe5b09898..014435d606 100644 --- a/spec/fabricators/tag_follow_fabricator.rb +++ b/spec/fabricators/tag_follow_fabricator.rb @@ -2,5 +2,5 @@ Fabricator(:tag_follow) do tag - account + account { Fabricate.build(:account) } end From 0e200d4e2f5b80209ae450a4625d713092969051 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Sat, 10 Jun 2023 13:30:43 -0300 Subject: [PATCH 14/27] Migrate to request specs in `/api/v1/admin/reports` (#25355) --- .../api/v1/admin/reports_controller_spec.rb | 111 ------- spec/requests/api/v1/admin/reports_spec.rb | 292 ++++++++++++++++++ 2 files changed, 292 insertions(+), 111 deletions(-) delete mode 100644 spec/controllers/api/v1/admin/reports_controller_spec.rb create mode 100644 spec/requests/api/v1/admin/reports_spec.rb diff --git a/spec/controllers/api/v1/admin/reports_controller_spec.rb b/spec/controllers/api/v1/admin/reports_controller_spec.rb deleted file mode 100644 index 4f0c484e59..0000000000 --- a/spec/controllers/api/v1/admin/reports_controller_spec.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Api::V1::Admin::ReportsController 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(:report) { Fabricate(:report) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - shared_examples 'forbidden for wrong scope' do |wrong_scope| - let(:scopes) { wrong_scope } - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - shared_examples 'forbidden for wrong role' do |wrong_role| - let(:role) { UserRole.find_by(name: wrong_role) } - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - describe 'GET #index' do - before do - get :index - 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 'GET #show' do - before do - get :show, params: { id: report.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 #resolve' do - before do - post :resolve, params: { id: report.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 #reopen' do - before do - post :reopen, params: { id: report.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 #assign_to_self' do - before do - post :assign_to_self, params: { id: report.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 #unassign' do - before do - post :unassign, params: { id: report.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/reports_spec.rb b/spec/requests/api/v1/admin/reports_spec.rb new file mode 100644 index 0000000000..cd9fc100e7 --- /dev/null +++ b/spec/requests/api/v1/admin/reports_spec.rb @@ -0,0 +1,292 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Reports' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read:reports admin:write:reports' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + describe 'GET /api/v1/admin/reports' do + subject do + get '/api/v1/admin/reports', headers: headers, params: params + end + + let(:params) { {} } + + 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 + + context 'when there are no reports' do + it 'returns an empty list' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are reports' do + let!(:reporter) { Fabricate(:account) } + let!(:spammer) { Fabricate(:account) } + let(:expected_response) do + scope.map do |report| + hash_including({ + id: report.id.to_s, + action_taken: report.action_taken?, + category: report.category, + comment: report.comment, + account: hash_including(id: report.account.id.to_s), + target_account: hash_including(id: report.target_account.id.to_s), + statuses: report.statuses, + rules: report.rules, + forwarded: report.forwarded, + }) + end + end + let(:scope) { Report.unresolved } + + before do + Fabricate(:report) + Fabricate(:report, target_account: spammer) + Fabricate(:report, account: reporter, target_account: spammer) + Fabricate(:report, action_taken_at: 4.days.ago, account: reporter) + Fabricate(:report, action_taken_at: 20.days.ago) + end + + it 'returns all unresolved reports' do + subject + + expect(body_as_json).to match_array(expected_response) + end + + context 'with resolved param' do + let(:params) { { resolved: true } } + let(:scope) { Report.resolved } + + it 'returns only the resolved reports' do + subject + + expect(body_as_json).to match_array(expected_response) + end + end + + context 'with account_id param' do + let(:params) { { account_id: reporter.id } } + let(:scope) { Report.unresolved.where(account: reporter) } + + it 'returns all unresolved reports filed by the specified account' do + subject + + expect(body_as_json).to match_array(expected_response) + end + end + + context 'with target_account_id param' do + let(:params) { { target_account_id: spammer.id } } + let(:scope) { Report.unresolved.where(target_account: spammer) } + + it 'returns all unresolved reports targeting the specified account' do + subject + + expect(body_as_json).to match_array(expected_response) + end + end + + context 'with limit param' do + let(:params) { { limit: 1 } } + + it 'returns only the requested number of reports' do + subject + + expect(body_as_json.size).to eq(1) + end + end + end + end + + describe 'GET /api/v1/admin/reports/:id' do + subject do + get "/api/v1/admin/reports/#{report.id}", headers: headers + end + + let(:report) { Fabricate(:report) } + + 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 'returns the requested report content' do + subject + + expect(body_as_json).to include( + { + id: report.id.to_s, + action_taken: report.action_taken?, + category: report.category, + comment: report.comment, + account: a_hash_including(id: report.account.id.to_s), + target_account: a_hash_including(id: report.target_account.id.to_s), + statuses: report.statuses, + rules: report.rules, + forwarded: report.forwarded, + } + ) + end + end + + describe 'PUT /api/v1/admin/reports/:id' do + subject do + put "/api/v1/admin/reports/#{report.id}", headers: headers, params: params + end + + 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 + expect { subject }.to change { report.reload.category }.from('other').to('spam') + end + + it 'returns the updated report content' do + subject + + report.reload + + expect(body_as_json).to include( + { + id: report.id.to_s, + action_taken: report.action_taken?, + category: report.category, + comment: report.comment, + account: a_hash_including(id: report.account.id.to_s), + target_account: a_hash_including(id: report.target_account.id.to_s), + statuses: report.statuses, + rules: report.rules, + forwarded: report.forwarded, + } + ) + end + end + + describe 'POST #resolve' do + subject do + post "/api/v1/admin/reports/#{report.id}/resolve", headers: headers + end + + let(:report) { Fabricate(:report, action_taken_at: nil) } + + 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 + expect { subject }.to change { report.reload.unresolved? }.from(true).to(false) + end + end + + describe 'POST #reopen' do + subject do + post "/api/v1/admin/reports/#{report.id}/reopen", headers: headers + end + + let(:report) { Fabricate(:report, action_taken_at: 10.days.ago) } + + 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 + expect { subject }.to change { report.reload.unresolved? }.from(false).to(true) + end + end + + describe 'POST #assign_to_self' do + subject do + post "/api/v1/admin/reports/#{report.id}/assign_to_self", headers: headers + end + + let(:report) { Fabricate(:report) } + + 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 + expect { subject }.to change { report.reload.assigned_account_id }.from(nil).to(user.account.id) + end + end + + describe 'POST #unassign' do + subject do + post "/api/v1/admin/reports/#{report.id}/unassign", headers: headers + end + + let(:report) { Fabricate(:report, assigned_account_id: user.account.id) } + + 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 + expect { subject }.to change { report.reload.assigned_account_id }.from(user.account.id).to(nil) + end + end +end From 841c220c4008e5391bbba1248b105a229fca1865 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Sat, 10 Jun 2023 13:32:07 -0300 Subject: [PATCH 15/27] Migrate to request specs in `/api/v1/admin/domain_blocks` (#25335) --- .../v1/admin/domain_blocks_controller_spec.rb | 180 ----------- .../api/v1/admin/domain_blocks_spec.rb | 284 ++++++++++++++++++ 2 files changed, 284 insertions(+), 180 deletions(-) delete mode 100644 spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb create mode 100644 spec/requests/api/v1/admin/domain_blocks_spec.rb diff --git a/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb deleted file mode 100644 index 5659843f7a..0000000000 --- a/spec/controllers/api/v1/admin/domain_blocks_controller_spec.rb +++ /dev/null @@ -1,180 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Api::V1::Admin::DomainBlocksController 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) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - shared_examples 'forbidden for wrong scope' do |wrong_scope| - let(:scopes) { wrong_scope } - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - shared_examples 'forbidden for wrong role' do |wrong_role| - let(:role) { UserRole.find_by(name: wrong_role) } - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - describe 'GET #index' do - let!(:block) { Fabricate(:domain_block) } - - before do - get :index - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns the expected domain blocks' do - json = body_as_json - expect(json.length).to eq 1 - expect(json[0][:id].to_i).to eq block.id - end - end - - describe 'GET #show' do - let!(:block) { Fabricate(:domain_block) } - - before do - get :show, params: { id: block.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns expected domain name' do - json = body_as_json - expect(json[:domain]).to eq block.domain - end - end - - describe 'PUT #update' do - let!(:remote_account) { Fabricate(:account, domain: 'example.com') } - let(:subject) do - post :update, params: { id: domain_block.id, domain: 'example.com', severity: new_severity } - end - let(:domain_block) { Fabricate(:domain_block, domain: 'example.com', severity: original_severity) } - - before do - BlockDomainService.new.call(domain_block) - end - - context 'when downgrading a domain suspension to silence' do - let(:original_severity) { 'suspend' } - let(:new_severity) { 'silence' } - - it 'changes the block severity' do - expect { subject }.to change { domain_block.reload.severity }.from('suspend').to('silence') - end - - it 'undoes individual suspensions' do - expect { subject }.to change { remote_account.reload.suspended? }.from(true).to(false) - end - - it 'performs individual silences' do - expect { subject }.to change { remote_account.reload.silenced? }.from(false).to(true) - end - end - - context 'when upgrading a domain silence to suspend' do - let(:original_severity) { 'silence' } - let(:new_severity) { 'suspend' } - - it 'changes the block severity' do - expect { subject }.to change { domain_block.reload.severity }.from('silence').to('suspend') - end - - it 'undoes individual silences' do - expect { subject }.to change { remote_account.reload.silenced? }.from(true).to(false) - end - - it 'performs individual suspends' do - expect { subject }.to change { remote_account.reload.suspended? }.from(false).to(true) - end - end - end - - describe 'DELETE #destroy' do - let!(:block) { Fabricate(:domain_block) } - - before do - delete :destroy, params: { id: block.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'deletes the block' do - expect(DomainBlock.find_by(id: block.id)).to be_nil - end - end - - describe 'POST #create' do - let(:existing_block_domain) { 'example.com' } - let!(:block) { Fabricate(:domain_block, domain: existing_block_domain, severity: :suspend) } - - before do - post :create, params: { domain: 'foo.bar.com', severity: :silence } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns expected domain name' do - json = body_as_json - expect(json[:domain]).to eq 'foo.bar.com' - end - - it 'creates a domain block' do - expect(DomainBlock.find_by(domain: 'foo.bar.com')).to_not be_nil - end - - context 'when a stricter domain block already exists' do - let(:existing_block_domain) { 'bar.com' } - - it 'returns http unprocessable entity' do - expect(response).to have_http_status(422) - end - - it 'renders existing domain block in error' do - json = body_as_json - expect(json[:existing_domain_block][:domain]).to eq existing_block_domain - end - end - end -end diff --git a/spec/requests/api/v1/admin/domain_blocks_spec.rb b/spec/requests/api/v1/admin/domain_blocks_spec.rb new file mode 100644 index 0000000000..b3d52311b3 --- /dev/null +++ b/spec/requests/api/v1/admin/domain_blocks_spec.rb @@ -0,0 +1,284 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Domain Blocks' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read:domain_blocks admin:write:domain_blocks' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + describe 'GET /api/v1/admin/domain_blocks' do + subject do + get '/api/v1/admin/domain_blocks', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + 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 + + context 'when there are no domain blocks' do + it 'returns an empty list' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are domain blocks' do + let!(:domain_blocks) do + [ + Fabricate(:domain_block, severity: :silence, reject_media: true), + Fabricate(:domain_block, severity: :suspend, obfuscate: true), + Fabricate(:domain_block, severity: :noop, reject_reports: true), + Fabricate(:domain_block, public_comment: 'Spam'), + Fabricate(:domain_block, private_comment: 'Spam'), + ] + end + let(:expected_responde) do + domain_blocks.map do |domain_block| + { + id: domain_block.id.to_s, + domain: domain_block.domain, + created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + severity: domain_block.severity.to_s, + reject_media: domain_block.reject_media, + reject_reports: domain_block.reject_reports, + private_comment: domain_block.private_comment, + public_comment: domain_block.public_comment, + obfuscate: domain_block.obfuscate, + } + end + end + + it 'returns the expected domain blocks' do + subject + + expect(body_as_json).to match_array(expected_responde) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of domain blocks' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + end + end + + describe 'GET /api/v1/admin/domain_blocks/:id' do + subject do + get "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers + end + + let!(:domain_block) { Fabricate(:domain_block) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + 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 the expected domain block content' do + subject + + expect(body_as_json).to eq( + { + id: domain_block.id.to_s, + domain: domain_block.domain, + created_at: domain_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + severity: domain_block.severity.to_s, + reject_media: domain_block.reject_media, + reject_reports: domain_block.reject_reports, + private_comment: domain_block.private_comment, + public_comment: domain_block.public_comment, + obfuscate: domain_block.obfuscate, + } + ) + end + + context 'when the requested domain block does not exist' do + it 'returns http not found' do + get '/api/v1/admin/domain_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/domain_blocks' do + subject do + post '/api/v1/admin/domain_blocks', headers: headers, params: params + end + + let(:params) { { domain: 'foo.bar.com', severity: :silence } } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + 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 + subject + + body = body_as_json + + 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 + + context 'when a stricter domain block already exists' do + before do + Fabricate(:domain_block, domain: 'bar.com', severity: :suspend) + end + + it 'returns http unprocessable entity' 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 + + context 'when given domain name is invalid' do + let(:params) { { domain: 'foo bar', severity: :silence } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + describe 'PUT /api/v1/admin/domain_blocks/:id' do + subject do + put "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers, params: params + end + + let!(:domain_block) { Fabricate(:domain_block, domain: 'example.com', severity: :silence) } + let(:params) { { domain: 'example.com', severity: 'suspend' } } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + 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 the updated domain block' do + subject + + expect(body_as_json).to match a_hash_including( + { + id: domain_block.id.to_s, + domain: domain_block.domain, + severity: 'suspend', + } + ) + end + + it 'updates the block severity' do + expect { subject }.to change { domain_block.reload.severity }.from('silence').to('suspend') + end + + context 'when domain block does not exist' do + it 'returns http not found' do + put '/api/v1/admin/domain_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'DELETE /api/v1/admin/domain_blocks/:id' do + subject do + delete "/api/v1/admin/domain_blocks/#{domain_block.id}", headers: headers + end + + let!(:domain_block) { Fabricate(:domain_block) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + 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 'deletes the domain block' do + subject + + expect(DomainBlock.find_by(id: domain_block.id)).to be_nil + end + + context 'when domain block does not exist' do + it 'returns http not found' do + delete '/api/v1/admin/domain_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end +end From 4301d8cbb3d62b9c04cf01bfffab3ed8564ae2a0 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Sat, 10 Jun 2023 13:32:26 -0300 Subject: [PATCH 16/27] Migrate to request specs in `/api/v1/admin/domain_allows` (#25333) --- .../v1/admin/domain_allows_controller_spec.rb | 140 ------------ spec/fabricators/domain_allow_fabricator.rb | 2 +- .../api/v1/admin/domain_allows_spec.rb | 214 ++++++++++++++++++ 3 files changed, 215 insertions(+), 141 deletions(-) delete mode 100644 spec/controllers/api/v1/admin/domain_allows_controller_spec.rb create mode 100644 spec/requests/api/v1/admin/domain_allows_spec.rb diff --git a/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb b/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb deleted file mode 100644 index 69aeb6451a..0000000000 --- a/spec/controllers/api/v1/admin/domain_allows_controller_spec.rb +++ /dev/null @@ -1,140 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -RSpec.describe Api::V1::Admin::DomainAllowsController 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) } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - shared_examples 'forbidden for wrong scope' do |wrong_scope| - let(:scopes) { wrong_scope } - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - shared_examples 'forbidden for wrong role' do |wrong_role| - let(:role) { UserRole.find_by(name: wrong_role) } - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - describe 'GET #index' do - let!(:domain_allow) { Fabricate(:domain_allow) } - - before do - get :index - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns the expected domain allows' do - json = body_as_json - expect(json.length).to eq 1 - expect(json[0][:id].to_i).to eq domain_allow.id - end - end - - describe 'GET #show' do - let!(:domain_allow) { Fabricate(:domain_allow) } - - before do - get :show, params: { id: domain_allow.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns expected domain name' do - json = body_as_json - expect(json[:domain]).to eq domain_allow.domain - end - end - - describe 'DELETE #destroy' do - let!(:domain_allow) { Fabricate(:domain_allow) } - - before do - delete :destroy, params: { id: domain_allow.id } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'deletes the block' do - expect(DomainAllow.find_by(id: domain_allow.id)).to be_nil - end - end - - describe 'POST #create' do - let!(:domain_allow) { Fabricate(:domain_allow, domain: 'example.com') } - - context 'with a valid domain' do - before do - post :create, params: { domain: 'foo.bar.com' } - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it 'returns expected domain name' do - json = body_as_json - expect(json[:domain]).to eq 'foo.bar.com' - end - - it 'creates a domain block' do - expect(DomainAllow.find_by(domain: 'foo.bar.com')).to_not be_nil - end - end - - context 'with invalid domain name' do - before do - post :create, params: { domain: 'foo bar' } - end - - it 'returns http unprocessable entity' do - expect(response).to have_http_status(422) - end - end - - context 'when domain name is not specified' do - it 'returns http unprocessable entity' do - post :create - - expect(response).to have_http_status(422) - end - end - end -end diff --git a/spec/fabricators/domain_allow_fabricator.rb b/spec/fabricators/domain_allow_fabricator.rb index b32af129bc..12fdaaea1a 100644 --- a/spec/fabricators/domain_allow_fabricator.rb +++ b/spec/fabricators/domain_allow_fabricator.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true Fabricator(:domain_allow) do - domain 'MyString' + domain { sequence(:domain) { |i| "example#{i}.com" } } end diff --git a/spec/requests/api/v1/admin/domain_allows_spec.rb b/spec/requests/api/v1/admin/domain_allows_spec.rb new file mode 100644 index 0000000000..eb7915e77a --- /dev/null +++ b/spec/requests/api/v1/admin/domain_allows_spec.rb @@ -0,0 +1,214 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Domain Allows' 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}" } } + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + describe 'GET /api/v1/admin/domain_allows' do + subject do + get '/api/v1/admin/domain_allows', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + 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 + + context 'when there is no allowed domains' do + it 'returns an empty body' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are allowed domains' do + let!(:domain_allows) { Fabricate.times(5, :domain_allow) } + let(:expected_response) do + domain_allows.map do |domain_allow| + { + id: domain_allow.id.to_s, + domain: domain_allow.domain, + created_at: domain_allow.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + } + end + end + + it 'returns the correct allowed domains' 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 allowed domains' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + end + end + + describe 'GET /api/v1/admin/domain_allows/:id' do + subject do + get "/api/v1/admin/domain_allows/#{domain_allow.id}", headers: headers + end + + let!(:domain_allow) { Fabricate(:domain_allow) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + 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 the expected allowed domain name' do + subject + + expect(body_as_json[:domain]).to eq domain_allow.domain + end + + context 'when the requested allowed domain does not exist' do + it 'returns http not found' do + get '/api/v1/admin/domain_allows/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/domain_allows' do + subject do + post '/api/v1/admin/domain_allows', headers: headers, params: params + end + + let(:params) { { domain: 'foo.bar.com' } } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + context 'with a valid domain name' do + it 'returns http success' 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 + + context 'with invalid domain name' do + let(:params) { 'foo bar' } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when domain name is not specified' do + let(:params) { {} } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the domain is already allowed' do + before do + DomainAllow.create(params) + end + + it 'returns the existing allowed domain name' do + subject + + expect(body_as_json[:domain]).to eq(params[:domain]) + end + end + end + + describe 'DELETE /api/v1/admin/domain_allows/:id' do + subject do + delete "/api/v1/admin/domain_allows/#{domain_allow.id}", headers: headers + end + + let!(:domain_allow) { Fabricate(:domain_allow) } + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + 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 'deletes the allowed domain' do + subject + + expect(DomainAllow.find_by(id: domain_allow.id)).to be_nil + end + + context 'when the allowed domain does not exist' do + it 'returns http not found' do + delete '/api/v1/admin/domain_allows/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end +end From b4e19f9610e97066b6f5881324d440d49c3fb31d Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Sat, 10 Jun 2023 13:32:46 -0300 Subject: [PATCH 17/27] Migrate to request specs in `/api/v1/admin/ip_blocks` (#25331) --- .../api/v1/admin/ip_blocks_controller_spec.rb | 309 ------------------ spec/requests/api/v1/admin/ip_blocks_spec.rb | 275 ++++++++++++++++ 2 files changed, 275 insertions(+), 309 deletions(-) delete mode 100644 spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb create mode 100644 spec/requests/api/v1/admin/ip_blocks_spec.rb diff --git a/spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb deleted file mode 100644 index a5787883ee..0000000000 --- a/spec/controllers/api/v1/admin/ip_blocks_controller_spec.rb +++ /dev/null @@ -1,309 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::Admin::IpBlocksController do - render_views - - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'admin:read:ip_blocks admin:write:ip_blocks' } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - shared_examples 'forbidden for wrong scope' do |wrong_scope| - let(:scopes) { wrong_scope } - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - shared_examples 'forbidden for wrong role' do |wrong_role| - let(:role) { UserRole.find_by(name: wrong_role) } - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - describe 'GET #index' do - context 'with wrong scope' do - before do - get :index - end - - it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks' - end - - context 'with wrong role' do - before do - get :index - end - - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end - - it 'returns http success' do - get :index - - expect(response).to have_http_status(200) - end - - context 'when there is no ip block' do - it 'returns an empty body' do - get :index - - json = body_as_json - - expect(json).to be_empty - end - end - - context 'when there are ip blocks' do - let!(:ip_blocks) do - [ - IpBlock.create(ip: '192.0.2.0/24', severity: :no_access), - IpBlock.create(ip: '172.16.0.1', severity: :sign_up_requires_approval, comment: 'Spam'), - IpBlock.create(ip: '2001:0db8::/32', severity: :sign_up_block, expires_in: 10.days), - ] - end - let(:expected_response) do - ip_blocks.map do |ip_block| - { - id: ip_block.id.to_s, - ip: ip_block.ip, - severity: ip_block.severity.to_s, - comment: ip_block.comment, - created_at: ip_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), - expires_at: ip_block.expires_at&.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), - } - end - end - - it 'returns the correct blocked ips' do - get :index - - json = body_as_json - - expect(json).to match_array(expected_response) - end - - context 'with limit param' do - let(:params) { { limit: 2 } } - - it 'returns only the requested number of ip blocks' do - get :index, params: params - - json = body_as_json - - expect(json.size).to eq(params[:limit]) - end - end - end - end - - describe 'GET #show' do - let!(:ip_block) { IpBlock.create(ip: '192.0.2.0/24', severity: :no_access) } - let(:params) { { id: ip_block.id } } - - context 'with wrong scope' do - before do - get :show, params: params - end - - it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks' - end - - context 'with wrong role' do - before do - get :show, params: params - end - - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end - - it 'returns http success' do - get :show, params: params - - expect(response).to have_http_status(200) - end - - it 'returns the correct ip block' do - get :show, params: params - - json = body_as_json - - expect(json[:ip]).to eq("#{ip_block.ip}/#{ip_block.ip.prefix}") - expect(json[:severity]).to eq(ip_block.severity.to_s) - end - - context 'when ip block does not exist' do - it 'returns http not found' do - get :show, params: { id: 0 } - - expect(response).to have_http_status(404) - end - end - end - - describe 'POST #create' do - let(:params) { { ip: '151.0.32.55', severity: 'no_access', comment: 'Spam' } } - - context 'with wrong scope' do - before do - post :create, params: params - end - - it_behaves_like 'forbidden for wrong scope', 'admin:read:ip_blocks' - end - - context 'with wrong role' do - before do - post :create, params: params - end - - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end - - it 'returns http success' do - post :create, params: params - - expect(response).to have_http_status(200) - end - - it 'returns the correct ip block' do - post :create, params: params - - json = body_as_json - - expect(json[:ip]).to eq("#{params[:ip]}/32") - expect(json[:severity]).to eq(params[:severity]) - expect(json[:comment]).to eq(params[:comment]) - end - - context 'when ip is not provided' do - let(:params) { { ip: '', severity: 'no_access' } } - - it 'returns http unprocessable entity' do - post :create, params: params - - expect(response).to have_http_status(422) - end - end - - context 'when severity is not provided' do - let(:params) { { ip: '173.65.23.1', severity: '' } } - - it 'returns http unprocessable entity' do - post :create, params: params - - expect(response).to have_http_status(422) - end - end - - context 'when provided ip is already blocked' do - before do - IpBlock.create(params) - end - - it 'returns http unprocessable entity' do - post :create, params: params - - expect(response).to have_http_status(422) - end - end - - context 'when provided ip address is invalid' do - let(:params) { { ip: '520.13.54.120', severity: 'no_access' } } - - it 'returns http unprocessable entity' do - post :create, params: params - - expect(response).to have_http_status(422) - end - end - end - - describe 'PUT #update' do - context 'when ip block exists' do - let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access', comment: 'Spam', expires_in: 48.hours) } - let(:params) { { id: ip_block.id, severity: 'sign_up_requires_approval', comment: 'Decreasing severity' } } - - it 'returns http success' do - put :update, params: params - - expect(response).to have_http_status(200) - end - - it 'returns the correct ip block' do - put :update, params: params - - json = body_as_json - - expect(json).to match(hash_including({ - ip: "#{ip_block.ip}/#{ip_block.ip.prefix}", - severity: 'sign_up_requires_approval', - comment: 'Decreasing severity', - })) - end - - it 'updates the severity correctly' do - expect { put :update, params: params }.to change { ip_block.reload.severity }.from('no_access').to('sign_up_requires_approval') - end - - it 'updates the comment correctly' do - expect { put :update, params: params }.to change { ip_block.reload.comment }.from('Spam').to('Decreasing severity') - end - end - - context 'when ip block does not exist' do - it 'returns http not found' do - put :update, params: { id: 0 } - - expect(response).to have_http_status(404) - end - end - end - - describe 'DELETE #destroy' do - context 'when ip block exists' do - let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access') } - let(:params) { { id: ip_block.id } } - - it 'returns http success' do - delete :destroy, params: params - - expect(response).to have_http_status(200) - end - - it 'returns an empty body' do - delete :destroy, params: params - - json = body_as_json - - expect(json).to be_empty - end - - it 'deletes the ip block' do - delete :destroy, params: params - - expect(IpBlock.find_by(id: ip_block.id)).to be_nil - end - end - - context 'when ip block does not exist' do - it 'returns http not found' do - delete :destroy, params: { id: 0 } - - expect(response).to have_http_status(404) - end - end - end -end diff --git a/spec/requests/api/v1/admin/ip_blocks_spec.rb b/spec/requests/api/v1/admin/ip_blocks_spec.rb new file mode 100644 index 0000000000..2091ef3dc6 --- /dev/null +++ b/spec/requests/api/v1/admin/ip_blocks_spec.rb @@ -0,0 +1,275 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'IP Blocks' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'admin:read:ip_blocks admin:write:ip_blocks' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + describe 'GET /api/v1/admin/ip_blocks' do + subject do + get '/api/v1/admin/ip_blocks', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks' + 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 + + context 'when there is no ip block' do + it 'returns an empty body' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are ip blocks' do + let!(:ip_blocks) do + [ + IpBlock.create(ip: '192.0.2.0/24', severity: :no_access), + IpBlock.create(ip: '172.16.0.1', severity: :sign_up_requires_approval, comment: 'Spam'), + IpBlock.create(ip: '2001:0db8::/32', severity: :sign_up_block, expires_in: 10.days), + ] + end + let(:expected_response) do + ip_blocks.map do |ip_block| + { + id: ip_block.id.to_s, + ip: ip_block.ip, + severity: ip_block.severity.to_s, + comment: ip_block.comment, + created_at: ip_block.created_at.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + expires_at: ip_block.expires_at&.strftime('%Y-%m-%dT%H:%M:%S.%LZ'), + } + end + end + + it 'returns the correct blocked ips' do + 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 ip blocks' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + end + end + + describe 'GET /api/v1/admin/ip_blocks/:id' do + subject do + get "/api/v1/admin/ip_blocks/#{ip_block.id}", headers: headers + end + + let!(:ip_block) { IpBlock.create(ip: '192.0.2.0/24', severity: :no_access) } + + it_behaves_like 'forbidden for wrong scope', 'admin:write:ip_blocks' + 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 the correct ip block' do + subject + + json = body_as_json + + expect(json[:ip]).to eq("#{ip_block.ip}/#{ip_block.ip.prefix}") + expect(json[:severity]).to eq(ip_block.severity.to_s) + end + + context 'when ip block does not exist' do + it 'returns http not found' do + get '/api/v1/admin/ip_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/ip_blocks' do + subject do + post '/api/v1/admin/ip_blocks', headers: headers, params: params + end + + let(:params) { { ip: '151.0.32.55', severity: 'no_access', comment: 'Spam' } } + + it_behaves_like 'forbidden for wrong scope', 'admin:read:ip_blocks' + 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 the correct ip block' do + subject + + json = body_as_json + + expect(json[:ip]).to eq("#{params[:ip]}/32") + expect(json[:severity]).to eq(params[:severity]) + expect(json[:comment]).to eq(params[:comment]) + end + + context 'when the required ip param is not provided' do + let(:params) { { ip: '', severity: 'no_access' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the required severity param is not provided' do + let(:params) { { ip: '173.65.23.1', severity: '' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the given ip address is already blocked' do + before do + IpBlock.create(params) + end + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the given ip address is invalid' do + let(:params) { { ip: '520.13.54.120', severity: 'no_access' } } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + describe 'PUT /api/v1/admin/ip_blocks/:id' do + subject do + put "/api/v1/admin/ip_blocks/#{ip_block.id}", headers: headers, params: params + end + + 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 + 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', + comment: 'Decreasing severity', + })) + end + + it 'updates the severity correctly' do + expect { subject }.to change { ip_block.reload.severity }.from('no_access').to('sign_up_requires_approval') + end + + it 'updates the comment correctly' do + expect { subject }.to change { ip_block.reload.comment }.from('Spam').to('Decreasing severity') + end + + context 'when ip block does not exist' do + it 'returns http not found' do + put '/api/v1/admin/ip_blocks/-1', headers: headers, params: params + + expect(response).to have_http_status(404) + end + end + end + + describe 'DELETE /api/v1/admin/ip_blocks/:id' do + subject do + delete "/api/v1/admin/ip_blocks/#{ip_block.id}", headers: headers + end + + let!(:ip_block) { IpBlock.create(ip: '185.200.13.3', severity: 'no_access') } + + it 'returns http success' 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 + + context 'when ip block does not exist' do + it 'returns http not found' do + delete '/api/v1/admin/ip_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end +end From 99216e34e5a309e51de1035a3ddccef82ef5d696 Mon Sep 17 00:00:00 2001 From: Daniel M Brasil Date: Sat, 10 Jun 2023 13:33:02 -0300 Subject: [PATCH 18/27] Migrate to request specs in `/api/v1/admin/canonical_email_blocks` (#25330) --- .../canonical_email_blocks_controller_spec.rb | 358 ------------------ .../v1/admin/canonical_email_blocks_spec.rb | 305 +++++++++++++++ 2 files changed, 305 insertions(+), 358 deletions(-) delete mode 100644 spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb create mode 100644 spec/requests/api/v1/admin/canonical_email_blocks_spec.rb diff --git a/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb b/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb deleted file mode 100644 index fe39596dfd..0000000000 --- a/spec/controllers/api/v1/admin/canonical_email_blocks_controller_spec.rb +++ /dev/null @@ -1,358 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Api::V1::Admin::CanonicalEmailBlocksController do - render_views - - let(:role) { UserRole.find_by(name: 'Admin') } - let(:user) { Fabricate(:user, role: role) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } - let(:scopes) { 'admin:read:canonical_email_blocks admin:write:canonical_email_blocks' } - - before do - allow(controller).to receive(:doorkeeper_token) { token } - end - - shared_examples 'forbidden for wrong scope' do |wrong_scope| - let(:scopes) { wrong_scope } - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - shared_examples 'forbidden for wrong role' do |wrong_role| - let(:role) { UserRole.find_by(name: wrong_role) } - - it 'returns http forbidden' do - expect(response).to have_http_status(403) - end - end - - describe 'GET #index' do - context 'with wrong scope' do - before do - get :index - end - - it_behaves_like 'forbidden for wrong scope', 'read:statuses' - end - - context 'with wrong role' do - before do - get :index - end - - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end - - it 'returns http success' do - get :index - - expect(response).to have_http_status(200) - end - - context 'when there is no canonical email block' do - it 'returns an empty list' do - get :index - - body = body_as_json - - expect(body).to be_empty - end - end - - context 'when there are canonical email blocks' do - let!(:canonical_email_blocks) { Fabricate.times(5, :canonical_email_block) } - let(:expected_email_hashes) { canonical_email_blocks.pluck(:canonical_email_hash) } - - it 'returns the correct canonical email hashes' do - get :index - - json = body_as_json - - expect(json.pluck(:canonical_email_hash)).to match_array(expected_email_hashes) - end - - context 'with limit param' do - let(:params) { { limit: 2 } } - - it 'returns only the requested number of canonical email blocks' do - get :index, params: params - - json = body_as_json - - expect(json.size).to eq(params[:limit]) - end - end - - context 'with since_id param' do - let(:params) { { since_id: canonical_email_blocks[1].id } } - - it 'returns only the canonical email blocks after since_id' do - get :index, params: params - - canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s) - json = body_as_json - - expect(json.pluck(:id)).to match_array(canonical_email_blocks_ids[2..]) - end - end - - context 'with max_id param' do - let(:params) { { max_id: canonical_email_blocks[3].id } } - - it 'returns only the canonical email blocks before max_id' do - get :index, params: params - - canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s) - json = body_as_json - - expect(json.pluck(:id)).to match_array(canonical_email_blocks_ids[..2]) - end - end - end - end - - describe 'GET #show' do - let!(:canonical_email_block) { Fabricate(:canonical_email_block) } - let(:params) { { id: canonical_email_block.id } } - - context 'with wrong scope' do - before do - get :show, params: params - end - - it_behaves_like 'forbidden for wrong scope', 'read:statuses' - end - - context 'with wrong role' do - before do - get :show, params: params - end - - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end - - context 'when canonical email block exists' do - it 'returns http success' do - get :show, params: params - - expect(response).to have_http_status(200) - end - - it 'returns canonical email block data correctly' do - get :show, params: params - - json = body_as_json - - expect(json[:id]).to eq(canonical_email_block.id.to_s) - expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) - end - end - - context 'when canonical block does not exist' do - it 'returns http not found' do - get :show, params: { id: 0 } - - expect(response).to have_http_status(404) - end - end - end - - describe 'POST #test' do - context 'with wrong scope' do - before do - post :test - end - - it_behaves_like 'forbidden for wrong scope', 'read:statuses' - end - - context 'with wrong role' do - before do - post :test, params: { email: 'whatever@email.com' } - end - - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end - - context 'when required email is not provided' do - it 'returns http bad request' do - post :test - - expect(response).to have_http_status(400) - end - end - - context 'when required email is provided' do - let(:params) { { email: 'example@email.com' } } - - context 'when there is a matching canonical email block' do - let!(:canonical_email_block) { CanonicalEmailBlock.create(params) } - - it 'returns http success' do - post :test, params: params - - expect(response).to have_http_status(200) - end - - it 'returns expected canonical email hash' do - post :test, params: params - - json = body_as_json - - expect(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 - post :test, params: params - - expect(response).to have_http_status(200) - end - - it 'returns an empty list' do - post :test, params: params - - json = body_as_json - - expect(json).to be_empty - end - end - end - end - - describe 'POST #create' do - let(:params) { { email: 'example@email.com' } } - let(:canonical_email_block) { CanonicalEmailBlock.new(email: params[:email]) } - - context 'with wrong scope' do - before do - post :create, params: params - end - - it_behaves_like 'forbidden for wrong scope', 'read:statuses' - end - - context 'with wrong role' do - before do - post :create, params: params - end - - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end - - it 'returns http success' do - post :create, params: params - - expect(response).to have_http_status(200) - end - - it 'returns canonical_email_hash correctly' do - post :create, params: params - - json = body_as_json - - expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) - end - - context 'when required email param is not provided' do - it 'returns http unprocessable entity' do - post :create - - expect(response).to have_http_status(422) - end - end - - context 'when canonical_email_hash param is provided instead of email' do - let(:params) { { canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } } - - it 'returns http success' do - post :create, params: params - - expect(response).to have_http_status(200) - end - - it 'returns correct canonical_email_hash' do - post :create, params: params - - json = body_as_json - - expect(json[:canonical_email_hash]).to eq(params[:canonical_email_hash]) - end - end - - context 'when both email and canonical_email_hash params are provided' do - let(:params) { { email: 'example@email.com', canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } } - - it 'returns http success' do - post :create, params: params - - expect(response).to have_http_status(200) - end - - it 'ignores canonical_email_hash param' do - post :create, params: params - - json = body_as_json - - expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) - end - end - - context 'when canonical email was already blocked' do - before do - canonical_email_block.save - end - - it 'returns http unprocessable entity' do - post :create, params: params - - expect(response).to have_http_status(422) - end - end - end - - describe 'DELETE #destroy' do - let!(:canonical_email_block) { Fabricate(:canonical_email_block) } - let(:params) { { id: canonical_email_block.id } } - - context 'with wrong scope' do - before do - delete :destroy, params: params - end - - it_behaves_like 'forbidden for wrong scope', 'read:statuses' - end - - context 'with wrong role' do - before do - delete :destroy, params: params - end - - it_behaves_like 'forbidden for wrong role', '' - it_behaves_like 'forbidden for wrong role', 'Moderator' - end - - it 'returns http success' do - delete :destroy, params: params - - expect(response).to have_http_status(200) - end - - context 'when canonical email block is not found' do - it 'returns http not found' do - delete :destroy, params: { id: 0 } - - expect(response).to have_http_status(404) - end - end - end -end diff --git a/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb new file mode 100644 index 0000000000..d70e6fc8a1 --- /dev/null +++ b/spec/requests/api/v1/admin/canonical_email_blocks_spec.rb @@ -0,0 +1,305 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Canonical Email Blocks' do + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:scopes) { 'admin:read:canonical_email_blocks admin:write:canonical_email_blocks' } + let(:headers) { { 'Authorization' => "Bearer #{token.token}" } } + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + + describe 'GET /api/v1/admin/canonical_email_blocks' do + subject do + get '/api/v1/admin/canonical_email_blocks', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + 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 + + context 'when there is no canonical email block' do + it 'returns an empty list' do + subject + + expect(body_as_json).to be_empty + end + end + + context 'when there are canonical email blocks' do + let!(:canonical_email_blocks) { Fabricate.times(5, :canonical_email_block) } + let(:expected_email_hashes) { canonical_email_blocks.pluck(:canonical_email_hash) } + + it 'returns the correct canonical email hashes' do + subject + + expect(body_as_json.pluck(:canonical_email_hash)).to match_array(expected_email_hashes) + end + + context 'with limit param' do + let(:params) { { limit: 2 } } + + it 'returns only the requested number of canonical email blocks' do + subject + + expect(body_as_json.size).to eq(params[:limit]) + end + end + + context 'with since_id param' do + let(:params) { { since_id: canonical_email_blocks[1].id } } + + it 'returns only the canonical email blocks after since_id' do + subject + + canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s) + + expect(body_as_json.pluck(:id)).to match_array(canonical_email_blocks_ids[2..]) + end + end + + context 'with max_id param' do + let(:params) { { max_id: canonical_email_blocks[3].id } } + + it 'returns only the canonical email blocks before max_id' do + subject + + canonical_email_blocks_ids = canonical_email_blocks.pluck(:id).map(&:to_s) + + expect(body_as_json.pluck(:id)).to match_array(canonical_email_blocks_ids[..2]) + end + end + end + end + + describe 'GET /api/v1/admin/canonical_email_blocks/:id' do + subject do + get "/api/v1/admin/canonical_email_blocks/#{canonical_email_block.id}", headers: headers + end + + let!(:canonical_email_block) { Fabricate(:canonical_email_block) } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + context 'when the requested canonical email block exists' do + it 'returns http success' 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) + expect(json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) + end + end + + context 'when the requested canonical block does not exist' do + it 'returns http not found' do + get '/api/v1/admin/canonical_email_blocks/-1', headers: headers + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /api/v1/admin/canonical_email_blocks/test' do + subject do + post '/api/v1/admin/canonical_email_blocks/test', headers: headers, params: params + end + + let(:params) { { email: 'email@example.com' } } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + it_behaves_like 'forbidden for wrong role', '' + it_behaves_like 'forbidden for wrong role', 'Moderator' + + context 'when the required email param is not provided' do + let(:params) { {} } + + it 'returns http bad request' do + subject + + expect(response).to have_http_status(400) + end + end + + context 'when the required email param is provided' do + context 'when there is a matching canonical email block' do + let!(:canonical_email_block) { CanonicalEmailBlock.create(params) } + + it 'returns http success' 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 + 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 + end + end + + describe 'POST /api/v1/admin/canonical_email_blocks' do + subject do + post '/api/v1/admin/canonical_email_blocks', headers: headers, params: params + end + + let(:params) { { email: 'example@email.com' } } + let(:canonical_email_block) { CanonicalEmailBlock.new(email: params[:email]) } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + 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 the canonical_email_hash correctly' do + subject + + expect(body_as_json[:canonical_email_hash]).to eq(canonical_email_block.canonical_email_hash) + end + + context 'when the required email param is not provided' do + let(:params) { {} } + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + + context 'when the canonical_email_hash param is provided instead of email' do + let(:params) { { canonical_email_hash: 'dd501ce4e6b08698f19df96f2f15737e48a75660b1fa79b6ff58ea25ee4851a4' } } + + it 'returns http success' 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 + + 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 + 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 + + context 'when the given canonical email was already blocked' do + before do + canonical_email_block.save + end + + it 'returns http unprocessable entity' do + subject + + expect(response).to have_http_status(422) + end + end + end + + describe 'DELETE /api/v1/admin/canonical_email_blocks/:id' do + subject do + delete "/api/v1/admin/canonical_email_blocks/#{canonical_email_block.id}", headers: headers + end + + let!(:canonical_email_block) { Fabricate(:canonical_email_block) } + + it_behaves_like 'forbidden for wrong scope', 'read:statuses' + + 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 'deletes the canonical email block' do + subject + + expect(CanonicalEmailBlock.find_by(id: canonical_email_block.id)).to be_nil + end + + context 'when the canonical email block is not found' do + it 'returns http not found' do + delete '/api/v1/admin/canonical_email_blocks/0', headers: headers + + expect(response).to have_http_status(404) + end + end + end +end From b19a69560829eaeca1e17c542b33b9f29807d94c Mon Sep 17 00:00:00 2001 From: fusagiko / takayamaki <24884114+takayamaki@users.noreply.github.com> Date: Sun, 11 Jun 2023 01:33:24 +0900 Subject: [PATCH 19/27] migrate test for `GET /api/v1/accounts/{account_id}` to request spec (#25322) --- .../api/v1/accounts_controller_spec.rb | 14 ----- spec/requests/api/v1/accounts_show_spec.rb | 53 +++++++++++++++++++ 2 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 spec/requests/api/v1/accounts_show_spec.rb diff --git a/spec/controllers/api/v1/accounts_controller_spec.rb b/spec/controllers/api/v1/accounts_controller_spec.rb index 992fb0e893..49d2867745 100644 --- a/spec/controllers/api/v1/accounts_controller_spec.rb +++ b/spec/controllers/api/v1/accounts_controller_spec.rb @@ -55,20 +55,6 @@ RSpec.describe Api::V1::AccountsController do end end - describe 'GET #show' do - let(:scopes) { 'read:accounts' } - - before do - get :show, params: { id: user.account.id } - end - - it 'returns http success' do - expect(response).to have_http_status(200) - end - - it_behaves_like 'forbidden for wrong scope', 'write:statuses' - end - describe 'POST #follow' do let(:scopes) { 'write:follows' } let(:other_account) { Fabricate(:account, username: 'bob', locked: locked) } diff --git a/spec/requests/api/v1/accounts_show_spec.rb b/spec/requests/api/v1/accounts_show_spec.rb new file mode 100644 index 0000000000..ee6e925aa9 --- /dev/null +++ b/spec/requests/api/v1/accounts_show_spec.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe 'GET /api/v1/accounts/{account_id}' do + it 'returns account entity as 200 OK' do + account = Fabricate(:account) + + get "/api/v1/accounts/#{account.id}" + + aggregate_failures do + expect(response).to have_http_status(200) + expect(body_as_json[:id]).to eq(account.id.to_s) + end + end + + it 'returns 404 if account not found' do + get '/api/v1/accounts/1' + + aggregate_failures do + expect(response).to have_http_status(404) + expect(body_as_json[:error]).to eq('Record not found') + end + end + + context 'when with token' do + it 'returns account entity as 200 OK if token is valid' do + account = Fabricate(:account) + user = Fabricate(:user, account: account) + token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts').token + + get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" } + + aggregate_failures do + expect(response).to have_http_status(200) + expect(body_as_json[:id]).to eq(account.id.to_s) + end + end + + it 'returns 403 if scope of token is invalid' do + account = Fabricate(:account) + user = Fabricate(:user, account: account) + token = Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'write:statuses').token + + get "/api/v1/accounts/#{account.id}", headers: { Authorization: "Bearer #{token}" } + + aggregate_failures do + expect(response).to have_http_status(403) + expect(body_as_json[:error]).to eq('This action is outside the authorized scopes') + end + end + end +end From 215081240f367ee39f785443a9200dfff4dfff57 Mon Sep 17 00:00:00 2001 From: Emelia Smith Date: Sat, 10 Jun 2023 18:35:57 +0200 Subject: [PATCH 20/27] Add logging of websocket send errors (#25280) --- streaming/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/streaming/index.js b/streaming/index.js index 9b43112d22..aabd2f8559 100644 --- a/streaming/index.js +++ b/streaming/index.js @@ -828,7 +828,11 @@ const startServer = async () => { return; } - ws.send(JSON.stringify({ stream: streamName, event, payload })); + ws.send(JSON.stringify({ stream: streamName, event, payload }), (err) => { + if (err) { + log.error(req.requestId, `Failed to send to websocket: ${err}`); + } + }); }; /** From 07933db7881843f7378b6a02692e1750dd522f69 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Sat, 10 Jun 2023 12:36:09 -0400 Subject: [PATCH 21/27] Add coverage for `CLI::Cache` command (#25238) --- lib/mastodon/cli/cache.rb | 48 +++++++++++++----- spec/fabricators/status_stat_fabricator.rb | 8 +++ spec/lib/mastodon/cli/cache_spec.rb | 59 ++++++++++++++++++++++ spec/rails_helper.rb | 12 +++++ 4 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 spec/fabricators/status_stat_fabricator.rb diff --git a/lib/mastodon/cli/cache.rb b/lib/mastodon/cli/cache.rb index 105d4b3c32..e8a2ac1610 100644 --- a/lib/mastodon/cli/cache.rb +++ b/lib/mastodon/cli/cache.rb @@ -23,22 +23,12 @@ module Mastodon::CLI def recount(type) case type when 'accounts' - processed, = parallelize_with_progress(Account.local.includes(:account_stat)) do |account| - account_stat = account.account_stat - account_stat.following_count = account.active_relationships.count - account_stat.followers_count = account.passive_relationships.count - account_stat.statuses_count = account.statuses.where.not(visibility: :direct).count - - account_stat.save if account_stat.changed? + processed, = parallelize_with_progress(accounts_with_stats) do |account| + recount_account_stats(account) end when 'statuses' - processed, = parallelize_with_progress(Status.includes(:status_stat)) do |status| - status_stat = status.status_stat - status_stat.replies_count = status.replies.where.not(visibility: :direct).count - status_stat.reblogs_count = status.reblogs.count - status_stat.favourites_count = status.favourites.count - - status_stat.save if status_stat.changed? + processed, = parallelize_with_progress(statuses_with_stats) do |status| + recount_status_stats(status) end else say("Unknown type: #{type}", :red) @@ -48,5 +38,35 @@ module Mastodon::CLI say say("OK, recounted #{processed} records", :green) end + + private + + def accounts_with_stats + Account.local.includes(:account_stat) + end + + def statuses_with_stats + Status.includes(:status_stat) + end + + def recount_account_stats(account) + account.account_stat.tap do |account_stat| + account_stat.following_count = account.active_relationships.count + account_stat.followers_count = account.passive_relationships.count + account_stat.statuses_count = account.statuses.where.not(visibility: :direct).count + + account_stat.save if account_stat.changed? + end + end + + def recount_status_stats(status) + status.status_stat.tap do |status_stat| + status_stat.replies_count = status.replies.where.not(visibility: :direct).count + status_stat.reblogs_count = status.reblogs.count + status_stat.favourites_count = status.favourites.count + + status_stat.save if status_stat.changed? + end + end end end diff --git a/spec/fabricators/status_stat_fabricator.rb b/spec/fabricators/status_stat_fabricator.rb new file mode 100644 index 0000000000..715e6d4ab2 --- /dev/null +++ b/spec/fabricators/status_stat_fabricator.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +Fabricator(:status_stat) do + status + replies_count '123' + reblogs_count '456' + favourites_count '789' +end diff --git a/spec/lib/mastodon/cli/cache_spec.rb b/spec/lib/mastodon/cli/cache_spec.rb index f101bc200f..3ab42dc8ce 100644 --- a/spec/lib/mastodon/cli/cache_spec.rb +++ b/spec/lib/mastodon/cli/cache_spec.rb @@ -4,9 +4,68 @@ require 'rails_helper' require 'mastodon/cli/cache' describe Mastodon::CLI::Cache 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 '#clear' do + before { allow(Rails.cache).to receive(:clear) } + + it 'clears the Rails cache' do + expect { cli.invoke(:clear) }.to output( + a_string_including('OK') + ).to_stdout + expect(Rails.cache).to have_received(:clear) + end + end + + describe '#recount' do + context 'with the `accounts` argument' do + let(:arguments) { ['accounts'] } + let(:account_stat) { Fabricate(:account_stat) } + + before do + account_stat.update(statuses_count: 123) + end + + it 're-calculates account records in the cache' do + expect { cli.invoke(:recount, arguments) }.to output( + a_string_including('OK') + ).to_stdout + + expect(account_stat.reload.statuses_count).to be_zero + end + end + + context 'with the `statuses` argument' do + let(:arguments) { ['statuses'] } + let(:status_stat) { Fabricate(:status_stat) } + + before do + status_stat.update(replies_count: 123) + end + + it 're-calculates account records in the cache' do + expect { cli.invoke(:recount, arguments) }.to output( + a_string_including('OK') + ).to_stdout + + expect(status_stat.reload.replies_count).to be_zero + end + end + + context 'with an unknown type' do + let(:arguments) { ['other-type'] } + + it 'Exits with an error message' do + expect { cli.invoke(:recount, arguments) }.to output( + a_string_including('Unknown') + ).to_stdout.and raise_error(SystemExit) + end + end + end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index f4113d565f..2645f74e40 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -79,6 +79,7 @@ RSpec.configure do |config| config.before :each, type: :cli do stub_stdout + stub_reset_connection_pools end config.before :each, type: :feature do @@ -121,9 +122,20 @@ def attachment_fixture(name) end def stub_stdout + # TODO: Is there a bettery way to: + # - Avoid CLI command output being printed out + # - Allow rspec to assert things against STDOUT + # - Avoid disabling stdout for other desirable output (deprecation warnings, for example) allow($stdout).to receive(:write) end +def stub_reset_connection_pools + # TODO: Is there a better way to correctly run specs without stubbing this? + # (Avoids reset_connection_pools! in test env) + 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')) From b5675e265e95e043018140cb3b23fe19b1608eff Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Sat, 10 Jun 2023 12:37:36 -0400 Subject: [PATCH 22/27] Add coverage for `CLI::Feeds` command (#25319) --- lib/mastodon/cli/feeds.rb | 8 ++++- spec/lib/mastodon/cli/feeds_spec.rb | 56 +++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/mastodon/cli/feeds.rb b/lib/mastodon/cli/feeds.rb index 34617e7538..3467dd427c 100644 --- a/lib/mastodon/cli/feeds.rb +++ b/lib/mastodon/cli/feeds.rb @@ -19,7 +19,7 @@ module Mastodon::CLI LONG_DESC def build(username = nil) if options[:all] || username.nil? - processed, = parallelize_with_progress(Account.joins(:user).merge(User.active)) do |account| + processed, = parallelize_with_progress(active_user_accounts) do |account| PrecomputeFeedService.new.call(account) unless dry_run? end @@ -47,5 +47,11 @@ module Mastodon::CLI redis.del(keys) say('OK', :green) end + + private + + def active_user_accounts + Account.joins(:user).merge(User.active) + end end end diff --git a/spec/lib/mastodon/cli/feeds_spec.rb b/spec/lib/mastodon/cli/feeds_spec.rb index 4e1e214eff..030f087212 100644 --- a/spec/lib/mastodon/cli/feeds_spec.rb +++ b/spec/lib/mastodon/cli/feeds_spec.rb @@ -4,9 +4,65 @@ require 'rails_helper' require 'mastodon/cli/feeds' describe Mastodon::CLI::Feeds 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 '#build' do + before { Fabricate(:account) } + + context 'with --all option' do + let(:options) { { all: true } } + + it 'regenerates feeds for all accounts' do + expect { cli.invoke(:build, [], options) }.to output( + a_string_including('Regenerated feeds') + ).to_stdout + end + end + + context 'with a username' do + before { Fabricate(:account, username: 'alice') } + + let(:arguments) { ['alice'] } + + it 'regenerates feeds for the account' do + expect { cli.invoke(:build, arguments) }.to output( + a_string_including('OK') + ).to_stdout + end + end + + context 'with invalid username' do + let(:arguments) { ['invalid-username'] } + + it 'displays an error and exits' do + expect { cli.invoke(:build, arguments) }.to output( + a_string_including('No such account') + ).to_stdout.and raise_error(SystemExit) + end + end + end + + describe '#clear' do + before do + allow(redis).to receive(:del).with(key_namespace) + end + + it 'clears the redis `feed:*` namespace' do + expect { cli.invoke(:clear) }.to output( + a_string_including('OK') + ).to_stdout + + expect(redis).to have_received(:del).with(key_namespace).once + end + + def key_namespace + redis.keys('feed:*') + end + end end From 62c996b52d4dc50e5713b8e7657fc5eaa52f39c8 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Sat, 10 Jun 2023 12:38:22 -0400 Subject: [PATCH 23/27] Reduce `RSpec/MultipleExpectations` cop max to 8 (#25313) --- .rubocop_todo.yml | 2 +- .../accounts/relationships_controller_spec.rb | 41 +++--- .../confirmations_controller_spec.rb | 31 +++-- spec/models/form/import_spec.rb | 49 +++++-- spec/models/notification_spec.rb | 120 ++++++++++-------- .../services/translate_status_service_spec.rb | 35 +++-- 6 files changed, 172 insertions(+), 106 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d1996ca74d..45593104dd 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -394,7 +394,7 @@ RSpec/MessageSpies: - 'spec/validators/status_length_validator_spec.rb' RSpec/MultipleExpectations: - Max: 19 + Max: 8 # Configuration parameters: AllowSubject. RSpec/MultipleMemoizedHelpers: diff --git a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb index 6bc07fa9e0..993ead636a 100644 --- a/spec/controllers/api/v1/accounts/relationships_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/relationships_controller_spec.rb @@ -48,25 +48,32 @@ describe Api::V1::Accounts::RelationshipsController do expect(response).to have_http_status(200) end - it 'returns JSON with correct data' do - json = body_as_json + context 'when there is returned JSON data' do + let(:json) { body_as_json } - expect(json).to be_a Enumerable - 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 - expect(json.first[:followed_by]).to be false - expect(json.first[:muting]).to be false - expect(json.first[:requested]).to be false - expect(json.first[:domain_blocking]).to be false + it 'returns an enumerable json' do + expect(json).to be_a Enumerable + end - 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 - expect(json.second[:followed_by]).to be true - expect(json.second[:muting]).to be false - expect(json.second[:requested]).to be false - expect(json.second[:domain_blocking]).to be false + it 'returns a correct first element' do + 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 + expect(json.first[:followed_by]).to be false + expect(json.first[:muting]).to be false + expect(json.first[:requested]).to be false + expect(json.first[:domain_blocking]).to be false + end + + it 'returns a correct second element' do + 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 + expect(json.second[:followed_by]).to be true + expect(json.second[:muting]).to be false + expect(json.second[:requested]).to be false + expect(json.second[:domain_blocking]).to be false + end end it 'returns JSON with correct data on cached requests too' do diff --git a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb index 0b807b280f..84dfd60b38 100644 --- a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb @@ -56,18 +56,11 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do end describe 'when creation succeeds' do + let!(:otp_backup_codes) { user.generate_otp_backup_codes! } + it 'renders page with success' do - otp_backup_codes = user.generate_otp_backup_codes! - expect_any_instance_of(User).to receive(:generate_otp_backup_codes!) do |value| - expect(value).to eq user - otp_backup_codes - end - expect_any_instance_of(User).to receive(:validate_and_consume_otp!) do |value, code, options| - expect(value).to eq user - expect(code).to eq '123456' - expect(options).to eq({ otp_secret: 'thisisasecretforthespecofnewview' }) - true - end + prepare_user_otp_generation + prepare_user_otp_consumption expect do post :create, @@ -80,6 +73,22 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do expect(response).to have_http_status(200) expect(response).to render_template('settings/two_factor_authentication/recovery_codes/index') end + + def prepare_user_otp_generation + expect_any_instance_of(User).to receive(:generate_otp_backup_codes!) do |value| + expect(value).to eq user + otp_backup_codes + end + end + + def prepare_user_otp_consumption + expect_any_instance_of(User).to receive(:validate_and_consume_otp!) do |value, code, options| + expect(value).to eq user + expect(code).to eq '123456' + expect(options).to eq({ otp_secret: 'thisisasecretforthespecofnewview' }) + true + end + end end describe 'when creation fails' do diff --git a/spec/models/form/import_spec.rb b/spec/models/form/import_spec.rb index 52cf1c96ed..2b70e396b9 100644 --- a/spec/models/form/import_spec.rb +++ b/spec/models/form/import_spec.rb @@ -245,17 +245,44 @@ RSpec.describe Form::Import do expect(account.bulk_imports.first.rows.pluck(:data)).to match_array(expected_rows) end - it 'creates a BulkImport with expected attributes' do - bulk_import = account.bulk_imports.first - expect(bulk_import).to_not be_nil - expect(bulk_import.type.to_sym).to eq subject.type.to_sym - expect(bulk_import.original_filename).to eq subject.data.original_filename - expect(bulk_import.likely_mismatched?).to eq subject.likely_mismatched? - expect(bulk_import.overwrite?).to eq !!subject.overwrite # rubocop:disable Style/DoubleNegation - expect(bulk_import.processed_items).to eq 0 - expect(bulk_import.imported_items).to eq 0 - expect(bulk_import.total_items).to eq bulk_import.rows.count - expect(bulk_import.unconfirmed?).to be true + context 'with a BulkImport' do + let(:bulk_import) { account.bulk_imports.first } + + it 'creates a non-nil bulk import' do + expect(bulk_import).to_not be_nil + end + + it 'matches the subjects type' do + expect(bulk_import.type.to_sym).to eq subject.type.to_sym + end + + it 'matches the subjects original filename' do + expect(bulk_import.original_filename).to eq subject.data.original_filename + end + + it 'matches the subjects likely_mismatched? value' do + expect(bulk_import.likely_mismatched?).to eq subject.likely_mismatched? + end + + it 'matches the subject overwrite value' do + expect(bulk_import.overwrite?).to eq !!subject.overwrite # rubocop:disable Style/DoubleNegation + end + + it 'has zero processed items' do + expect(bulk_import.processed_items).to eq 0 + end + + it 'has zero imported items' do + expect(bulk_import.imported_items).to eq 0 + end + + it 'has a correct total_items value' do + expect(bulk_import.total_items).to eq bulk_import.rows.count + end + + it 'defaults to unconfirmed true' do + expect(bulk_import.unconfirmed?).to be true + end end end diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 0dd9264e00..d6e2282022 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -99,73 +99,87 @@ RSpec.describe Notification do ] end - it 'preloads target status' do - # mention - expect(subject[0].type).to eq :mention - expect(subject[0].association(:mention)).to be_loaded - expect(subject[0].mention.association(:status)).to be_loaded + context 'with a preloaded target status' do + it 'preloads mention' do + expect(subject[0].type).to eq :mention + expect(subject[0].association(:mention)).to be_loaded + expect(subject[0].mention.association(:status)).to be_loaded + end - # status - expect(subject[1].type).to eq :status - expect(subject[1].association(:status)).to be_loaded + it 'preloads status' do + expect(subject[1].type).to eq :status + expect(subject[1].association(:status)).to be_loaded + end - # reblog - expect(subject[2].type).to eq :reblog - expect(subject[2].association(:status)).to be_loaded - expect(subject[2].status.association(:reblog)).to be_loaded + it 'preloads reblog' do + expect(subject[2].type).to eq :reblog + expect(subject[2].association(:status)).to be_loaded + expect(subject[2].status.association(:reblog)).to be_loaded + end - # follow: nothing - expect(subject[3].type).to eq :follow - expect(subject[3].target_status).to be_nil + it 'preloads follow as nil' do + expect(subject[3].type).to eq :follow + expect(subject[3].target_status).to be_nil + end - # follow_request: nothing - expect(subject[4].type).to eq :follow_request - expect(subject[4].target_status).to be_nil + it 'preloads follow_request as nill' do + expect(subject[4].type).to eq :follow_request + expect(subject[4].target_status).to be_nil + end - # favourite - expect(subject[5].type).to eq :favourite - expect(subject[5].association(:favourite)).to be_loaded - expect(subject[5].favourite.association(:status)).to be_loaded + it 'preloads favourite' do + expect(subject[5].type).to eq :favourite + expect(subject[5].association(:favourite)).to be_loaded + expect(subject[5].favourite.association(:status)).to be_loaded + end - # poll - expect(subject[6].type).to eq :poll - expect(subject[6].association(:poll)).to be_loaded - expect(subject[6].poll.association(:status)).to be_loaded + it 'preloads poll' do + expect(subject[6].type).to eq :poll + expect(subject[6].association(:poll)).to be_loaded + expect(subject[6].poll.association(:status)).to be_loaded + end end - it 'replaces to cached status' do - # mention - expect(subject[0].type).to eq :mention - expect(subject[0].target_status.association(:account)).to be_loaded - expect(subject[0].target_status).to eq mention.status + context 'with a cached status' do + it 'replaces mention' do + expect(subject[0].type).to eq :mention + expect(subject[0].target_status.association(:account)).to be_loaded + expect(subject[0].target_status).to eq mention.status + end - # status - expect(subject[1].type).to eq :status - expect(subject[1].target_status.association(:account)).to be_loaded - expect(subject[1].target_status).to eq status + it 'replaces status' do + expect(subject[1].type).to eq :status + expect(subject[1].target_status.association(:account)).to be_loaded + expect(subject[1].target_status).to eq status + end - # reblog - expect(subject[2].type).to eq :reblog - expect(subject[2].target_status.association(:account)).to be_loaded - expect(subject[2].target_status).to eq reblog.reblog + it 'replaces reblog' do + expect(subject[2].type).to eq :reblog + expect(subject[2].target_status.association(:account)).to be_loaded + expect(subject[2].target_status).to eq reblog.reblog + end - # follow: nothing - expect(subject[3].type).to eq :follow - expect(subject[3].target_status).to be_nil + it 'replaces follow' do + expect(subject[3].type).to eq :follow + expect(subject[3].target_status).to be_nil + end - # follow_request: nothing - expect(subject[4].type).to eq :follow_request - expect(subject[4].target_status).to be_nil + it 'replaces follow_request' do + expect(subject[4].type).to eq :follow_request + expect(subject[4].target_status).to be_nil + end - # favourite - expect(subject[5].type).to eq :favourite - expect(subject[5].target_status.association(:account)).to be_loaded - expect(subject[5].target_status).to eq favourite.status + it 'replaces favourite' do + expect(subject[5].type).to eq :favourite + expect(subject[5].target_status.association(:account)).to be_loaded + expect(subject[5].target_status).to eq favourite.status + end - # poll - expect(subject[6].type).to eq :poll - expect(subject[6].target_status.association(:account)).to be_loaded - expect(subject[6].target_status).to eq poll.status + it 'replaces poll' do + expect(subject[6].type).to eq :poll + expect(subject[6].target_status.association(:account)).to be_loaded + expect(subject[6].target_status).to eq poll.status + end end end end diff --git a/spec/services/translate_status_service_spec.rb b/spec/services/translate_status_service_spec.rb index 074f55544a..515dd1a997 100644 --- a/spec/services/translate_status_service_spec.rb +++ b/spec/services/translate_status_service_spec.rb @@ -152,22 +152,31 @@ RSpec.describe TranslateStatusService, type: :service do describe 'status has poll' do let(:poll) { Fabricate(:poll, options: %w(Blue Green)) } - it 'returns formatted poll options' do - source_texts = service.send(:source_texts) - expect(source_texts.size).to eq 3 - expect(source_texts.values).to eq %w(

Hello

Blue Green) + context 'with source texts from the service' do + let!(:source_texts) { service.send(:source_texts) } - expect(source_texts.keys.first).to eq :content + it 'returns formatted poll options' do + expect(source_texts.size).to eq 3 + expect(source_texts.values).to eq %w(

Hello

Blue Green) + end - option1 = source_texts.keys.second - expect(option1).to be_a Poll::Option - expect(option1.id).to eq '0' - expect(option1.title).to eq 'Blue' + it 'has a first key with content' do + expect(source_texts.keys.first).to eq :content + end - option2 = source_texts.keys.third - expect(option2).to be_a Poll::Option - expect(option2.id).to eq '1' - expect(option2.title).to eq 'Green' + it 'has the first option in the second key with correct options' do + option1 = source_texts.keys.second + expect(option1).to be_a Poll::Option + expect(option1.id).to eq '0' + expect(option1.title).to eq 'Blue' + end + + it 'has the second option in the third key with correct options' do + option2 = source_texts.keys.third + expect(option2).to be_a Poll::Option + expect(option2.id).to eq '1' + expect(option2.title).to eq 'Green' + end end end From 432a5d2d4bc307d9a9c7b484de96d3eb7926fa93 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Sun, 11 Jun 2023 04:47:07 +0200 Subject: [PATCH 24/27] Change "bot" label to "automated" (#25356) --- app/javascript/mastodon/features/account/components/header.jsx | 2 +- app/javascript/mastodon/locales/en.json | 2 +- config/locales/simple_form.en.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index a206bcc3ba..5eea1abf04 100644 --- a/app/javascript/mastodon/features/account/components/header.jsx +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -374,7 +374,7 @@ class Header extends ImmutablePureComponent { let badge; if (account.get('bot')) { - badge = (
); + badge = (
); } else if (account.get('group')) { badge = (
); } else { diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index ef0964b192..6589d46746 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -13,7 +13,7 @@ "about.rules": "Server rules", "account.account_note_header": "Note", "account.add_or_remove_from_list": "Add or Remove from lists", - "account.badges.bot": "Bot", + "account.badges.bot": "Automated", "account.badges.group": "Group", "account.block": "Block @{name}", "account.block_domain": "Block domain {domain}", diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 330c8732ff..a99a50e993 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -168,7 +168,7 @@ en: defaults: autofollow: Invite to follow your account avatar: Avatar - bot: This is a bot account + bot: This is an automated account chosen_languages: Filter languages confirm_new_password: Confirm new password confirm_password: Confirm password From dfaf59d99a2aa1f913608faee978bc3e1586a3ce Mon Sep 17 00:00:00 2001 From: alfe Date: Sun, 11 Jun 2023 11:47:18 +0900 Subject: [PATCH 25/27] Rewrite as FC and TS (#25363) --- .../mastodon/components/load_pending.jsx | 23 ------------------- .../mastodon/components/load_pending.tsx | 18 +++++++++++++++ .../mastodon/components/scrollable_list.jsx | 2 +- 3 files changed, 19 insertions(+), 24 deletions(-) delete mode 100644 app/javascript/mastodon/components/load_pending.jsx create mode 100644 app/javascript/mastodon/components/load_pending.tsx diff --git a/app/javascript/mastodon/components/load_pending.jsx b/app/javascript/mastodon/components/load_pending.jsx deleted file mode 100644 index e9c1a97836..0000000000 --- a/app/javascript/mastodon/components/load_pending.jsx +++ /dev/null @@ -1,23 +0,0 @@ -import PropTypes from 'prop-types'; -import { PureComponent } from 'react'; - -import { FormattedMessage } from 'react-intl'; - -export default class LoadPending extends PureComponent { - - static propTypes = { - onClick: PropTypes.func, - count: PropTypes.number, - }; - - render() { - const { count } = this.props; - - return ( - - ); - } - -} diff --git a/app/javascript/mastodon/components/load_pending.tsx b/app/javascript/mastodon/components/load_pending.tsx new file mode 100644 index 0000000000..f7589622ed --- /dev/null +++ b/app/javascript/mastodon/components/load_pending.tsx @@ -0,0 +1,18 @@ +import { FormattedMessage } from 'react-intl'; + +interface Props { + onClick: (event: React.MouseEvent) => void; + count: number; +} + +export const LoadPending: React.FC = ({ onClick, count }) => { + return ( + + ); +}; diff --git a/app/javascript/mastodon/components/scrollable_list.jsx b/app/javascript/mastodon/components/scrollable_list.jsx index 53a84ecb53..5c990d6dca 100644 --- a/app/javascript/mastodon/components/scrollable_list.jsx +++ b/app/javascript/mastodon/components/scrollable_list.jsx @@ -16,7 +16,7 @@ import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from import IntersectionObserverWrapper from '../features/ui/util/intersection_observer_wrapper'; import { LoadMore } from './load_more'; -import LoadPending from './load_pending'; +import { LoadPending } from './load_pending'; import LoadingIndicator from './loading_indicator'; const MOUSE_IDLE_DELAY = 300; From 6637ef7852fb5cb341b971807a76a5b013300d22 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 12 Jun 2023 14:22:46 +0200 Subject: [PATCH 26/27] Add unsubscribe link to e-mails (#25378) --- .../mail_subscriptions_controller.rb | 41 +++++++++++++++++++ app/mailers/notification_mailer.rb | 30 +++++++++----- app/views/layouts/mailer.html.haml | 6 ++- app/views/mail_subscriptions/create.html.haml | 9 ++++ app/views/mail_subscriptions/show.html.haml | 12 ++++++ config/i18n-tasks.yml | 1 + config/locales/en.yml | 16 ++++++++ config/routes.rb | 2 + 8 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 app/controllers/mail_subscriptions_controller.rb create mode 100644 app/views/mail_subscriptions/create.html.haml create mode 100644 app/views/mail_subscriptions/show.html.haml diff --git a/app/controllers/mail_subscriptions_controller.rb b/app/controllers/mail_subscriptions_controller.rb new file mode 100644 index 0000000000..b071a80605 --- /dev/null +++ b/app/controllers/mail_subscriptions_controller.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class MailSubscriptionsController < ApplicationController + layout 'auth' + + skip_before_action :require_functional! + + before_action :set_body_classes + before_action :set_user + before_action :set_type + + def show; end + + def create + @user.settings[email_type_from_param] = false + @user.save! + end + + private + + def set_user + @user = GlobalID::Locator.locate_signed(params[:token], for: 'unsubscribe') + end + + def set_body_classes + @body_classes = 'lighter' + end + + def set_type + @type = email_type_from_param + end + + def email_type_from_param + case params[:type] + when 'follow', 'reblog', 'favourite', 'mention', 'follow_request' + "notification_emails.#{params[:type]}" + else + raise ArgumentError + end + end +end diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb index c428fd30d6..7cd3bab1af 100644 --- a/app/mailers/notification_mailer.rb +++ b/app/mailers/notification_mailer.rb @@ -8,61 +8,71 @@ class NotificationMailer < ApplicationMailer def mention(recipient, notification) @me = recipient + @user = recipient.user + @type = 'mention' @status = notification.target_status - return unless @me.user.functional? && @status.present? + return unless @user.functional? && @status.present? locale_for_account(@me) do thread_by_conversation(@status.conversation) - mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct) + mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct) end end def follow(recipient, notification) @me = recipient + @user = recipient.user + @type = 'follow' @account = notification.from_account - return unless @me.user.functional? + return unless @user.functional? locale_for_account(@me) do - mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow.subject', name: @account.acct) + mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.follow.subject', name: @account.acct) end end def favourite(recipient, notification) @me = recipient + @user = recipient.user + @type = 'favourite' @account = notification.from_account @status = notification.target_status - return unless @me.user.functional? && @status.present? + return unless @user.functional? && @status.present? locale_for_account(@me) do thread_by_conversation(@status.conversation) - mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct) + mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct) end end def reblog(recipient, notification) @me = recipient + @user = recipient.user + @type = 'reblog' @account = notification.from_account @status = notification.target_status - return unless @me.user.functional? && @status.present? + return unless @user.functional? && @status.present? locale_for_account(@me) do thread_by_conversation(@status.conversation) - mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct) + mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct) end end def follow_request(recipient, notification) @me = recipient + @user = recipient.user + @type = 'follow_request' @account = notification.from_account - return unless @me.user.functional? + return unless @user.functional? locale_for_account(@me) do - mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct) + mail to: email_address_with_name(@user.email, @me.username), subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct) end end diff --git a/app/views/layouts/mailer.html.haml b/app/views/layouts/mailer.html.haml index 43c8559270..e39a09780e 100644 --- a/app/views/layouts/mailer.html.haml +++ b/app/views/layouts/mailer.html.haml @@ -44,7 +44,11 @@ %tbody %td.column-cell %p= t 'about.hosted_on', domain: site_hostname - %p= link_to t('application_mailer.notification_preferences'), settings_preferences_notifications_url + %p + = link_to t('application_mailer.notification_preferences'), settings_preferences_notifications_url + - if defined?(@type) + ยท + = link_to t('application_mailer.unsubscribe'), unsubscribe_url(token: @user.to_sgid(for: 'unsubscribe').to_s, type: @type) %td.column-cell.text-right = link_to root_url do = image_tag full_pack_url('media/images/mailer/logo.png'), alt: 'Mastodon', height: 24 diff --git a/app/views/mail_subscriptions/create.html.haml b/app/views/mail_subscriptions/create.html.haml new file mode 100644 index 0000000000..16ee486b00 --- /dev/null +++ b/app/views/mail_subscriptions/create.html.haml @@ -0,0 +1,9 @@ +- content_for :page_title do + = t('mail_subscriptions.unsubscribe.title') + +.simple_form + %h1.title= t('mail_subscriptions.unsubscribe.complete') + %p.lead + = t('mail_subscriptions.unsubscribe.success_html', domain: content_tag(:strong, site_hostname), type: content_tag(:strong, I18n.t(@type, scope: 'mail_subscriptions.unsubscribe.emails')), email: content_tag(:strong, @user.email)) + %p.lead + = t('mail_subscriptions.unsubscribe.resubscribe_html', settings_path: settings_preferences_notifications_path) diff --git a/app/views/mail_subscriptions/show.html.haml b/app/views/mail_subscriptions/show.html.haml new file mode 100644 index 0000000000..afa2ab6ed7 --- /dev/null +++ b/app/views/mail_subscriptions/show.html.haml @@ -0,0 +1,12 @@ +- content_for :page_title do + = t('mail_subscriptions.unsubscribe.title') + +.simple_form + %h1.title= t('mail_subscriptions.unsubscribe.title') + %p.lead + = t('mail_subscriptions.unsubscribe.confirmation_html', domain: content_tag(:strong, site_hostname), type: content_tag(:strong, I18n.t(@type, scope: 'mail_subscriptions.unsubscribe.emails')), email: content_tag(:strong, @user.email), settings_path: settings_preferences_notifications_path) + + = form_tag unsubscribe_path, method: :post do + = hidden_field_tag :token, params[:token] + = hidden_field_tag :type, params[:type] + = button_tag t('mail_subscriptions.unsubscribe.action'), type: :submit diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml index ddf503197c..035a0e999d 100644 --- a/config/i18n-tasks.yml +++ b/config/i18n-tasks.yml @@ -66,6 +66,7 @@ ignore_unused: - 'notification_mailer.*' - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks}_html' - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks}_html' + - 'mail_subscriptions.unsubscribe.emails.*' ignore_inconsistent_interpolations: - '*.one' diff --git a/config/locales/en.yml b/config/locales/en.yml index 2c292c42d4..10eac9aeac 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -978,6 +978,7 @@ en: notification_preferences: Change e-mail preferences salutation: "%{name}," settings: 'Change e-mail preferences: %{link}' + unsubscribe: Unsubscribe view: 'View:' view_profile: View profile view_status: View post @@ -1342,6 +1343,21 @@ en: failed_sign_in_html: Failed sign-in attempt with %{method} from %{ip} (%{browser}) successful_sign_in_html: Successful sign-in with %{method} from %{ip} (%{browser}) title: Authentication history + mail_subscriptions: + unsubscribe: + action: Yes, unsubscribe + complete: Unsubscribed + confirmation_html: Are you sure you want to unsubscribe from receiving %{type} for Mastodon on %{domain} to your e-mail at %{email}? You can always re-subscribe from your e-mail notification settings. + emails: + notification_emails: + favourite: favorite notification e-mails + follow: follow notification e-mails + follow_request: follow request e-mails + mention: mention notification e-mails + reblog: boost notification e-mails + resubscribe_html: If you've unsubscribed by mistake, you can re-subscribe from your e-mail notification settings. + success_html: You'll no longer receive %{type} for Mastodon on %{domain} to your e-mail at %{email}. + title: Unsubscribe media_attachments: validations: images_and_video: Cannot attach a video to a post that already contains images diff --git a/config/routes.rb b/config/routes.rb index 55e5cf36a0..f11fcdc237 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -67,6 +67,8 @@ Rails.application.routes.draw do devise_scope :user do get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite + resource :unsubscribe, only: [:show, :create], controller: :mail_subscriptions + namespace :auth do resource :setup, only: [:show, :update], controller: :setup resource :challenge, only: [:create], controller: :challenges From 25c66fa640962a4d54d59a3f53516ab6dcb1dae6 Mon Sep 17 00:00:00 2001 From: Matt Jankowski Date: Mon, 12 Jun 2023 11:37:43 -0400 Subject: [PATCH 27/27] Enable paperclip for account attachment examples (#25381) --- spec/support/examples/models/concerns/account_avatar.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/examples/models/concerns/account_avatar.rb b/spec/support/examples/models/concerns/account_avatar.rb index 8f5b81f3a5..16ebda5641 100644 --- a/spec/support/examples/models/concerns/account_avatar.rb +++ b/spec/support/examples/models/concerns/account_avatar.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true shared_examples 'AccountAvatar' do |fabricator| - describe 'static avatars' do + describe 'static avatars', paperclip_processing: true do describe 'when GIF' do it 'creates a png static style' do account = Fabricate(fabricator, avatar: attachment_fixture('avatar.gif'))