From 41acabad193a341955550ed6c4f6fac5525b127d Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Thu, 5 Jan 2023 16:41:22 +1000 Subject: [PATCH 01/53] DEV: Change system spec JS log level to SEVERE by default (#19757) Having this set to ALL pollutes the JS system spec logs with a bunch of unnecessary noise like this: > "PresenceChannel '/chat-user/core/1' dropped message (received 315, expecting 246), resyncing..." Or: > "DEPRECATION: The \u003Cdiscourse@component:plugin-connector::ember1112>#save computed property was just overridden. This removes the computed property and replaces it with a plain value, and has been deprecated. Now, we will only log errors. To configure this set the `SELENIUM_BROWSER_LOG_LEVEL` env var. --- spec/rails_helper.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index f1831ef300..4b065872c6 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -257,8 +257,17 @@ RSpec.configure do |config| capybara_config.server_port = 31337 + ENV['TEST_ENV_NUMBER'].to_i end + # The valid values for SELENIUM_BROWSER_LOG_LEVEL are: + # + # OFF + # SEVERE + # WARNING + # INFO + # DEBUG + # ALL + browser_log_level = ENV["SELENIUM_BROWSER_LOG_LEVEL"] || "SEVERE" chrome_browser_options = Selenium::WebDriver::Chrome::Options.new( - logging_prefs: { "browser" => "INFO", "driver" => "ALL" } + logging_prefs: { "browser" => browser_log_level, "driver" => "ALL" } ).tap do |options| options.add_argument("--window-size=1400,1400") options.add_argument("--no-sandbox") From d5491b13f5ddce9e116de2d36969849612ac8099 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Thu, 5 Jan 2023 12:47:05 +0000 Subject: [PATCH 02/53] DEV: Fix syntax/formatting in xenforo import script (#19761) Followup to 7dfe85fc --- script/import_scripts/xenforo.rb | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/script/import_scripts/xenforo.rb b/script/import_scripts/xenforo.rb index 39ef48090d..38e45342c6 100755 --- a/script/import_scripts/xenforo.rb +++ b/script/import_scripts/xenforo.rb @@ -89,6 +89,7 @@ class ImportScripts::XenForo < ImportScripts::Base post_create_action: proc do |u| import_avatar(user['id'], u) end + } end end end @@ -117,7 +118,7 @@ class ImportScripts::XenForo < ImportScripts::Base position: c['display_order'], post_create_action: proc do |category| url = "board/#{c['node_name']}" - Permalink.find_or_create_by(url: url, category_id: category.id) + Permalink.find_or_create_by(url: url, category_id: category.id) end } end @@ -135,7 +136,7 @@ class ImportScripts::XenForo < ImportScripts::Base parent_category_id: category_id_from_imported_category_id(c['parent_node_id']), post_create_action: proc do |category| url = "board/#{c['node_name']}" - Permalink.find_or_create_by(url: url, category_id: category.id) + Permalink.find_or_create_by(url: url, category_id: category.id) end } end @@ -192,7 +193,7 @@ class ImportScripts::XenForo < ImportScripts::Base total_count = mysql_query("SELECT COUNT(*) AS count FROM #{TABLE_PREFIX}liked_content WHERE content_type = 'post'").first["count"] batches(BATCH_SIZE) do |offset| results = mysql_query( - "SELECT like_id, content_id, like_user_id, like_date + "SELECT like_id, content_id, like_user_id, like_date FROM #{TABLE_PREFIX}liked_content WHERE content_type = 'post' ORDER BY like_id @@ -297,10 +298,10 @@ class ImportScripts::XenForo < ImportScripts::Base post_count = mysql_query("SELECT COUNT(*) count FROM xf_conversation_message").first["count"] batches(BATCH_SIZE) do |offset| posts = mysql_query <<-SQL - SELECT c.conversation_id, c.recipients, c.title, m.message, m.user_id, m.message_date, m.message_id, IF(c.first_message_id != m.message_id, c.first_message_id, 0) as topic_id - FROM xf_conversation_master c - LEFT JOIN xf_conversation_message m ON m.conversation_id = c.conversation_id - ORDER BY c.conversation_id, m.message_id + SELECT c.conversation_id, c.recipients, c.title, m.message, m.user_id, m.message_date, m.message_id, IF(c.first_message_id != m.message_id, c.first_message_id, 0) as topic_id + FROM xf_conversation_master c + LEFT JOIN xf_conversation_message m ON m.conversation_id = c.conversation_id + ORDER BY c.conversation_id, m.message_id LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL @@ -316,7 +317,7 @@ class ImportScripts::XenForo < ImportScripts::Base id: message_id, user_id: user_id, raw: raw, - created_at: Time.zone.at(post["message_date"].to_i), + created_at: Time.zone.at(post["message_date"].to_i), import_mode: true } unless post["topic_id"] > 0 @@ -450,13 +451,13 @@ class ImportScripts::XenForo < ImportScripts::Base def process_xf_attachments(xf_type, s, import_id) ids = Set.new ids.merge(s.scan(get_xf_regexp(xf_type)).map { |x| x[0].to_i }) - + # not all attachments have an [ATTACH=] tag so we need to get the other ID's from the xf_attachment table if xf_type == :attachment && import_id > 0 sql = "SELECT attachment_id FROM #{TABLE_PREFIX}attachment WHERE content_id=#{import_id} and content_type='post';" - ids.merge(mysql_query(sql).to_a.map { |v| v["attachment_id"].to_i}) + ids.merge(mysql_query(sql).to_a.map { |v| v["attachment_id"].to_i }) end - + ids.each do |id| next unless id sql = get_xf_sql(xf_type, id).dup.squish! @@ -519,7 +520,7 @@ class ImportScripts::XenForo < ImportScripts::Base when :attachment <<-SQL SELECT a.attachment_id, a.data_id, d.filename, d.file_hash, d.user_id - FROM #{TABLE_PREFIX}attachment AS a + FROM #{TABLE_PREFIX}attachment AS a INNER JOIN #{TABLE_PREFIX}attachment_data d ON a.data_id = d.data_id WHERE attachment_id = #{id} AND content_type = 'post' From 7ecf4d12a96cff5044905b231cd30a3ed592e3e6 Mon Sep 17 00:00:00 2001 From: Vinoth Kannan Date: Thu, 5 Jan 2023 19:31:37 +0530 Subject: [PATCH 03/53] FIX: use reviewer's guardian permissions to create post/topic while approve. (#19710) We previously used post creator's guardian permissions which will raise an error if the reviewer added a staff-only (restricted) tag. Co-authored-by: Natalie Tay --- app/models/reviewable_queued_post.rb | 8 +++++--- spec/models/reviewable_queued_post_spec.rb | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/app/models/reviewable_queued_post.rb b/app/models/reviewable_queued_post.rb index 50a453c734..928b841060 100644 --- a/app/models/reviewable_queued_post.rb +++ b/app/models/reviewable_queued_post.rb @@ -74,13 +74,15 @@ class ReviewableQueuedPost < Reviewable def perform_approve_post(performed_by, args) created_post = nil - - creator = PostCreator.new(created_by, create_options.merge( + opts = create_options.merge( skip_validations: true, skip_jobs: true, skip_events: true, skip_guardian: true - )) + ) + opts.merge!(guardian: Guardian.new(performed_by)) if performed_by.staff? + + creator = PostCreator.new(created_by, opts) created_post = creator.create unless created_post && creator.errors.blank? diff --git a/spec/models/reviewable_queued_post_spec.rb b/spec/models/reviewable_queued_post_spec.rb index 1d79a301f4..da9fa7565e 100644 --- a/spec/models/reviewable_queued_post_spec.rb +++ b/spec/models/reviewable_queued_post_spec.rb @@ -180,7 +180,21 @@ RSpec.describe ReviewableQueuedPost, type: :model do expect(Post.count).to eq(post_count + 1) end - it "creates the post and topic when rejected" do + it "creates a topic with staff tag when approved" do + hidden_tag = Fabricate(:tag) + staff_tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [hidden_tag.name]) + reviewable.payload['tags'] += [hidden_tag.name] + + result = reviewable.perform(moderator, :approve_post) + + expect(result.success?).to eq(true) + expect(result.created_post_topic).to be_present + expect(result.created_post_topic).to be_valid + expect(reviewable.topic_id).to eq(result.created_post_topic.id) + expect(result.created_post_topic.tags.pluck(:name)).to match_array(reviewable.payload['tags']) + end + + it "does not create the post and topic when rejected" do topic_count, post_count = Topic.count, Post.count result = reviewable.perform(moderator, :reject_post) From 80164322e0affbdf2a6dac35e41703174f365c12 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Thu, 5 Jan 2023 13:35:10 -0800 Subject: [PATCH 04/53] DEV: Remove unused locale (#19764) --- config/locales/client.en.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 4aa447355c..4a88bd3665 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -4903,7 +4903,6 @@ en: customize: title: "Customize" - long_title: "Site Customizations" preview: "preview" explain_preview: "See the site with this theme enabled" save: "Save" From 6977761593780b72c55d31af2fd937a075bce1df Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Fri, 6 Jan 2023 09:04:38 +1000 Subject: [PATCH 05/53] DEV: Re-enable chat transcript nested system spec (#19758) The problem here was that if your input has an Enter listener (such as the chat message input) and the `fill_in(with: str)` string has a `\n` at the end, this is treated as an Enter keypress, so this `fill_in` was submitting the chat message. --- plugins/chat/spec/system/page_objects/chat/chat_channel.rb | 1 + plugins/chat/spec/system/transcript_spec.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/chat/spec/system/page_objects/chat/chat_channel.rb b/plugins/chat/spec/system/page_objects/chat/chat_channel.rb index 6d17e6d1ad..715c2a3d2e 100644 --- a/plugins/chat/spec/system/page_objects/chat/chat_channel.rb +++ b/plugins/chat/spec/system/page_objects/chat/chat_channel.rb @@ -69,6 +69,7 @@ module PageObjects end def send_message(text = nil) + text = text.chomp if text.present? # having \n on the end of the string counts as an Enter keypress find(".chat-composer-input").click # makes helper more reliable by ensuring focus is not lost find(".chat-composer-input").fill_in(with: text) click_send_message diff --git a/plugins/chat/spec/system/transcript_spec.rb b/plugins/chat/spec/system/transcript_spec.rb index 9db9cd9392..ad5e75212b 100644 --- a/plugins/chat/spec/system/transcript_spec.rb +++ b/plugins/chat/spec/system/transcript_spec.rb @@ -173,7 +173,7 @@ RSpec.describe "Quoting chat message transcripts", type: :system, js: true do context "when quoting a message in another message" do fab!(:message_1) { Fabricate(:chat_message, chat_channel: chat_channel_1) } - xit "quotes the message" do + it "quotes the message" do chat_page.visit_channel(chat_channel_1) expect(chat_channel_page).to have_no_loading_skeleton From 07cc8c863478ef969b224dafcf5f5aa225610ee0 Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Fri, 6 Jan 2023 09:04:52 +1000 Subject: [PATCH 06/53] FIX: Do not error if autogenerated channel slug is blank (#19759) In certain cases, like when `SiteSetting.slug_generation_method` is set to `none` with certain locales, the autogenerated chat channel slugs will end up blank. This was causing errors in unrelated jobs calling `update!` on the channel. Instead, we should just copy Category behaviour, which does not error if the autogenerated slug ends up blank. We already allow for this with chat channel URLs, using `-` in place of the missing slug. --- plugins/chat/app/models/category_channel.rb | 20 ++++++++++--------- .../chat/spec/models/category_channel_spec.rb | 7 +++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/plugins/chat/app/models/category_channel.rb b/plugins/chat/app/models/category_channel.rb index e95c3d5cff..b205e82b4a 100644 --- a/plugins/chat/app/models/category_channel.rb +++ b/plugins/chat/app/models/category_channel.rb @@ -28,16 +28,18 @@ class CategoryChannel < ChatChannel end def ensure_slug_ok - # if we don't unescape it first we strip the % from the encoded version - slug = SiteSetting.slug_generation_method == "encoded" ? CGI.unescape(self.slug) : self.slug - self.slug = Slug.for(slug, "", method: :encoded) + if self.slug.present? + # if we don't unescape it first we strip the % from the encoded version + slug = SiteSetting.slug_generation_method == "encoded" ? CGI.unescape(self.slug) : self.slug + self.slug = Slug.for(slug, "", method: :encoded) - if self.slug.blank? - errors.add(:slug, :invalid) - elsif SiteSetting.slug_generation_method == "ascii" && !CGI.unescape(self.slug).ascii_only? - errors.add(:slug, I18n.t("chat.category_channel.errors.slug_contains_non_ascii_chars")) - elsif duplicate_slug? - errors.add(:slug, I18n.t("chat.category_channel.errors.is_already_in_use")) + if self.slug.blank? + errors.add(:slug, :invalid) + elsif SiteSetting.slug_generation_method == "ascii" && !CGI.unescape(self.slug).ascii_only? + errors.add(:slug, I18n.t("chat.category_channel.errors.slug_contains_non_ascii_chars")) + elsif duplicate_slug? + errors.add(:slug, I18n.t("chat.category_channel.errors.is_already_in_use")) + end end end end diff --git a/plugins/chat/spec/models/category_channel_spec.rb b/plugins/chat/spec/models/category_channel_spec.rb index 17d9139f6e..e78681eb8b 100644 --- a/plugins/chat/spec/models/category_channel_spec.rb +++ b/plugins/chat/spec/models/category_channel_spec.rb @@ -124,6 +124,13 @@ RSpec.describe CategoryChannel do channel.validate expect(channel.errors.full_messages).to include("Slug is invalid") end + + it "does not add a validation error when autogenerated slug ends up empty" do + SiteSetting.slug_generation_method = "none" + channel.update(name: "は無効です", slug: nil) + expect(channel).to be_valid + expect(channel.slug).to eq("") + end end context "when there is a duplicate slug" do From 12f843068d763246e5db625426c73f023cc1715e Mon Sep 17 00:00:00 2001 From: Keegan George Date: Thu, 5 Jan 2023 13:45:57 -0800 Subject: [PATCH 07/53] DEV: Remove unused locale --- config/locales/client.en.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 4a88bd3665..aae22cd306 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -346,7 +346,6 @@ en: remove_reminder_keep_bookmark: "Remove reminder and keep bookmark" created_with_reminder: "You've bookmarked this post with a reminder %{date}. %{name}" created_with_reminder_generic: "You've bookmarked this with a reminder %{date}. %{name}" - remove: "Remove Bookmark" delete: "Delete Bookmark" confirm_delete: "Are you sure you want to delete this bookmark? The reminder will also be deleted." confirm_clear: "Are you sure you want to clear all your bookmarks from this topic?" From c4ea1586563a5e055082c56ff69d7ebbdaa175ee Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Fri, 6 Jan 2023 10:03:02 +1000 Subject: [PATCH 08/53] FIX: Improve tags in email subjects and add filter headers (#19760) This commit does a couple of things: 1. Changes the limit of tags to include a subject for a notification email to the `max_tags_per_topic` setting instead of the arbitrary 3 limit 2. Adds both an X-Discourse-Tags and X-Discourse-Category custom header to outbound emails containing the tags and category from the subject, so people on mail clients that allow advanced filtering (i.e. not Gmail) can filter mail by tags and category, which is useful for mailing list mode users c.f. https://meta.discourse.org/t/headers-for-email-notifications-so-that-gmail-users-can-filter-on-tags/249982/17 --- app/mailers/user_notifications.rb | 2 +- lib/email/message_builder.rb | 5 +++++ spec/lib/email/message_builder_spec.rb | 10 ++++++++++ spec/mailers/user_notifications_spec.rb | 13 +++++++++++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/app/mailers/user_notifications.rb b/app/mailers/user_notifications.rb index 78bdbd722b..3aca5a9334 100644 --- a/app/mailers/user_notifications.rb +++ b/app/mailers/user_notifications.rb @@ -538,7 +538,7 @@ class UserNotifications < ActionMailer::Base .visible_tags(Guardian.new(user)) .joins(:topic_tags) .where("topic_tags.topic_id = ?", post.topic_id) - .limit(3) + .limit(SiteSetting.max_tags_per_topic) .pluck(:name) show_tags_in_subject = tags.any? ? tags.join(" ") : nil diff --git a/lib/email/message_builder.rb b/lib/email/message_builder.rb index 9308705cd6..42aefe600a 100644 --- a/lib/email/message_builder.rb +++ b/lib/email/message_builder.rb @@ -161,6 +161,11 @@ module Email result['X-Discourse-Post-Id'] = @opts[:post_id].to_s if @opts[:post_id] result['X-Discourse-Topic-Id'] = @opts[:topic_id].to_s if @opts[:topic_id] + # at this point these have been filtered by the recipient's guardian for visibility, + # see UserNotifications#send_notification_email + result['X-Discourse-Tags'] = @template_args[:show_tags_in_subject] if @opts[:show_tags_in_subject] + result['X-Discourse-Category'] = @template_args[:show_category_in_subject] if @opts[:show_category_in_subject] + # please, don't send us automatic responses... result['X-Auto-Response-Suppress'] = 'All' diff --git a/spec/lib/email/message_builder_spec.rb b/spec/lib/email/message_builder_spec.rb index 00487992c0..4a9c1993be 100644 --- a/spec/lib/email/message_builder_spec.rb +++ b/spec/lib/email/message_builder_spec.rb @@ -155,6 +155,8 @@ RSpec.describe Email::MessageBuilder do body: 'hello world', topic_id: 1234, post_id: 4567, + show_tags_in_subject: "foo bar baz", + show_category_in_subject: "random" }.merge(additional_opts) ) end @@ -171,6 +173,14 @@ RSpec.describe Email::MessageBuilder do expect(message_with_header_args.header_args['Reply-To']).to eq("\"Discourse\" <#{SiteSetting.notification_email}>") end + it "passes through the topic tags" do + expect(message_with_header_args.header_args['X-Discourse-Tags']).to eq('foo bar baz') + end + + it "passes through the topic category" do + expect(message_with_header_args.header_args['X-Discourse-Category']).to eq('random') + end + context "when allow_reply_by_email is enabled " do let(:additional_opts) { { allow_reply_by_email: true } } diff --git a/spec/mailers/user_notifications_spec.rb b/spec/mailers/user_notifications_spec.rb index 63e4350633..6111bdde8f 100644 --- a/spec/mailers/user_notifications_spec.rb +++ b/spec/mailers/user_notifications_spec.rb @@ -400,6 +400,19 @@ RSpec.describe UserNotifications do expect(mail_html.scan(/>bobmarley/).count).to eq(1) end + it "the number of tags shown in subject should match max_tags_per_topic" do + SiteSetting.email_subject = "[%{site_name}] %{optional_pm}%{optional_cat}%{optional_tags}%{topic_title}" + SiteSetting.max_tags_per_topic = 1 + mail = UserNotifications.user_replied( + user, + post: response, + notification_type: notification.notification_type, + notification_data_hash: notification.data_hash + ) + expect(mail.subject).to match(/Taggo/) + expect(mail.subject).not_to match(/Taggie/) + end + it "doesn't include details when private_email is enabled" do SiteSetting.private_email = true mail = UserNotifications.user_replied( From 19a0bdc0ee08777eebd5c4c25bc92d1dc8b978e5 Mon Sep 17 00:00:00 2001 From: Jamie Wilson Date: Thu, 5 Jan 2023 15:38:32 -0500 Subject: [PATCH 09/53] FIX: Link to category settings should use slug Links to category settings were created using the category name. If the name was a single word, the link would be valid (regardless of capitalization). For example, if the category was named `Awesome` `/c/Awesome/edit/settings` is a valid URL as that is a case-insensitive match for the category slug of `awesome`. However, if the category had a space in it, the URL would be `/c/Awesome%20Name/edit/settings` which does not match the slug of `awesome-name`. This change uses the category slug, rather than the name, which is the expected behaviour (see `Category.find_by_slug_path`). --- app/serializers/reviewable_score_serializer.rb | 2 +- spec/fabricators/category_fabricator.rb | 1 + spec/serializers/reviewable_score_serializer_spec.rb | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/serializers/reviewable_score_serializer.rb b/app/serializers/reviewable_score_serializer.rb index 6a0eac38d0..5a4d1f05de 100644 --- a/app/serializers/reviewable_score_serializer.rb +++ b/app/serializers/reviewable_score_serializer.rb @@ -72,7 +72,7 @@ class ReviewableScoreSerializer < ApplicationSerializer when 'watched_word' "#{Discourse.base_url}/admin/customize/watched_words" when 'category' - "#{Discourse.base_url}/c/#{object.reviewable.category&.name}/edit/settings" + "#{Discourse.base_url}/c/#{object.reviewable.category&.slug}/edit/settings" else "#{Discourse.base_url}/admin/site_settings/category/all_results?filter=#{text}" end diff --git a/spec/fabricators/category_fabricator.rb b/spec/fabricators/category_fabricator.rb index 0e14967af4..3793bb118e 100644 --- a/spec/fabricators/category_fabricator.rb +++ b/spec/fabricators/category_fabricator.rb @@ -2,6 +2,7 @@ Fabricator(:category) do name { sequence(:name) { |n| "Amazing Category #{n}" } } + slug { sequence(:slug) { |n| "amazing-category-#{n}" } } skip_category_definition true user end diff --git a/spec/serializers/reviewable_score_serializer_spec.rb b/spec/serializers/reviewable_score_serializer_spec.rb index 1a52cbe9a8..4b58979493 100644 --- a/spec/serializers/reviewable_score_serializer_spec.rb +++ b/spec/serializers/reviewable_score_serializer_spec.rb @@ -18,7 +18,7 @@ RSpec.describe ReviewableScoreSerializer do category = Fabricate.build(:category) reviewable.category = category serialized = serialized_score('category') - link_url = "#{Discourse.base_url}/c/#{category.name}/edit/settings" + link_url = "#{Discourse.base_url}/c/#{category.slug}/edit/settings" category_link = "#{I18n.t('reviewables.reasons.links.category')}" expect(serialized.reason).to include(category_link) From c46cd1bd0440d9d33c194320e459b01d2f274dc7 Mon Sep 17 00:00:00 2001 From: Jamie Wilson Date: Thu, 5 Jan 2023 18:05:17 -0500 Subject: [PATCH 10/53] DEV: Specify slug name during Category fabrication --- spec/fabricators/category_fabricator.rb | 1 - spec/serializers/reviewable_score_serializer_spec.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/fabricators/category_fabricator.rb b/spec/fabricators/category_fabricator.rb index 3793bb118e..0e14967af4 100644 --- a/spec/fabricators/category_fabricator.rb +++ b/spec/fabricators/category_fabricator.rb @@ -2,7 +2,6 @@ Fabricator(:category) do name { sequence(:name) { |n| "Amazing Category #{n}" } } - slug { sequence(:slug) { |n| "amazing-category-#{n}" } } skip_category_definition true user end diff --git a/spec/serializers/reviewable_score_serializer_spec.rb b/spec/serializers/reviewable_score_serializer_spec.rb index 4b58979493..2894db4e87 100644 --- a/spec/serializers/reviewable_score_serializer_spec.rb +++ b/spec/serializers/reviewable_score_serializer_spec.rb @@ -15,7 +15,7 @@ RSpec.describe ReviewableScoreSerializer do end it 'adds a link for category settings' do - category = Fabricate.build(:category) + category = Fabricate(:category, name: 'Reviewable Category', slug: 'reviewable-category') reviewable.category = category serialized = serialized_score('category') link_url = "#{Discourse.base_url}/c/#{category.slug}/edit/settings" From f71e3c07dd0b2051ec82c9b02c814a03cf38b02b Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Mon, 19 Dec 2022 10:31:00 +0800 Subject: [PATCH 11/53] DEV: Experiment plugin api to add custom count to category section link This commit introduces the experimental `registerUserCategorySectionLinkCountable` and `refreshUserSidebarCategoriesSectionCounts` plugin APIs that allows a plugin to register custom countables to category section links on top of the defaults of unread and new. --- .../sidebar/user/categories-section.js | 33 +++- .../discourse/app/lib/plugin-api.js | 83 ++++++++++ .../category-section-link.js | 145 +++++++++++++++--- .../acceptance/sidebar-plugin-api-test.js | 117 +++++++++++++- 4 files changed, 348 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.js b/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.js index 10c66629d6..6175aa28a1 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.js @@ -1,28 +1,53 @@ import { inject as service } from "@ember/service"; import { action } from "@ember/object"; -import Category from "discourse/models/category"; import { cached } from "@glimmer/tracking"; +import Category from "discourse/models/category"; import SidebarCommonCategoriesSection from "discourse/components/sidebar/common/categories-section"; +import discourseDebounce from "discourse-common/lib/debounce"; + +export const REFRESH_COUNTS_APP_EVENT_NAME = + "sidebar:refresh-categories-section-counts"; export default class SidebarUserCategoriesSection extends SidebarCommonCategoriesSection { @service router; @service currentUser; + @service appEvents; constructor() { super(...arguments); this.callbackId = this.topicTrackingState.onStateChange(() => { - this.sectionLinks.forEach((sectionLink) => { - sectionLink.refreshCounts(); - }); + this.#refreshCounts(); }); + + this.appEvents.on(REFRESH_COUNTS_APP_EVENT_NAME, this, this.#refreshCounts); } willDestroy() { super.willDestroy(...arguments); this.topicTrackingState.offStateChange(this.callbackId); + + this.appEvents.off( + REFRESH_COUNTS_APP_EVENT_NAME, + this, + this.#refreshCounts + ); + } + + #refreshCounts() { + // TopicTrackingState changes or plugins can trigger this function so we debounce to ensure we're not refreshing + // unnecessarily. + discourseDebounce( + this, + () => { + this.sectionLinks.forEach((sectionLink) => { + sectionLink.refreshCounts(); + }); + }, + 300 + ); } @cached diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js index fe587a4335..0822df4fbe 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js @@ -104,6 +104,8 @@ import { downloadCalendar } from "discourse/lib/download-calendar"; import { consolePrefix } from "discourse/lib/source-identifier"; import { addSectionLink as addCustomCommunitySectionLink } from "discourse/lib/sidebar/custom-community-section-links"; import { addSidebarSection } from "discourse/lib/sidebar/custom-sections"; +import { registerCustomCountable as registerUserCategorySectionLinkCountable } from "discourse/lib/sidebar/user/categories-section/category-section-link"; +import { REFRESH_COUNTS_APP_EVENT_NAME as REFRESH_USER_SIDEBAR_CATEGORIES_SECTION_COUNTS_APP_EVENT_NAME } from "discourse/components/sidebar/user/categories-section"; import DiscourseURL from "discourse/lib/url"; import { registerNotificationTypeRenderer } from "discourse/lib/notification-types-manager"; import { registerUserMenuTab } from "discourse/lib/user-menu/tab"; @@ -1809,6 +1811,87 @@ class PluginApi { addCustomCommunitySectionLink(arg, secondary); } + /** + * EXPERIMENTAL. Do not use. + * Registers a new countable for section links under Sidebar Categories section on top of the default countables of + * unread topics count and new topics count. + * + * ``` + * api.registerUserCategorySectionLinkCountable({ + * badgeTextFunction: (count) => { + * return I18n.t("custom.open_count", count: count"); + * }, + * route: "discovery.openCategory", + * shouldRegister: ({ category } => { + * return category.custom_fields.enable_open_topics_count; + * }), + * refreshCountFunction: ({ _topicTrackingState, category } => { + * return category.open_topics_count; + * }), + * prioritizeDefaults: ({ currentUser, category } => { + * return category.custom_fields.show_open_topics_count_first; + * }) + * }) + * ``` + * + * @callback badgeTextFunction + * @param {Integer} count - The count as given by the `refreshCountFunction`. + * @returns {String} - Text for the badge displayed in the section link. + * + * @callback shouldRegister + * @param {Object} arg + * @param {Category} arg.category - The category model for the sidebar section link. + * @returns {Boolean} - Whether the countable should be registered for the sidebar section link. + * + * @callback refreshCountFunction + * @param {Object} arg + * @param {Category} arg.category - The category model for the sidebar section link. + * @returns {integer} - The value used to set the property for the count. + * + * @callback prioritizeOverDefaults + * @param {Object} arg + * @param {Category} arg.category - The category model for the sidebar section link. + * @param {User} arg.currentUser - The user model for the current user. + * @returns {boolean} - Whether the countable should be prioritized over the defaults. + * + * @param {Object} arg - An object + * @param {string} arg.badgeTextFunction - Function used to generate the text for the badge displayed in the section link. + * @param {string} arg.route - The Ember route name to generate the href attribute for the link. + * @param {Object=} arg.routeQuery - Object representing the query params that should be appended to the route generated. + * @param {shouldRegister} arg.shouldRegister - Function used to determine if the countable should be registered for the category. + * @param {refreshCountFunction} arg.refreshCountFunction - Function used to calculate the value used to set the property for the count whenever the sidebar section link refreshes. + * @param {prioritizeOverDefaults} args.prioritizeOverDefaults - Function used to determine whether the countable should be prioritized over the default countables of unread/new. + */ + registerUserCategorySectionLinkCountable({ + badgeTextFunction, + route, + routeQuery, + shouldRegister, + refreshCountFunction, + prioritizeOverDefaults, + }) { + registerUserCategorySectionLinkCountable({ + badgeTextFunction, + route, + routeQuery, + shouldRegister, + refreshCountFunction, + prioritizeOverDefaults, + }); + } + + /** + * EXPERIMENTAL. Do not use. + * Triggers a refresh of the counts for all category section links under the categories section for a logged in user. + */ + refreshUserSidebarCategoriesSectionCounts() { + const appEvents = this._lookupContainer("service:app-events"); + + appEvents?.trigger( + REFRESH_USER_SIDEBAR_CATEGORIES_SECTION_COUNTS_APP_EVENT_NAME + ); + } + /** * EXPERIMENTAL. Do not use. * Support for adding a Sidebar section by returning a class which extends from the BaseCustomSidebarSection diff --git a/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js b/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js index e855089d0a..1427b50bdf 100644 --- a/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js +++ b/app/assets/javascripts/discourse/app/lib/sidebar/user/categories-section/category-section-link.js @@ -1,35 +1,121 @@ import I18n from "I18n"; import { tracked } from "@glimmer/tracking"; +import { get, set } from "@ember/object"; import { bind } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sidebar"; +const DEFAULT_COUNTABLES = [ + { + propertyName: "totalUnread", + badgeTextFunction: (count) => { + return I18n.t("sidebar.unread_count", { count }); + }, + route: "discovery.unreadCategory", + refreshCountFunction: ({ topicTrackingState, category }) => { + return topicTrackingState.countUnread({ + categoryId: category.id, + }); + }, + }, + { + propertyName: "totalNew", + badgeTextFunction: (count) => { + return I18n.t("sidebar.new_count", { count }); + }, + route: "discovery.newCategory", + refreshCountFunction: ({ topicTrackingState, category }) => { + return topicTrackingState.countNew({ + categoryId: category.id, + }); + }, + }, +]; + +const customCountables = []; + +export function registerCustomCountable({ + badgeTextFunction, + route, + routeQuery, + shouldRegister, + refreshCountFunction, + prioritizeOverDefaults, +}) { + const length = customCountables.length + 1; + + customCountables.push({ + propertyName: `customCountableProperty${length}`, + badgeTextFunction, + route, + routeQuery, + shouldRegister, + refreshCountFunction, + prioritizeOverDefaults, + }); +} + +export function resetCustomCountables() { + customCountables.length = 0; +} + export default class CategorySectionLink { - @tracked totalUnread = 0; - @tracked totalNew = 0; - @tracked hideCount = - this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION; + @tracked activeCountable; constructor({ category, topicTrackingState, currentUser }) { this.category = category; this.topicTrackingState = topicTrackingState; this.currentUser = currentUser; + this.countables = this.#countables(); + this.refreshCounts(); } + #countables() { + const countables = [...DEFAULT_COUNTABLES]; + + if (customCountables.length > 0) { + customCountables.forEach((customCountable) => { + if ( + !customCountable.shouldRegister || + customCountable.shouldRegister({ category: this.category }) + ) { + if ( + customCountable?.prioritizeOverDefaults({ + category: this.category, + currentUser: this.currentUser, + }) + ) { + countables.unshift(customCountable); + } else { + countables.push(customCountable); + } + } + }); + } + + return countables; + } + + get hideCount() { + return this.currentUser?.sidebarListDestination !== UNREAD_LIST_DESTINATION; + } + @bind refreshCounts() { - this.totalUnread = this.topicTrackingState.countUnread({ - categoryId: this.category.id, - }); + this.countables = this.#countables(); - if (this.totalUnread === 0) { - this.totalNew = this.topicTrackingState.countNew({ - categoryId: this.category.id, + this.activeCountable = this.countables.find((countable) => { + const count = countable.refreshCountFunction({ + topicTrackingState: this.topicTrackingState, + category: this.category, }); - } + + set(this, countable.propertyName, count); + return count > 0; + }); } get name() { @@ -74,29 +160,38 @@ export default class CategorySectionLink { if (this.hideCount) { return; } - if (this.totalUnread > 0) { - return I18n.t("sidebar.unread_count", { - count: this.totalUnread, - }); - } else if (this.totalNew > 0) { - return I18n.t("sidebar.new_count", { - count: this.totalNew, - }); + + const activeCountable = this.activeCountable; + + if (activeCountable) { + return activeCountable.badgeTextFunction( + get(this, activeCountable.propertyName) + ); } } get route() { if (this.currentUser?.sidebarListDestination === UNREAD_LIST_DESTINATION) { - if (this.totalUnread > 0) { - return "discovery.unreadCategory"; - } - if (this.totalNew > 0) { - return "discovery.newCategory"; + const activeCountable = this.activeCountable; + + if (activeCountable) { + return activeCountable.route; } } + return "discovery.category"; } + get query() { + if (this.currentUser?.sidebarListDestination === UNREAD_LIST_DESTINATION) { + const activeCountable = this.activeCountable; + + if (activeCountable?.routeQuery) { + return activeCountable.routeQuery; + } + } + } + get suffixCSSClass() { return "unread"; } @@ -106,7 +201,7 @@ export default class CategorySectionLink { } get suffixValue() { - if (this.hideCount && (this.totalUnread || this.totalNew)) { + if (this.hideCount && this.activeCountable) { return "circle"; } } diff --git a/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js b/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js index 489972f675..70f063461e 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/sidebar-plugin-api-test.js @@ -1,13 +1,17 @@ import { test } from "qunit"; import I18n from "I18n"; -import { click, visit } from "@ember/test-helpers"; +import { click, settled, visit } from "@ember/test-helpers"; import { acceptance, exists, query, queryAll, + updateCurrentUser, } from "discourse/tests/helpers/qunit-helpers"; import { withPluginApi } from "discourse/lib/plugin-api"; +import Site from "discourse/models/site"; +import { resetCustomCountables } from "discourse/lib/sidebar/user/categories-section/category-section-link"; +import { UNREAD_LIST_DESTINATION } from "discourse/controllers/preferences/sidebar"; import { bind } from "discourse-common/utils/decorators"; acceptance("Sidebar - Plugin API", function (needs) { @@ -629,4 +633,115 @@ acceptance("Sidebar - Plugin API", function (needs) { "does not display the section" ); }); + + test("Registering a custom countable for a section link in the user's sidebar categories section", async function (assert) { + try { + return await withPluginApi("1.6.0", async (api) => { + const categories = Site.current().categories; + const category1 = categories[0]; + const category2 = categories[1]; + + updateCurrentUser({ + sidebar_category_ids: [category1.id, category2.id], + }); + + // User has one unread topic + this.container.lookup("service:topic-tracking-state").loadStates([ + { + topic_id: 2, + highest_post_number: 12, + last_read_post_number: 11, + created_at: "2020-02-09T09:40:02.672Z", + category_id: category1.id, + notification_level: 2, + created_in_new_period: false, + treat_as_new_topic_start_date: "2022-05-09T03:17:34.286Z", + }, + ]); + + api.registerUserCategorySectionLinkCountable({ + badgeTextFunction: (count) => { + return `some custom ${count}`; + }, + route: "discovery.latestCategory", + routeQuery: { status: "open" }, + shouldRegister: ({ category }) => { + if (category.name === category1.name) { + return true; + } else if (category.name === category2.name) { + return false; + } + }, + refreshCountFunction: ({ category }) => { + return category.topic_count; + }, + prioritizeOverDefaults: ({ category }) => { + return category.topic_count > 1000; + }, + }); + + await visit("/"); + + assert.ok( + exists( + `.sidebar-section-link-${category1.name} .sidebar-section-link-suffix.unread` + ), + "the right suffix is displayed when custom countable is active" + ); + + assert.strictEqual( + query(`.sidebar-section-link-${category1.name}`).pathname, + `/c/${category1.name}/${category1.id}`, + "does not use route configured for custom countable when user has elected not to show any counts in sidebar" + ); + + assert.notOk( + exists( + `.sidebar-section-link-${category2.name} .sidebar-section-link-suffix.unread` + ), + "does not display suffix when custom countable is not registered" + ); + + updateCurrentUser({ + sidebar_list_destination: UNREAD_LIST_DESTINATION, + }); + + assert.strictEqual( + query( + `.sidebar-section-link-${category1.name} .sidebar-section-link-content-badge` + ).innerText.trim(), + I18n.t("sidebar.unread_count", { count: 1 }), + "displays the right badge text in section link when unread is present and custom countable is not prioritised over unread" + ); + + category1.set("topic_count", 2000); + + api.refreshUserSidebarCategoriesSectionCounts(); + + await settled(); + + assert.strictEqual( + query( + `.sidebar-section-link-${category1.name} .sidebar-section-link-content-badge` + ).innerText.trim(), + `some custom ${category1.topic_count}`, + "displays the right badge text in section link when unread is present but custom countable is prioritised over unread" + ); + + assert.strictEqual( + query(`.sidebar-section-link-${category1.name}`).pathname, + `/c/${category1.name}/${category1.id}/l/latest`, + "has the right pathname for section link" + ); + + assert.strictEqual( + query(`.sidebar-section-link-${category1.name}`).search, + "?status=open", + "has the right query params for section link" + ); + }); + } finally { + resetCustomCountables(); + } + }); }); From 1ee9356a548fc30baba4c6df75cd8c80a60d4219 Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Thu, 5 Jan 2023 11:36:53 +0800 Subject: [PATCH 12/53] PR reviews --- .../sidebar/user/categories-section.js | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.js b/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.js index 6175aa28a1..d3bc1378a7 100644 --- a/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.js +++ b/app/assets/javascripts/discourse/app/components/sidebar/user/categories-section.js @@ -2,9 +2,9 @@ import { inject as service } from "@ember/service"; import { action } from "@ember/object"; import { cached } from "@glimmer/tracking"; +import { debounce } from "discourse-common/utils/decorators"; import Category from "discourse/models/category"; import SidebarCommonCategoriesSection from "discourse/components/sidebar/common/categories-section"; -import discourseDebounce from "discourse-common/lib/debounce"; export const REFRESH_COUNTS_APP_EVENT_NAME = "sidebar:refresh-categories-section-counts"; @@ -18,10 +18,10 @@ export default class SidebarUserCategoriesSection extends SidebarCommonCategorie super(...arguments); this.callbackId = this.topicTrackingState.onStateChange(() => { - this.#refreshCounts(); + this._refreshCounts(); }); - this.appEvents.on(REFRESH_COUNTS_APP_EVENT_NAME, this, this.#refreshCounts); + this.appEvents.on(REFRESH_COUNTS_APP_EVENT_NAME, this, this._refreshCounts); } willDestroy() { @@ -32,22 +32,17 @@ export default class SidebarUserCategoriesSection extends SidebarCommonCategorie this.appEvents.off( REFRESH_COUNTS_APP_EVENT_NAME, this, - this.#refreshCounts + this._refreshCounts ); } - #refreshCounts() { - // TopicTrackingState changes or plugins can trigger this function so we debounce to ensure we're not refreshing - // unnecessarily. - discourseDebounce( - this, - () => { - this.sectionLinks.forEach((sectionLink) => { - sectionLink.refreshCounts(); - }); - }, - 300 - ); + // TopicTrackingState changes or plugins can trigger this function so we debounce to ensure we're not refreshing + // unnecessarily. + @debounce(300) + _refreshCounts() { + this.sectionLinks.forEach((sectionLink) => { + sectionLink.refreshCounts(); + }); } @cached From 66e8a35b4d2f05d081ee0c459774158e00033ef8 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 6 Jan 2023 11:26:18 +0000 Subject: [PATCH 13/53] DEV: Include message-bus request type in HTTP request data (#19762) --- lib/middleware/request_tracker.rb | 15 ++++++++++++++- spec/lib/middleware/request_tracker_spec.rb | 20 ++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/middleware/request_tracker.rb b/lib/middleware/request_tracker.rb index fda5fc46f2..4decb0983c 100644 --- a/lib/middleware/request_tracker.rb +++ b/lib/middleware/request_tracker.rb @@ -125,13 +125,26 @@ class Middleware::RequestTracker is_api: is_api, is_user_api: is_user_api, is_background: is_message_bus || is_topic_timings, - background_type: is_message_bus ? "message-bus" : "topic-timings", is_mobile: helper.is_mobile?, track_view: track_view, timing: timing, queue_seconds: env['REQUEST_QUEUE_SECONDS'] } + if h[:is_background] + h[:background_type] = if is_message_bus + if request.query_string.include?("dlp=t") + "message-bus-dlp" + elsif env["HTTP_DONT_CHUNK"] + "message-bus-dontchunk" + else + "message-bus" + end + else + "topic-timings" + end + end + if h[:is_crawler] user_agent = env['HTTP_USER_AGENT'] if user_agent && (user_agent.encoding != Encoding::UTF_8) diff --git a/spec/lib/middleware/request_tracker_spec.rb b/spec/lib/middleware/request_tracker_spec.rb index b3953a5391..d00fc3f759 100644 --- a/spec/lib/middleware/request_tracker_spec.rb +++ b/spec/lib/middleware/request_tracker_spec.rb @@ -2,10 +2,10 @@ RSpec.describe Middleware::RequestTracker do def env(opts = {}) - create_request_env.merge( + path = opts.delete(:path) || "/path?bla=1" + create_request_env(path: path).merge( "HTTP_HOST" => "http://test.com", "HTTP_USER_AGENT" => "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36", - "REQUEST_URI" => "/path?bla=1", "REQUEST_METHOD" => "GET", "HTTP_ACCEPT" => "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "rack.input" => StringIO.new @@ -666,5 +666,21 @@ RSpec.describe Middleware::RequestTracker do expect(headers["X-Runtime"].to_f).to be > 0 end + + it "can correctly log messagebus request types" do + tracker = Middleware::RequestTracker.new(app([200, {}, []])) + + tracker.call(env(path: "/message-bus/abcde/poll")) + expect(@data[:is_background]).to eq(true) + expect(@data[:background_type]).to eq("message-bus") + + tracker.call(env(path: "/message-bus/abcde/poll?dlp=t")) + expect(@data[:is_background]).to eq(true) + expect(@data[:background_type]).to eq("message-bus-dlp") + + tracker.call(env("HTTP_DONT_CHUNK" => "True", path: "/message-bus/abcde/poll")) + expect(@data[:is_background]).to eq(true) + expect(@data[:background_type]).to eq("message-bus-dontchunk") + end end end From aa4ff472088e5d347efd061380766bb70c6c4b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=20Marjanovi=C4=87?= Date: Fri, 6 Jan 2023 05:18:35 -0800 Subject: [PATCH 14/53] FEATURE: Allow `target` attribute in links in user_field descriptions (#19102) This change adds `target` to the set of attributes allowed by the HTML sanitizer which is applied to the description of a user_field. The rationale for this change: * If one puts a link (...) in the description of a user_field that is present and/or required at sign-up, the expectation is that a prospective new user will click on that link during sign-up. * Without an appropriate `target` attribute on the link, the new page will be loaded in the same window/tab as the sign-up form, but this will obliterate any fields that the user had already filled-out on the form. (E.g., hitting the back-button will return to an empty form.) * Such UX behavior is incredibly aggravating to new users. This change allows an admin to add a `target` attribute to links, to instruct the browser to open them in a different window/tab, leaving a sign-up form intact. --- app/models/user_field.rb | 2 +- spec/models/user_field_spec.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/models/user_field.rb b/app/models/user_field.rb index ea8330cc69..a872af740f 100644 --- a/app/models/user_field.rb +++ b/app/models/user_field.rb @@ -28,7 +28,7 @@ class UserField < ActiveRecord::Base def sanitize_description if description_changed? - self.description = sanitize_field(self.description) + self.description = sanitize_field(self.description, additional_attributes: ['target']) end end end diff --git a/spec/models/user_field_spec.rb b/spec/models/user_field_spec.rb index e93c956bed..ad81162d79 100644 --- a/spec/models/user_field_spec.rb +++ b/spec/models/user_field_spec.rb @@ -19,4 +19,13 @@ RSpec.describe UserField do expect(user_field.description).to eq("click me!alert('TEST');") end + + it 'allows target attribute in the description' do + link = "elsewhere" + user_field = Fabricate(:user_field) + + user_field.update!(description: link) + + expect(user_field.description).to eq(link) + end end From d464f1fc6213a3f549062e3a74c0e76f9bbdd76a Mon Sep 17 00:00:00 2001 From: Joffrey JAFFEUX Date: Fri, 6 Jan 2023 14:38:08 +0100 Subject: [PATCH 15/53] FIX: render_404 is not defined (#19769) Note this endpoint is soon going to be replaced. --- plugins/chat/app/controllers/chat_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/chat/app/controllers/chat_controller.rb b/plugins/chat/app/controllers/chat_controller.rb index 6b7d74d020..9849ffc290 100644 --- a/plugins/chat/app/controllers/chat_controller.rb +++ b/plugins/chat/app/controllers/chat_controller.rb @@ -288,8 +288,8 @@ class Chat::ChatController < Chat::ChatBaseController end def message_link - return render_404 if @message.blank? || @message.deleted_at.present? - return render_404 if @message.chat_channel.blank? + raise Discourse::NotFound if @message.blank? || @message.deleted_at.present? + raise Discourse::NotFound if @message.chat_channel.blank? set_channel_and_chatable_with_access_check(chat_channel_id: @message.chat_channel_id) render json: success_json.merge( From 5ce5ff053e517e6c526349052868bc2841d3a06c Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 6 Jan 2023 14:31:10 +0000 Subject: [PATCH 16/53] FIX: Bump messagebus to v4.3.1 (#19771) Includes "FIX: Ensure non-long-polling requests are always spaced out": https://github.com/discourse/message_bus/commit/233b248c964b4a88253227be27f298d7e0c4151d --- Gemfile.lock | 2 +- app/assets/javascripts/yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 6edfc5a9f3..6bac0060e9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -215,7 +215,7 @@ GEM matrix (0.4.2) maxminddb (0.1.22) memory_profiler (1.0.1) - message_bus (4.3.0) + message_bus (4.3.1) rack (>= 1.1.3) method_source (1.0.0) mini_mime (1.1.2) diff --git a/app/assets/javascripts/yarn.lock b/app/assets/javascripts/yarn.lock index d62417e440..2d13091451 100644 --- a/app/assets/javascripts/yarn.lock +++ b/app/assets/javascripts/yarn.lock @@ -6881,9 +6881,9 @@ merge2@^1.2.3, merge2@^1.3.0: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== message-bus-client@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/message-bus-client/-/message-bus-client-4.3.0.tgz#03710b9acdbaa4c9117b2215b2dd8038a360c168" - integrity sha512-tJbSFz+NB8fBXn5OqGqisi881SUv1ypIoETHgyQHfg5NHFr15Y+BIx4gPqXhy5iG0EJlayZfcNyFxYfFfhyv+w== + version "4.3.1" + resolved "https://registry.yarnpkg.com/message-bus-client/-/message-bus-client-4.3.1.tgz#2107b569131b03d7277801cd3409059e48e9f25e" + integrity sha512-gPG8POalZrM6t9xZPIzER3uDCiAfdwMEjx6ulbYICqzJx0CpLSnZRXKuWvhds4dM3iZQZXpH37UCfYYNICKu5g== messageformat@0.1.5: version "0.1.5" From 7b5f7b4484db9afc0b7e5cd0bb497b705009e82a Mon Sep 17 00:00:00 2001 From: Roman Rizzi Date: Fri, 6 Jan 2023 11:47:15 -0300 Subject: [PATCH 17/53] FIX: Don't change the default allowed_attribute when calling #sanitize_field (#19770) --- app/models/concerns/has_sanitizable_fields.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/has_sanitizable_fields.rb b/app/models/concerns/has_sanitizable_fields.rb index b0db07de00..426c420747 100644 --- a/app/models/concerns/has_sanitizable_fields.rb +++ b/app/models/concerns/has_sanitizable_fields.rb @@ -6,7 +6,7 @@ module HasSanitizableFields def sanitize_field(field, additional_attributes: []) if field sanitizer = Rails::Html::SafeListSanitizer.new - allowed_attributes = Rails::Html::SafeListSanitizer.allowed_attributes + allowed_attributes = Rails::Html::SafeListSanitizer.allowed_attributes.dup if additional_attributes.present? allowed_attributes = allowed_attributes.merge(additional_attributes) From 93e2dad656c0766fd0ad14e0957e45b34b24d577 Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 7 Jan 2023 11:11:08 +0000 Subject: [PATCH 18/53] DEV: Introduce syntax_tree code formatter (#19775) This commit introduces the necessary gems and config, but adds all our ruby code directories to the `--ignore-files` list. Future commits will apply syntax_tree to parts of the codebase, removing the ignore patterns as we go --- .github/workflows/linting.yml | 4 ++++ .rubocop.yml | 2 +- .streerc | 10 +++++++++- Gemfile | 3 +++ Gemfile.lock | 8 +++++++- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 14d4e59c5f..b2125eea16 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -68,6 +68,10 @@ jobs: if: ${{ !cancelled() }} run: bundle exec rubocop --parallel . + - name: syntax_tree + if: ${{ !cancelled() }} + run: bundle exec stree check Gemfile $(git ls-files '*.rb') $(git ls-files '*.rake') + - name: ESLint (core) if: ${{ !cancelled() }} run: yarn eslint app/assets/javascripts diff --git a/.rubocop.yml b/.rubocop.yml index b7edfe4894..4d4ad6d8d8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,5 @@ inherit_gem: - rubocop-discourse: default.yml + rubocop-discourse: stree-compat.yml # Still work to do in ensuring we don't link old files Discourse/NoAddReferenceOrAliasesActiveRecordMigration: diff --git a/.streerc b/.streerc index 0bc4379d46..3669f107c5 100644 --- a/.streerc +++ b/.streerc @@ -1,2 +1,10 @@ --print-width=100 ---plugins=plugin/trailing_comma +--plugins=plugin/trailing_comma,disable_ternary +--ignore-files=Gemfile +--ignore-files=app/* +--ignore-files=config/* +--ignore-files=db/* +--ignore-files=lib/* +--ignore-files=plugins/* +--ignore-files=script/* +--ignore-files=spec/* diff --git a/Gemfile b/Gemfile index 8aef1556fa..1069509ac6 100644 --- a/Gemfile +++ b/Gemfile @@ -169,6 +169,9 @@ group :test, :development do gem 'rswag-specs' gem 'annotate' + + gem "syntax_tree" + gem 'syntax_tree-disable_ternary' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 6bac0060e9..9c4fbc7b11 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -301,6 +301,7 @@ GEM parser (3.2.0.0) ast (~> 2.4.1) pg (1.4.5) + prettier_print (1.2.0) progress (3.6.0) pry (0.14.1) coderay (~> 1.1) @@ -408,7 +409,7 @@ GEM unicode-display_width (>= 1.4.0, < 3.0) rubocop-ast (1.24.1) parser (>= 3.1.1.0) - rubocop-discourse (3.0.1) + rubocop-discourse (3.0.2) rubocop (>= 1.1.0) rubocop-rspec (>= 2.0.0) rubocop-rspec (2.16.0) @@ -460,6 +461,9 @@ GEM sprockets (>= 3.0.0) sshkey (2.0.0) stackprof (0.2.23) + syntax_tree (5.2.0) + prettier_print (>= 1.2.0) + syntax_tree-disable_ternary (1.0.0) test-prof (1.1.0) thor (1.2.1) tilt (2.0.11) @@ -627,6 +631,8 @@ DEPENDENCIES sprockets-rails sshkey stackprof + syntax_tree + syntax_tree-disable_ternary test-prof thor uglifier From 055310cea496519a996b9c3bf4dc7e716cfe62ba Mon Sep 17 00:00:00 2001 From: David Taylor Date: Fri, 6 Jan 2023 20:42:16 +0000 Subject: [PATCH 19/53] DEV: Apply syntax_tree formatting to `plugins/*` --- .streerc | 1 - .../api/category_chatables_controller.rb | 5 +- .../app/controllers/api/hints_controller.rb | 11 +- .../app/jobs/regular/delete_user_messages.rb | 5 +- .../scheduled/delete_old_chat_messages.rb | 14 +- plugins/chat/app/models/chat_channel.rb | 1 - .../app/services/chat_message_destroyer.rb | 12 +- ...8_save_chat_allowed_groups_site_setting.rb | 6 +- plugins/chat/lib/chat_channel_fetcher.rb | 15 +- plugins/chat/lib/chat_mailer.rb | 9 +- plugins/chat/lib/chat_notifier.rb | 48 +- .../chat/lib/duplicate_message_validator.rb | 10 +- .../user_notifications_extension.rb | 14 +- .../components/chat_message_creator_spec.rb | 44 +- .../chat_message_rate_limiter_spec.rb | 12 +- .../chat/spec/fabricators/chat_fabricator.rb | 4 +- .../spec/integration/post_chat_quote_spec.rb | 14 +- .../spec/jobs/chat_channel_delete_spec.rb | 28 +- .../send_message_notifications_spec.rb | 19 +- .../spec/lib/chat_channel_fetcher_spec.rb | 27 +- .../spec/lib/chat_message_reactor_spec.rb | 2 +- plugins/chat/spec/lib/chat_notifier_spec.rb | 29 +- .../chat/spec/lib/chat_review_queue_spec.rb | 8 +- .../chat/spec/lib/guardian_extensions_spec.rb | 21 +- .../spec/mailers/user_notifications_spec.rb | 100 ++- plugins/chat/spec/models/chat_message_spec.rb | 3 +- .../spec/models/deleted_chat_user_spec.rb | 4 +- ..._notifications_settings_controller_spec.rb | 6 +- .../chat_channels_status_controller_spec.rb | 12 +- .../spec/requests/chat_controller_spec.rb | 6 +- .../core_ext/categories_controller_spec.rb | 2 +- .../services/chat_message_destroyer_spec.rb | 6 +- .../spec/support/examples/chatable_model.rb | 4 +- .../spec/system/hashtag_autocomplete_spec.rb | 18 +- .../spec/system/list_channels/mobile_spec.rb | 8 +- .../system/list_channels/no_sidebar_spec.rb | 8 +- .../spec/system/list_channels/sidebar_spec.rb | 8 +- .../spec/system/navigating_to_message_spec.rb | 42 +- .../spec/system/page_objects/chat/chat.rb | 4 +- plugins/discourse-details/plugin.rb | 36 +- .../spec/components/pretty_text_spec.rb | 24 +- plugins/discourse-local-dates/plugin.rb | 58 +- .../spec/integration/local_dates_spec.rb | 42 +- .../spec/lib/pretty_text_spec.rb | 144 +-- .../spec/models/post_spec.rb | 10 +- .../spec/system/local_dates_spec.rb | 19 +- .../discourse_narrative_bot/grant_badges.rb | 38 +- .../remap_old_bot_images.rb | 52 +- .../autoload/jobs/regular/bot_input.rb | 9 +- .../autoload/jobs/regular/narrative_init.rb | 6 +- .../regular/send_default_welcome_message.rb | 21 +- .../db/fixtures/001_discobot.rb | 8 +- .../db/fixtures/002_badges.rb | 38 +- .../lib/discourse_narrative_bot/actions.rb | 37 +- .../advanced_user_narrative.rb | 203 +++-- .../lib/discourse_narrative_bot/base.rb | 62 +- .../certificate_generator.rb | 14 +- .../lib/discourse_narrative_bot/dice.rb | 13 +- .../discourse_narrative_bot/magic_8_ball.rb | 7 +- .../new_user_narrative.rb | 283 +++--- .../quote_generator.rb | 13 +- .../discourse_narrative_bot/track_selector.rb | 99 +- .../welcome_post_type_site_setting.rb | 10 +- plugins/discourse-narrative-bot/plugin.rb | 179 ++-- .../advanced_user_narrative_spec.rb | 558 +++++++----- .../new_user_narrative_spec.rb | 849 ++++++++++-------- .../discourse_narrative_bot/store_spec.rb | 22 +- .../track_selector_spec.rb | 696 +++++++------- .../spec/jobs/onceoff/grant_badges_spec.rb | 13 +- .../jobs/onceoff/remap_old_bot_images_spec.rb | 23 +- .../jobs/send_default_welcome_message_spec.rb | 51 +- .../spec/lib/certificate_generator_spec.rb | 19 +- .../requests/discobot_certificate_spec.rb | 43 +- .../requests/discobot_welcome_post_spec.rb | 47 +- .../discourse-narrative-bot/spec/user_spec.rb | 117 ++- plugins/discourse-presence/plugin.rb | 25 +- .../spec/integration/presence_spec.rb | 61 +- plugins/lazy-yt/plugin.rb | 38 +- .../poll/app/controllers/polls_controller.rb | 12 +- plugins/poll/app/models/poll.rb | 28 +- .../poll/app/serializers/poll_serializer.rb | 7 +- ...0501152228_rename_total_votes_to_voters.rb | 42 +- .../20151016163051_merge_polls_votes.rb | 29 +- ...0321164925_close_polls_in_closed_topics.rb | 15 +- .../20180820073549_create_polls_tables.rb | 6 +- .../20180820080623_migrate_polls_data.rb | 117 +-- plugins/poll/jobs/regular/close_poll.rb | 11 +- plugins/poll/lib/poll.rb | 289 +++--- plugins/poll/lib/polls_updater.rb | 131 +-- plugins/poll/lib/polls_validator.rb | 90 +- plugins/poll/lib/post_validator.rb | 8 +- plugins/poll/lib/tasks/migrate_old_polls.rake | 96 +- plugins/poll/plugin.rb | 135 +-- .../spec/controllers/polls_controller_spec.rb | 332 ++++--- .../spec/controllers/posts_controller_spec.rb | 303 ++++--- .../spec/integration/poll_endpoints_spec.rb | 147 +-- .../poll/spec/jobs/regular/close_poll_spec.rb | 19 +- .../poll/spec/lib/new_post_manager_spec.rb | 11 +- plugins/poll/spec/lib/poll_spec.rb | 138 ++- plugins/poll/spec/lib/polls_updater_spec.rb | 76 +- plugins/poll/spec/lib/polls_validator_spec.rb | 86 +- plugins/poll/spec/lib/pretty_text_spec.rb | 37 +- plugins/poll/spec/models/poll_spec.rb | 12 +- .../spec/requests/users_controller_spec.rb | 2 +- .../poll_option_serializer_spec.rb | 24 +- .../styleguide/styleguide_controller.rb | 2 +- plugins/styleguide/config/routes.rb | 4 +- plugins/styleguide/plugin.rb | 8 +- .../spec/integration/access_spec.rb | 58 +- .../spec/integration/assets_spec.rb | 18 +- 110 files changed, 3712 insertions(+), 3158 deletions(-) diff --git a/.streerc b/.streerc index 3669f107c5..8a00459773 100644 --- a/.streerc +++ b/.streerc @@ -5,6 +5,5 @@ --ignore-files=config/* --ignore-files=db/* --ignore-files=lib/* ---ignore-files=plugins/* --ignore-files=script/* --ignore-files=spec/* diff --git a/plugins/chat/app/controllers/api/category_chatables_controller.rb b/plugins/chat/app/controllers/api/category_chatables_controller.rb index d3f93907d4..352326cda6 100644 --- a/plugins/chat/app/controllers/api/category_chatables_controller.rb +++ b/plugins/chat/app/controllers/api/category_chatables_controller.rb @@ -9,7 +9,10 @@ class Chat::Api::CategoryChatablesController < ApplicationController Group .joins(:category_groups) .where(category_groups: { category_id: category.id }) - .where("category_groups.permission_type IN (?)", [CategoryGroup.permission_types[:full], CategoryGroup.permission_types[:create_post]]) + .where( + "category_groups.permission_type IN (?)", + [CategoryGroup.permission_types[:full], CategoryGroup.permission_types[:create_post]], + ) .joins("LEFT OUTER JOIN group_users ON groups.id = group_users.group_id") .group("groups.id", "groups.name") .pluck("groups.name", "COUNT(group_users.user_id)") diff --git a/plugins/chat/app/controllers/api/hints_controller.rb b/plugins/chat/app/controllers/api/hints_controller.rb index b06d325c53..3a0635114b 100644 --- a/plugins/chat/app/controllers/api/hints_controller.rb +++ b/plugins/chat/app/controllers/api/hints_controller.rb @@ -9,16 +9,17 @@ class Chat::Api::HintsController < ApplicationController raise Discourse::InvalidParameters.new(:mentions) if group_names.blank? - visible_groups = Group - .where("LOWER(name) IN (?)", group_names) - .visible_groups(current_user) - .pluck(:name) + visible_groups = + Group.where("LOWER(name) IN (?)", group_names).visible_groups(current_user).pluck(:name) mentionable_groups = filter_mentionable_groups(visible_groups) result = { unreachable: visible_groups - mentionable_groups.map(&:name), - over_members_limit: mentionable_groups.select { |g| g.user_count > SiteSetting.max_users_notified_per_group_mention }.map(&:name), + over_members_limit: + mentionable_groups + .select { |g| g.user_count > SiteSetting.max_users_notified_per_group_mention } + .map(&:name), } result[:invalid] = (group_names - result[:unreachable]) - result[:over_members_limit] diff --git a/plugins/chat/app/jobs/regular/delete_user_messages.rb b/plugins/chat/app/jobs/regular/delete_user_messages.rb index ca52ba7386..22c35624ef 100644 --- a/plugins/chat/app/jobs/regular/delete_user_messages.rb +++ b/plugins/chat/app/jobs/regular/delete_user_messages.rb @@ -5,8 +5,9 @@ module Jobs def execute(args) return if args[:user_id].nil? - ChatMessageDestroyer.new - .destroy_in_batches(ChatMessage.with_deleted.where(user_id: args[:user_id])) + ChatMessageDestroyer.new.destroy_in_batches( + ChatMessage.with_deleted.where(user_id: args[:user_id]), + ) end end end diff --git a/plugins/chat/app/jobs/scheduled/delete_old_chat_messages.rb b/plugins/chat/app/jobs/scheduled/delete_old_chat_messages.rb index 6fa8519719..0fbc06141b 100644 --- a/plugins/chat/app/jobs/scheduled/delete_old_chat_messages.rb +++ b/plugins/chat/app/jobs/scheduled/delete_old_chat_messages.rb @@ -15,10 +15,9 @@ module Jobs return unless valid_day_value?(:chat_channel_retention_days) ChatMessageDestroyer.new.destroy_in_batches( - ChatMessage - .in_public_channel - .with_deleted - .created_before(SiteSetting.chat_channel_retention_days.days.ago) + ChatMessage.in_public_channel.with_deleted.created_before( + SiteSetting.chat_channel_retention_days.days.ago, + ), ) end @@ -26,10 +25,9 @@ module Jobs return unless valid_day_value?(:chat_dm_retention_days) ChatMessageDestroyer.new.destroy_in_batches( - ChatMessage - .in_dm_channel - .with_deleted - .created_before(SiteSetting.chat_dm_retention_days.days.ago) + ChatMessage.in_dm_channel.with_deleted.created_before( + SiteSetting.chat_dm_retention_days.days.ago, + ), ) end diff --git a/plugins/chat/app/models/chat_channel.rb b/plugins/chat/app/models/chat_channel.rb index f46746592d..389a16a4c1 100644 --- a/plugins/chat/app/models/chat_channel.rb +++ b/plugins/chat/app/models/chat_channel.rb @@ -89,7 +89,6 @@ class ChatChannel < ActiveRecord::Base # TODO (martin) Move UpdateUserCountsForChatChannels into here def self.update_counts - # NOTE: ChatChannel#messages_count is not updated every time # a message is created or deleted in a channel, so it should not # be displayed in the UI. It is updated eventually via Jobs::ChatPeriodicalUpdates diff --git a/plugins/chat/app/services/chat_message_destroyer.rb b/plugins/chat/app/services/chat_message_destroyer.rb index 311e1f9611..f5f159b816 100644 --- a/plugins/chat/app/services/chat_message_destroyer.rb +++ b/plugins/chat/app/services/chat_message_destroyer.rb @@ -2,11 +2,13 @@ class ChatMessageDestroyer def destroy_in_batches(chat_messages_query, batch_size: 200) - chat_messages_query.in_batches(of: batch_size).each do |relation| - destroyed_ids = relation.destroy_all.pluck(:id) - reset_last_read(destroyed_ids) - delete_flags(destroyed_ids) - end + chat_messages_query + .in_batches(of: batch_size) + .each do |relation| + destroyed_ids = relation.destroy_all.pluck(:id) + reset_last_read(destroyed_ids) + delete_flags(destroyed_ids) + end end private diff --git a/plugins/chat/db/migrate/20221122070108_save_chat_allowed_groups_site_setting.rb b/plugins/chat/db/migrate/20221122070108_save_chat_allowed_groups_site_setting.rb index 32d446ffc2..cea003abed 100644 --- a/plugins/chat/db/migrate/20221122070108_save_chat_allowed_groups_site_setting.rb +++ b/plugins/chat/db/migrate/20221122070108_save_chat_allowed_groups_site_setting.rb @@ -2,10 +2,12 @@ class SaveChatAllowedGroupsSiteSetting < ActiveRecord::Migration[7.0] def up - chat_enabled = DB.query_single("SELECT value FROM site_settings WHERE name = 'chat_enabled' AND value = 't'") + chat_enabled = + DB.query_single("SELECT value FROM site_settings WHERE name = 'chat_enabled' AND value = 't'") return if chat_enabled.blank? - chat_allowed_groups = DB.query_single("SELECT value FROM site_settings WHERE name = 'chat_allowed_groups'") + chat_allowed_groups = + DB.query_single("SELECT value FROM site_settings WHERE name = 'chat_allowed_groups'") return if chat_allowed_groups.present? # The original default was auto group ID 3 (staff) so we are diff --git a/plugins/chat/lib/chat_channel_fetcher.rb b/plugins/chat/lib/chat_channel_fetcher.rb index f0e90fb3dc..028e0f3643 100644 --- a/plugins/chat/lib/chat_channel_fetcher.rb +++ b/plugins/chat/lib/chat_channel_fetcher.rb @@ -30,10 +30,14 @@ module Chat::ChatChannelFetcher end def self.generate_allowed_channel_ids_sql(guardian, exclude_dm_channels: false) - category_channel_sql = Category.post_create_allowed(guardian) - .joins("INNER JOIN chat_channels ON chat_channels.chatable_id = categories.id AND chat_channels.chatable_type = 'Category'") - .select("chat_channels.id") - .to_sql + category_channel_sql = + Category + .post_create_allowed(guardian) + .joins( + "INNER JOIN chat_channels ON chat_channels.chatable_id = categories.id AND chat_channels.chatable_type = 'Category'", + ) + .select("chat_channels.id") + .to_sql dm_channel_sql = "" if !exclude_dm_channels dm_channel_sql = <<~SQL @@ -75,8 +79,7 @@ module Chat::ChatChannelFetcher end def self.secured_public_channel_search(guardian, options = {}) - allowed_channel_ids = - generate_allowed_channel_ids_sql(guardian, exclude_dm_channels: true) + allowed_channel_ids = generate_allowed_channel_ids_sql(guardian, exclude_dm_channels: true) channels = ChatChannel.includes(chatable: [:topic_only_relative_url]) channels = channels.includes(:chat_channel_archive) if options[:include_archives] diff --git a/plugins/chat/lib/chat_mailer.rb b/plugins/chat/lib/chat_mailer.rb index 8c914b497d..7600a25fe4 100644 --- a/plugins/chat/lib/chat_mailer.rb +++ b/plugins/chat/lib/chat_mailer.rb @@ -32,10 +32,11 @@ class Chat::ChatMailer when_away_frequency = UserOption.chat_email_frequencies[:when_away] allowed_group_ids = Chat.allowed_group_ids - users = User - .joins(:user_option) - .where(user_options: { chat_enabled: true, chat_email_frequency: when_away_frequency }) - .where("users.last_seen_at < ?", 15.minutes.ago) + users = + User + .joins(:user_option) + .where(user_options: { chat_enabled: true, chat_email_frequency: when_away_frequency }) + .where("users.last_seen_at < ?", 15.minutes.ago) if !allowed_group_ids.include?(Group::AUTO_GROUPS[:everyone]) users = users.joins(:groups).where(groups: { id: allowed_group_ids }) diff --git a/plugins/chat/lib/chat_notifier.rb b/plugins/chat/lib/chat_notifier.rb index ff34fdecba..017aa1b320 100644 --- a/plugins/chat/lib/chat_notifier.rb +++ b/plugins/chat/lib/chat_notifier.rb @@ -41,7 +41,7 @@ class Chat::ChatNotifier :send_message_notifications, chat_message_id: chat_message.id, timestamp: timestamp.iso8601(6), - reason: :edit + reason: :edit, ) end @@ -50,7 +50,7 @@ class Chat::ChatNotifier :send_message_notifications, chat_message_id: chat_message.id, timestamp: timestamp.iso8601(6), - reason: :new + reason: :new, ) end end @@ -112,8 +112,7 @@ class Chat::ChatNotifier group_mentions_count = group_name_mentions.length skip_notifications = - (direct_mentions_count + group_mentions_count) > - SiteSetting.max_mentions_per_chat_message + (direct_mentions_count + group_mentions_count) > SiteSetting.max_mentions_per_chat_message {}.tap do |to_notify| # The order of these methods is the precedence @@ -248,24 +247,21 @@ class Chat::ChatNotifier end def visible_groups - @visible_groups ||= - Group - .where("LOWER(name) IN (?)", group_name_mentions) - .visible_groups(@user) + @visible_groups ||= Group.where("LOWER(name) IN (?)", group_name_mentions).visible_groups(@user) end def expand_group_mentions(to_notify, already_covered_ids, skip) return [] if skip || visible_groups.empty? - mentionable_groups = Group - .mentionable(@user, include_public: false) - .where(id: visible_groups.map(&:id)) + mentionable_groups = + Group.mentionable(@user, include_public: false).where(id: visible_groups.map(&:id)) mentions_disabled = visible_groups - mentionable_groups - too_many_members, mentionable = mentionable_groups.partition do |group| - group.user_count > SiteSetting.max_users_notified_per_group_mention - end + too_many_members, mentionable = + mentionable_groups.partition do |group| + group.user_count > SiteSetting.max_users_notified_per_group_mention + end to_notify[:group_mentions_disabled] = mentions_disabled to_notify[:too_many_members] = too_many_members @@ -275,7 +271,9 @@ class Chat::ChatNotifier reached_by_group = chat_users .includes(:groups) - .joins(:groups).where(groups: mentionable).where.not(id: already_covered_ids) + .joins(:groups) + .where(groups: mentionable) + .where.not(id: already_covered_ids) grouped = group_users_to_notify(reached_by_group) @@ -295,7 +293,13 @@ class Chat::ChatNotifier end def notify_creator_of_inaccessible_mentions(to_notify) - inaccessible = to_notify.extract!(:unreachable, :welcome_to_join, :too_many_members, :group_mentions_disabled) + inaccessible = + to_notify.extract!( + :unreachable, + :welcome_to_join, + :too_many_members, + :group_mentions_disabled, + ) return if inaccessible.values.all?(&:blank?) ChatPublisher.publish_inaccessible_mentions( @@ -304,7 +308,7 @@ class Chat::ChatNotifier inaccessible[:unreachable].to_a, inaccessible[:welcome_to_join].to_a, inaccessible[:too_many_members].to_a, - inaccessible[:group_mentions_disabled].to_a + inaccessible[:group_mentions_disabled].to_a, ) end @@ -321,9 +325,7 @@ class Chat::ChatNotifier to_notify .except(:unreachable, :welcome_to_join) .each do |key, user_ids| - to_notify[key] = user_ids.reject do |user_id| - screener.ignoring_or_muting_actor?(user_id) - end + to_notify[key] = user_ids.reject { |user_id| screener.ignoring_or_muting_actor?(user_id) } end # :welcome_to_join contains users because it's serialized by MB. @@ -351,11 +353,7 @@ class Chat::ChatNotifier def notify_watching_users(except: []) Jobs.enqueue( :chat_notify_watching, - { - chat_message_id: @chat_message.id, - except_user_ids: except, - timestamp: @timestamp, - }, + { chat_message_id: @chat_message.id, except_user_ids: except, timestamp: @timestamp }, ) end end diff --git a/plugins/chat/lib/duplicate_message_validator.rb b/plugins/chat/lib/duplicate_message_validator.rb index c66420f9d7..7b094692ff 100644 --- a/plugins/chat/lib/duplicate_message_validator.rb +++ b/plugins/chat/lib/duplicate_message_validator.rb @@ -22,11 +22,11 @@ class Chat::DuplicateMessageValidator # Check if the same duplicate message has been posted in the last N seconds by any user if !chat_message - .chat_channel - .chat_messages - .where("created_at > ?", matrix[:min_past_seconds].seconds.ago) - .where(message: chat_message.message) - .exists? + .chat_channel + .chat_messages + .where("created_at > ?", matrix[:min_past_seconds].seconds.ago) + .where(message: chat_message.message) + .exists? return end diff --git a/plugins/chat/lib/extensions/user_notifications_extension.rb b/plugins/chat/lib/extensions/user_notifications_extension.rb index 8b3723cdb8..a054b108d8 100644 --- a/plugins/chat/lib/extensions/user_notifications_extension.rb +++ b/plugins/chat/lib/extensions/user_notifications_extension.rb @@ -85,27 +85,27 @@ module Chat::UserNotificationsExtension "user_notifications.chat_summary.subject.chat_channel_more", email_prefix: @email_prefix, channel: channels.first.title, - count: total_count - 1 + count: total_count - 1, ) elsif channels.size == 1 && dm_users.size == 0 I18n.t( "user_notifications.chat_summary.subject.chat_channel_1", email_prefix: @email_prefix, - channel: channels.first.title + channel: channels.first.title, ) elsif channels.size == 1 && dm_users.size == 1 I18n.t( "user_notifications.chat_summary.subject.chat_channel_and_direct_message", email_prefix: @email_prefix, channel: channels.first.title, - username: dm_users.first.username + username: dm_users.first.username, ) elsif channels.size == 2 I18n.t( "user_notifications.chat_summary.subject.chat_channel_2", email_prefix: @email_prefix, channel1: channels.first.title, - channel2: channels.second.title + channel2: channels.second.title, ) end end @@ -116,21 +116,21 @@ module Chat::UserNotificationsExtension I18n.t( "user_notifications.chat_summary.subject.direct_message_from_1", email_prefix: @email_prefix, - username: dm_users.first.username + username: dm_users.first.username, ) when 2 I18n.t( "user_notifications.chat_summary.subject.direct_message_from_2", email_prefix: @email_prefix, username1: dm_users.first.username, - username2: dm_users.second.username + username2: dm_users.second.username, ) else I18n.t( "user_notifications.chat_summary.subject.direct_message_from_more", email_prefix: @email_prefix, username: dm_users.first.username, - count: dm_users.size - 1 + count: dm_users.size - 1, ) end end diff --git a/plugins/chat/spec/components/chat_message_creator_spec.rb b/plugins/chat/spec/components/chat_message_creator_spec.rb index 6f87534fa2..45f3a4ad2e 100644 --- a/plugins/chat/spec/components/chat_message_creator_spec.rb +++ b/plugins/chat/spec/components/chat_message_creator_spec.rb @@ -32,10 +32,7 @@ describe Chat::ChatMessageCreator do ) end let(:direct_message_channel) do - Chat::DirectMessageChannelCreator.create!( - acting_user: user1, - target_users: [user1, user2], - ) + Chat::DirectMessageChannelCreator.create!(acting_user: user1, target_users: [user1, user2]) end before do @@ -135,13 +132,14 @@ describe Chat::ChatMessageCreator do end it "publishes a DiscourseEvent for new messages" do - events = DiscourseEvent.track_events { - Chat::ChatMessageCreator.create( - chat_channel: public_chat_channel, - user: user1, - content: "this is a message", - ) - } + events = + DiscourseEvent.track_events do + Chat::ChatMessageCreator.create( + chat_channel: public_chat_channel, + user: user1, + content: "this is a message", + ) + end expect(events.map { _1[:event_name] }).to include(:chat_message_created) end @@ -368,8 +366,8 @@ describe Chat::ChatMessageCreator do content: "hello @#{admin_group.name}", ) }.to change { admin1.chat_mentions.count }.by(1).and change { - admin2.chat_mentions.count - }.by(1) + admin2.chat_mentions.count + }.by(1) end it "doesn't mention users twice if they are direct mentioned and group mentioned" do @@ -380,8 +378,8 @@ describe Chat::ChatMessageCreator do content: "hello @#{admin_group.name} @#{admin1.username} and @#{admin2.username}", ) }.to change { admin1.chat_mentions.count }.by(1).and change { - admin2.chat_mentions.count - }.by(1) + admin2.chat_mentions.count + }.by(1) end it "creates chat mentions for group mentions and direct mentions" do @@ -392,8 +390,8 @@ describe Chat::ChatMessageCreator do content: "hello @#{admin_group.name} @#{user2.username}", ) }.to change { admin1.chat_mentions.count }.by(1).and change { - admin2.chat_mentions.count - }.by(1).and change { user2.chat_mentions.count }.by(1) + admin2.chat_mentions.count + }.by(1).and change { user2.chat_mentions.count }.by(1) end it "creates chat mentions for group mentions and direct mentions" do @@ -404,10 +402,10 @@ describe Chat::ChatMessageCreator do content: "hello @#{admin_group.name} @#{user_group.name}", ) }.to change { admin1.chat_mentions.count }.by(1).and change { - admin2.chat_mentions.count - }.by(1).and change { user2.chat_mentions.count }.by(1).and change { - user3.chat_mentions.count - }.by(1) + admin2.chat_mentions.count + }.by(1).and change { user2.chat_mentions.count }.by(1).and change { + user3.chat_mentions.count + }.by(1) end it "doesn't create chat mentions for group mentions where the group is un-mentionable" do @@ -475,8 +473,8 @@ describe Chat::ChatMessageCreator do upload_ids: [upload1.id, upload2.id], ) }.to change { ChatUpload.where(upload_id: upload1.id).count }.by(1).and change { - ChatUpload.where(upload_id: upload2.id).count - }.by(1) + ChatUpload.where(upload_id: upload2.id).count + }.by(1) end it "filters out uploads that weren't uploaded by the user" do diff --git a/plugins/chat/spec/components/chat_message_rate_limiter_spec.rb b/plugins/chat/spec/components/chat_message_rate_limiter_spec.rb index b91616a359..fa73e92781 100644 --- a/plugins/chat/spec/components/chat_message_rate_limiter_spec.rb +++ b/plugins/chat/spec/components/chat_message_rate_limiter_spec.rb @@ -64,11 +64,11 @@ describe Chat::ChatMessageRateLimiter do limiter.run! expect { limiter.run! }.to raise_error(RateLimiter::LimitExceeded).and change { - UserHistory.where( - target_user: user, - acting_user: Discourse.system_user, - action: UserHistory.actions[:silence_user], - ).count - }.by(1) + UserHistory.where( + target_user: user, + acting_user: Discourse.system_user, + action: UserHistory.actions[:silence_user], + ).count + }.by(1) end end diff --git a/plugins/chat/spec/fabricators/chat_fabricator.rb b/plugins/chat/spec/fabricators/chat_fabricator.rb index 4ecd7bfab3..e123c06bc5 100644 --- a/plugins/chat/spec/fabricators/chat_fabricator.rb +++ b/plugins/chat/spec/fabricators/chat_fabricator.rb @@ -76,9 +76,7 @@ end Fabricator(:chat_upload) do transient :user - user do - Fabricate(:user) - end + user { Fabricate(:user) } chat_message { |attrs| Fabricate(:chat_message, user: attrs[:user]) } upload { |attrs| Fabricate(:upload, user: attrs[:user]) } diff --git a/plugins/chat/spec/integration/post_chat_quote_spec.rb b/plugins/chat/spec/integration/post_chat_quote_spec.rb index 51d5c327cf..a49ae55665 100644 --- a/plugins/chat/spec/integration/post_chat_quote_spec.rb +++ b/plugins/chat/spec/integration/post_chat_quote_spec.rb @@ -219,9 +219,19 @@ martin channel = Fabricate(:chat_channel) message1 = Fabricate(:chat_message, chat_channel: channel, user: post.user) message2 = Fabricate(:chat_message, chat_channel: channel, user: post.user) - md = ChatTranscriptService.new(channel, message2.user, messages_or_ids: [message2.id]).generate_markdown + md = + ChatTranscriptService.new( + channel, + message2.user, + messages_or_ids: [message2.id], + ).generate_markdown message1.update!(message: md) - md_for_post = ChatTranscriptService.new(channel, message1.user, messages_or_ids: [message1.id]).generate_markdown + md_for_post = + ChatTranscriptService.new( + channel, + message1.user, + messages_or_ids: [message1.id], + ).generate_markdown post.update!(raw: md_for_post) expect(post.cooked.chomp).to eq(<<~COOKED.chomp)
diff --git a/plugins/chat/spec/jobs/chat_channel_delete_spec.rb b/plugins/chat/spec/jobs/chat_channel_delete_spec.rb index 55176fdd60..033274f04c 100644 --- a/plugins/chat/spec/jobs/chat_channel_delete_spec.rb +++ b/plugins/chat/spec/jobs/chat_channel_delete_spec.rb @@ -56,23 +56,23 @@ describe Jobs::ChatChannelDelete do expect { described_class.new.execute(chat_channel_id: chat_channel.id) }.to change { IncomingChatWebhook.where(chat_channel_id: chat_channel.id).count }.by(-1).and change { - ChatWebhookEvent.where(incoming_chat_webhook_id: @incoming_chat_webhook_id).count - }.by(-1).and change { ChatDraft.where(chat_channel: chat_channel).count }.by( + ChatWebhookEvent.where(incoming_chat_webhook_id: @incoming_chat_webhook_id).count + }.by(-1).and change { ChatDraft.where(chat_channel: chat_channel).count }.by( -1, ).and change { UserChatChannelMembership.where(chat_channel: chat_channel).count }.by(-3).and change { - ChatMessageRevision.where(chat_message_id: @message_ids).count - }.by(-1).and change { - ChatMention.where(chat_message_id: @message_ids).count - }.by(-1).and change { - ChatUpload.where(chat_message_id: @message_ids).count - }.by(-10).and change { - ChatMessage.where(id: @message_ids).count - }.by(-20).and change { - ChatMessageReaction.where( - chat_message_id: @message_ids, - ).count - }.by(-10) + ChatMessageRevision.where(chat_message_id: @message_ids).count + }.by(-1).and change { + ChatMention.where(chat_message_id: @message_ids).count + }.by(-1).and change { + ChatUpload.where(chat_message_id: @message_ids).count + }.by(-10).and change { + ChatMessage.where(id: @message_ids).count + }.by(-20).and change { + ChatMessageReaction.where( + chat_message_id: @message_ids, + ).count + }.by(-10) end end diff --git a/plugins/chat/spec/jobs/regular/send_message_notifications_spec.rb b/plugins/chat/spec/jobs/regular/send_message_notifications_spec.rb index eee9303438..e00bad83f5 100644 --- a/plugins/chat/spec/jobs/regular/send_message_notifications_spec.rb +++ b/plugins/chat/spec/jobs/regular/send_message_notifications_spec.rb @@ -21,7 +21,7 @@ RSpec.describe Jobs::SendMessageNotifications do subject.execute( chat_message_id: chat_message.id, reason: "invalid", - timestamp: 1.minute.ago + timestamp: 1.minute.ago, ) end @@ -29,32 +29,21 @@ RSpec.describe Jobs::SendMessageNotifications do Chat::ChatNotifier.any_instance.expects(:notify_new).never Chat::ChatNotifier.any_instance.expects(:notify_edit).never - subject.execute( - chat_message_id: chat_message.id, - reason: "new" - ) + subject.execute(chat_message_id: chat_message.id, reason: "new") end it "calls notify_new when the reason is 'new'" do Chat::ChatNotifier.any_instance.expects(:notify_new).once Chat::ChatNotifier.any_instance.expects(:notify_edit).never - subject.execute( - chat_message_id: chat_message.id, - reason: "new", - timestamp: 1.minute.ago - ) + subject.execute(chat_message_id: chat_message.id, reason: "new", timestamp: 1.minute.ago) end it "calls notify_edit when the reason is 'edit'" do Chat::ChatNotifier.any_instance.expects(:notify_new).never Chat::ChatNotifier.any_instance.expects(:notify_edit).once - subject.execute( - chat_message_id: chat_message.id, - reason: "edit", - timestamp: 1.minute.ago - ) + subject.execute(chat_message_id: chat_message.id, reason: "edit", timestamp: 1.minute.ago) end end end diff --git a/plugins/chat/spec/lib/chat_channel_fetcher_spec.rb b/plugins/chat/spec/lib/chat_channel_fetcher_spec.rb index 7de79fe043..46a1f39419 100644 --- a/plugins/chat/spec/lib/chat_channel_fetcher_spec.rb +++ b/plugins/chat/spec/lib/chat_channel_fetcher_spec.rb @@ -142,17 +142,38 @@ describe Chat::ChatChannelFetcher do fab!(:group_user) { Fabricate(:group_user, group: group, user: user1) } it "does not include the category channel for member of group with readonly access" do - category_channel.update!(chatable: Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:readonly])) + category_channel.update!( + chatable: + Fabricate( + :private_category, + group: group, + permission_type: CategoryGroup.permission_types[:readonly], + ), + ) expect(subject.all_secured_channel_ids(guardian)).to be_empty end it "includes the category channel for member of group with create_post access" do - category_channel.update!(chatable: Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:create_post])) + category_channel.update!( + chatable: + Fabricate( + :private_category, + group: group, + permission_type: CategoryGroup.permission_types[:create_post], + ), + ) expect(subject.all_secured_channel_ids(guardian)).to match_array([category_channel.id]) end it "includes the category channel for member of group with full access" do - category_channel.update!(chatable: Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:full])) + category_channel.update!( + chatable: + Fabricate( + :private_category, + group: group, + permission_type: CategoryGroup.permission_types[:full], + ), + ) expect(subject.all_secured_channel_ids(guardian)).to match_array([category_channel.id]) end end diff --git a/plugins/chat/spec/lib/chat_message_reactor_spec.rb b/plugins/chat/spec/lib/chat_message_reactor_spec.rb index 3245f248f0..565fab80db 100644 --- a/plugins/chat/spec/lib/chat_message_reactor_spec.rb +++ b/plugins/chat/spec/lib/chat_message_reactor_spec.rb @@ -9,7 +9,7 @@ describe Chat::ChatMessageReactor do fab!(:message_1) { Fabricate(:chat_message, chat_channel: channel, user: reacting_user) } let(:subject) { described_class.new(reacting_user, channel) } - it 'calls guardian ensure_can_join_chat_channel!' do + it "calls guardian ensure_can_join_chat_channel!" do Guardian.any_instance.expects(:ensure_can_join_chat_channel!).once subject.react!(message_id: message_1.id, react_action: :add, emoji: ":+1:") end diff --git a/plugins/chat/spec/lib/chat_notifier_spec.rb b/plugins/chat/spec/lib/chat_notifier_spec.rb index 4b6ddbf0b6..e9f413cfd6 100644 --- a/plugins/chat/spec/lib/chat_notifier_spec.rb +++ b/plugins/chat/spec/lib/chat_notifier_spec.rb @@ -275,7 +275,7 @@ describe Chat::ChatNotifier do include_examples "ensure only channel members are notified" - it 'calls guardian can_join_chat_channel?' do + it "calls guardian can_join_chat_channel?" do Guardian.any_instance.expects(:can_join_chat_channel?).at_least_once msg = build_cooked_msg("Hello @#{group.name} and @#{user_2.username}", user_1) to_notify = described_class.new(msg, msg.created_at).notify_new @@ -463,7 +463,8 @@ describe Chat::ChatNotifier do expect(not_participating_msg).to be_present expect(not_participating_msg.data[:cannot_see]).to be_empty - not_participating_users = not_participating_msg.data[:without_membership].map { |u| u["id"] } + not_participating_users = + not_participating_msg.data[:without_membership].map { |u| u["id"] } expect(not_participating_users).to contain_exactly(user_3.id) end @@ -515,7 +516,8 @@ describe Chat::ChatNotifier do expect(not_participating_msg).to be_present expect(not_participating_msg.data[:cannot_see]).to be_empty - not_participating_users = not_participating_msg.data[:without_membership].map { |u| u["id"] } + not_participating_users = + not_participating_msg.data[:without_membership].map { |u| u["id"] } expect(not_participating_users).to contain_exactly(user_3.id) end @@ -539,7 +541,8 @@ describe Chat::ChatNotifier do expect(not_participating_msg).to be_present expect(not_participating_msg.data[:cannot_see]).to be_empty - not_participating_users = not_participating_msg.data[:without_membership].map { |u| u["id"] } + not_participating_users = + not_participating_msg.data[:without_membership].map { |u| u["id"] } expect(not_participating_users).to contain_exactly(user_3.id) end @@ -598,11 +601,12 @@ describe Chat::ChatNotifier do SiteSetting.max_users_notified_per_group_mention = (group.user_count - 1) msg = build_cooked_msg("Hello @#{group.name}", user_1) - messages = MessageBus.track_publish("/chat/#{channel.id}") do - to_notify = described_class.new(msg, msg.created_at).notify_new + messages = + MessageBus.track_publish("/chat/#{channel.id}") do + to_notify = described_class.new(msg, msg.created_at).notify_new - expect(to_notify[group.name]).to be_nil - end + expect(to_notify[group.name]).to be_nil + end too_many_members_msg = messages.first expect(too_many_members_msg).to be_present @@ -614,11 +618,12 @@ describe Chat::ChatNotifier do group.update!(mentionable_level: Group::ALIAS_LEVELS[:only_admins]) msg = build_cooked_msg("Hello @#{group.name}", user_1) - messages = MessageBus.track_publish("/chat/#{channel.id}") do - to_notify = described_class.new(msg, msg.created_at).notify_new + messages = + MessageBus.track_publish("/chat/#{channel.id}") do + to_notify = described_class.new(msg, msg.created_at).notify_new - expect(to_notify[group.name]).to be_nil - end + expect(to_notify[group.name]).to be_nil + end mentions_disabled_msg = messages.first expect(mentions_disabled_msg).to be_present diff --git a/plugins/chat/spec/lib/chat_review_queue_spec.rb b/plugins/chat/spec/lib/chat_review_queue_spec.rb index 23433dab01..5559543c52 100644 --- a/plugins/chat/spec/lib/chat_review_queue_spec.rb +++ b/plugins/chat/spec/lib/chat_review_queue_spec.rb @@ -46,7 +46,7 @@ describe Chat::ChatReviewQueue do it "returns an error" do expect(second_flag_result).to include success: false, - errors: [I18n.t("chat.reviewables.message_already_handled")] + errors: [I18n.t("chat.reviewables.message_already_handled")] end it "returns an error when trying to use notify_moderators and the previous flag is still pending" do @@ -59,7 +59,7 @@ describe Chat::ChatReviewQueue do ) expect(notify_moderators_result).to include success: false, - errors: [I18n.t("chat.reviewables.message_already_handled")] + errors: [I18n.t("chat.reviewables.message_already_handled")] end end @@ -87,7 +87,7 @@ describe Chat::ChatReviewQueue do queue.flag_message(message, admin_guardian, ReviewableScore.types[:spam]) expect(second_flag_result).to include success: false, - errors: [I18n.t("chat.reviewables.message_already_handled")] + errors: [I18n.t("chat.reviewables.message_already_handled")] end end @@ -105,7 +105,7 @@ describe Chat::ChatReviewQueue do it "raises an error when we are inside the cooldown window" do expect(second_flag_result).to include success: false, - errors: [I18n.t("chat.reviewables.message_already_handled")] + errors: [I18n.t("chat.reviewables.message_already_handled")] end it "allows the user to re-flag after the cooldown period" do diff --git a/plugins/chat/spec/lib/guardian_extensions_spec.rb b/plugins/chat/spec/lib/guardian_extensions_spec.rb index 02da5c577b..e2e4c4cbc7 100644 --- a/plugins/chat/spec/lib/guardian_extensions_spec.rb +++ b/plugins/chat/spec/lib/guardian_extensions_spec.rb @@ -92,17 +92,32 @@ RSpec.describe Chat::GuardianExtensions do fab!(:group_user) { Fabricate(:group_user, group: group, user: user) } it "returns true if the user can join the category" do - category = Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:readonly]) + category = + Fabricate( + :private_category, + group: group, + permission_type: CategoryGroup.permission_types[:readonly], + ) channel.update(chatable: category) guardian = Guardian.new(user) expect(guardian.can_join_chat_channel?(channel)).to eq(false) - category = Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:create_post]) + category = + Fabricate( + :private_category, + group: group, + permission_type: CategoryGroup.permission_types[:create_post], + ) channel.update(chatable: category) guardian = Guardian.new(user) expect(guardian.can_join_chat_channel?(channel)).to eq(true) - category = Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:full]) + category = + Fabricate( + :private_category, + group: group, + permission_type: CategoryGroup.permission_types[:full], + ) channel.update(chatable: category) guardian = Guardian.new(user) expect(guardian.can_join_chat_channel?(channel)).to eq(true) diff --git a/plugins/chat/spec/mailers/user_notifications_spec.rb b/plugins/chat/spec/mailers/user_notifications_spec.rb index 943739bb82..fa282a8a13 100644 --- a/plugins/chat/spec/mailers/user_notifications_spec.rb +++ b/plugins/chat/spec/mailers/user_notifications_spec.rb @@ -25,7 +25,7 @@ describe UserNotifications do Chat::DirectMessageChannelCreator.create!(acting_user: sender, target_users: [sender, user]) end - it 'calls guardian can_join_chat_channel?' do + it "calls guardian can_join_chat_channel?" do Fabricate(:chat_message, user: sender, chat_channel: channel) Guardian.any_instance.expects(:can_join_chat_channel?).once email = described_class.chat_summary(user, {}) @@ -34,11 +34,12 @@ describe UserNotifications do describe "email subject" do it "includes the sender username in the subject" do - expected_subject = I18n.t( - "user_notifications.chat_summary.subject.direct_message_from_1", - email_prefix: SiteSetting.title, - username: sender.username - ) + expected_subject = + I18n.t( + "user_notifications.chat_summary.subject.direct_message_from_1", + email_prefix: SiteSetting.title, + username: sender.username, + ) Fabricate(:chat_message, user: sender, chat_channel: channel) email = described_class.chat_summary(user, {}) @@ -54,11 +55,12 @@ describe UserNotifications do chat_channel: channel, ) DirectMessageUser.create!(direct_message: channel.chatable, user: another_participant) - expected_subject = I18n.t( - "user_notifications.chat_summary.subject.direct_message_from_1", - email_prefix: SiteSetting.title, - username: sender.username - ) + expected_subject = + I18n.t( + "user_notifications.chat_summary.subject.direct_message_from_1", + email_prefix: SiteSetting.title, + username: sender.username, + ) Fabricate(:chat_message, user: sender, chat_channel: channel) email = described_class.chat_summary(user, {}) @@ -80,12 +82,13 @@ describe UserNotifications do Fabricate(:chat_message, user: sender, chat_channel: channel) email = described_class.chat_summary(user, {}) - expected_subject = I18n.t( - "user_notifications.chat_summary.subject.direct_message_from_2", - email_prefix: SiteSetting.title, - username1: another_dm_user.username, - username2: sender.username - ) + expected_subject = + I18n.t( + "user_notifications.chat_summary.subject.direct_message_from_2", + email_prefix: SiteSetting.title, + username1: another_dm_user.username, + username2: sender.username, + ) expect(email.subject).to eq(expected_subject) expect(email.subject).to include(sender.username) @@ -116,12 +119,13 @@ describe UserNotifications do email = described_class.chat_summary(user, {}) - expected_subject = I18n.t( - "user_notifications.chat_summary.subject.direct_message_from_more", - email_prefix: SiteSetting.title, - username: senders.first.username, - count: 2 - ) + expected_subject = + I18n.t( + "user_notifications.chat_summary.subject.direct_message_from_more", + email_prefix: SiteSetting.title, + username: senders.first.username, + count: 2, + ) expect(email.subject).to eq(expected_subject) end @@ -162,11 +166,12 @@ describe UserNotifications do before { Fabricate(:chat_mention, user: user, chat_message: chat_message) } it "includes the sender username in the subject" do - expected_subject = I18n.t( - "user_notifications.chat_summary.subject.chat_channel_1", - email_prefix: SiteSetting.title, - channel: channel.title(user) - ) + expected_subject = + I18n.t( + "user_notifications.chat_summary.subject.chat_channel_1", + email_prefix: SiteSetting.title, + channel: channel.title(user), + ) email = described_class.chat_summary(user, {}) @@ -193,12 +198,13 @@ describe UserNotifications do email = described_class.chat_summary(user, {}) - expected_subject = I18n.t( - "user_notifications.chat_summary.subject.chat_channel_2", - email_prefix: SiteSetting.title, - channel1: channel.title(user), - channel2: another_chat_channel.title(user) - ) + expected_subject = + I18n.t( + "user_notifications.chat_summary.subject.chat_channel_2", + email_prefix: SiteSetting.title, + channel1: channel.title(user), + channel2: another_chat_channel.title(user), + ) expect(email.subject).to eq(expected_subject) expect(email.subject).to include(channel.title(user)) @@ -224,12 +230,13 @@ describe UserNotifications do Fabricate(:chat_mention, user: user, chat_message: another_chat_message) end - expected_subject = I18n.t( - "user_notifications.chat_summary.subject.chat_channel_more", - email_prefix: SiteSetting.title, - channel: channel.title(user), - count: 2 - ) + expected_subject = + I18n.t( + "user_notifications.chat_summary.subject.chat_channel_more", + email_prefix: SiteSetting.title, + channel: channel.title(user), + count: 2, + ) email = described_class.chat_summary(user, {}) @@ -250,12 +257,13 @@ describe UserNotifications do end it "always includes the DM second" do - expected_subject = I18n.t( - "user_notifications.chat_summary.subject.chat_channel_and_direct_message", - email_prefix: SiteSetting.title, - channel: channel.title(user), - username: sender.username - ) + expected_subject = + I18n.t( + "user_notifications.chat_summary.subject.chat_channel_and_direct_message", + email_prefix: SiteSetting.title, + channel: channel.title(user), + username: sender.username, + ) email = described_class.chat_summary(user, {}) diff --git a/plugins/chat/spec/models/chat_message_spec.rb b/plugins/chat/spec/models/chat_message_spec.rb index 89d1920eac..1ce6a39ed3 100644 --- a/plugins/chat/spec/models/chat_message_spec.rb +++ b/plugins/chat/spec/models/chat_message_spec.rb @@ -517,7 +517,8 @@ describe ChatMessage do it "keeps the same hashtags the user has permission to after rebake" do group.add(chat_message.user) chat_message.update!( - message: "this is the message ##{category.slug} ##{secure_category.slug} ##{chat_message.chat_channel.slug}", + message: + "this is the message ##{category.slug} ##{secure_category.slug} ##{chat_message.chat_channel.slug}", ) chat_message.cook chat_message.save! diff --git a/plugins/chat/spec/models/deleted_chat_user_spec.rb b/plugins/chat/spec/models/deleted_chat_user_spec.rb index 92617c58f7..387eb5c89f 100644 --- a/plugins/chat/spec/models/deleted_chat_user_spec.rb +++ b/plugins/chat/spec/models/deleted_chat_user_spec.rb @@ -11,9 +11,7 @@ describe DeletedChatUser do describe "#avatar_template" do it "returns a default path" do - expect(subject.avatar_template).to eq( - "/plugins/chat/images/deleted-chat-user-avatar.png", - ) + expect(subject.avatar_template).to eq("/plugins/chat/images/deleted-chat-user-avatar.png") end end end diff --git a/plugins/chat/spec/requests/api/chat_channels_current_user_notifications_settings_controller_spec.rb b/plugins/chat/spec/requests/api/chat_channels_current_user_notifications_settings_controller_spec.rb index bf0dc2c353..f5fe1f3ae3 100644 --- a/plugins/chat/spec/requests/api/chat_channels_current_user_notifications_settings_controller_spec.rb +++ b/plugins/chat/spec/requests/api/chat_channels_current_user_notifications_settings_controller_spec.rb @@ -12,11 +12,7 @@ RSpec.describe Chat::Api::ChatChannelsCurrentUserNotificationsSettingsController include_examples "channel access example", :put, "/notifications-settings/me", - { - notifications_settings: { - muted: true, - }, - } + { notifications_settings: { muted: true } } context "when category channel has invalid params" do fab!(:channel_1) { Fabricate(:category_channel) } diff --git a/plugins/chat/spec/requests/api/chat_channels_status_controller_spec.rb b/plugins/chat/spec/requests/api/chat_channels_status_controller_spec.rb index 942d063a7e..c2e687da59 100644 --- a/plugins/chat/spec/requests/api/chat_channels_status_controller_spec.rb +++ b/plugins/chat/spec/requests/api/chat_channels_status_controller_spec.rb @@ -61,9 +61,9 @@ RSpec.describe Chat::Api::ChatChannelsStatusController do context "when changing from open to closed" do it "changes the status" do - expect { put "/chat/api/channels/#{channel_1.id}/status", params: status("closed") }.to change { - channel_1.reload.status - }.to("closed").from("open") + expect { + put "/chat/api/channels/#{channel_1.id}/status", params: status("closed") + }.to change { channel_1.reload.status }.to("closed").from("open") expect(response.status).to eq(200) channel = response.parsed_body["channel"] @@ -75,9 +75,9 @@ RSpec.describe Chat::Api::ChatChannelsStatusController do before { channel_1.update!(status: "closed") } it "changes the status" do - expect { put "/chat/api/channels/#{channel_1.id}/status", params: status("open") }.to change { - channel_1.reload.status - }.to("open").from("closed") + expect { + put "/chat/api/channels/#{channel_1.id}/status", params: status("open") + }.to change { channel_1.reload.status }.to("open").from("closed") expect(response.status).to eq(200) channel = response.parsed_body["channel"] diff --git a/plugins/chat/spec/requests/chat_controller_spec.rb b/plugins/chat/spec/requests/chat_controller_spec.rb index 8b08203a69..8ff5912df5 100644 --- a/plugins/chat/spec/requests/chat_controller_spec.rb +++ b/plugins/chat/spec/requests/chat_controller_spec.rb @@ -1114,7 +1114,11 @@ RSpec.describe Chat::ChatController do it "returns a 403 if the user can't see the channel" do category.update!(read_restricted: true) group = Fabricate(:group) - CategoryGroup.create(group: group, category: category, permission_type: CategoryGroup.permission_types[:create_post]) + CategoryGroup.create( + group: group, + category: category, + permission_type: CategoryGroup.permission_types[:create_post], + ) sign_in(user) post "/chat/#{channel.id}/quote.json", params: { diff --git a/plugins/chat/spec/requests/core_ext/categories_controller_spec.rb b/plugins/chat/spec/requests/core_ext/categories_controller_spec.rb index 431ded1561..3ecc2011ff 100644 --- a/plugins/chat/spec/requests/core_ext/categories_controller_spec.rb +++ b/plugins/chat/spec/requests/core_ext/categories_controller_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe CategoriesController do - describe '#destroy' do + describe "#destroy" do subject(:destroy_category) { delete "/categories/#{category.slug}.json" } fab!(:admin) { Fabricate(:admin) } diff --git a/plugins/chat/spec/services/chat_message_destroyer_spec.rb b/plugins/chat/spec/services/chat_message_destroyer_spec.rb index 66e58a85aa..e6016b7bca 100644 --- a/plugins/chat/spec/services/chat_message_destroyer_spec.rb +++ b/plugins/chat/spec/services/chat_message_destroyer_spec.rb @@ -23,11 +23,7 @@ RSpec.describe ChatMessageDestroyer do it "deletes flags associated to deleted chat messages" do guardian = Guardian.new(Discourse.system_user) - Chat::ChatReviewQueue.new.flag_message( - message_1, - guardian, - ReviewableScore.types[:off_topic], - ) + Chat::ChatReviewQueue.new.flag_message(message_1, guardian, ReviewableScore.types[:off_topic]) reviewable = ReviewableChatMessage.last expect(reviewable).to be_present diff --git a/plugins/chat/spec/support/examples/chatable_model.rb b/plugins/chat/spec/support/examples/chatable_model.rb index 78237d7937..8c274b3853 100644 --- a/plugins/chat/spec/support/examples/chatable_model.rb +++ b/plugins/chat/spec/support/examples/chatable_model.rb @@ -6,8 +6,8 @@ RSpec.shared_examples "a chatable model" do it "returns a new chat channel model" do expect(chat_channel).to have_attributes persisted?: false, - class: channel_class, - chatable: chatable + class: channel_class, + chatable: chatable end end diff --git a/plugins/chat/spec/system/hashtag_autocomplete_spec.rb b/plugins/chat/spec/system/hashtag_autocomplete_spec.rb index ad61344943..47d4401c7a 100644 --- a/plugins/chat/spec/system/hashtag_autocomplete_spec.rb +++ b/plugins/chat/spec/system/hashtag_autocomplete_spec.rb @@ -32,7 +32,9 @@ describe "Using #hashtag autocompletion to search for and lookup channels", count: 3, ) hashtag_results = page.all(".hashtag-autocomplete__link", count: 3) - expect(hashtag_results.map(&:text).map { |r| r.gsub("\n", " ") }).to eq(["Random", "Raspberry", "razed (x0)"]) + expect(hashtag_results.map(&:text).map { |r| r.gsub("\n", " ") }).to eq( + ["Random", "Raspberry", "razed (x0)"], + ) end it "searches for channels as well with # in a topic composer and deprioritises them" do @@ -44,18 +46,26 @@ describe "Using #hashtag autocompletion to search for and lookup channels", count: 3, ) hashtag_results = page.all(".hashtag-autocomplete__link", count: 3) - expect(hashtag_results.map(&:text).map { |r| r.gsub("\n", " ") }).to eq(["Raspberry", "razed (x0)", "Random"]) + expect(hashtag_results.map(&:text).map { |r| r.gsub("\n", " ") }).to eq( + ["Raspberry", "razed (x0)", "Random"], + ) end it "cooks the hashtags for channels, categories, and tags serverside when the chat message is saved to the database" do chat_page.visit_channel(channel1) expect(chat_channel_page).to have_no_loading_skeleton - chat_channel_page.type_in_composer("this is #random and this is #raspberry-beret and this is #razed which is cool") + chat_channel_page.type_in_composer( + "this is #random and this is #raspberry-beret and this is #razed which is cool", + ) chat_channel_page.click_send_message message = nil try_until_success do - message = ChatMessage.find_by(user: user, message: "this is #random and this is #raspberry-beret and this is #razed which is cool") + message = + ChatMessage.find_by( + user: user, + message: "this is #random and this is #raspberry-beret and this is #razed which is cool", + ) expect(message).not_to eq(nil) end expect(chat_channel_page).to have_message(id: message.id) diff --git a/plugins/chat/spec/system/list_channels/mobile_spec.rb b/plugins/chat/spec/system/list_channels/mobile_spec.rb index 073ca8533d..59744187b7 100644 --- a/plugins/chat/spec/system/list_channels/mobile_spec.rb +++ b/plugins/chat/spec/system/list_channels/mobile_spec.rb @@ -43,8 +43,12 @@ RSpec.describe "List channels | mobile", type: :system, js: true, mobile: true d it "sorts them alphabetically" do visit("/chat") - expect(page.find("#public-channels a:nth-child(1)")["data-chat-channel-id"]).to eq(channel_2.id.to_s) - expect(page.find("#public-channels a:nth-child(2)")["data-chat-channel-id"]).to eq(channel_1.id.to_s) + expect(page.find("#public-channels a:nth-child(1)")["data-chat-channel-id"]).to eq( + channel_2.id.to_s, + ) + expect(page.find("#public-channels a:nth-child(2)")["data-chat-channel-id"]).to eq( + channel_1.id.to_s, + ) end end diff --git a/plugins/chat/spec/system/list_channels/no_sidebar_spec.rb b/plugins/chat/spec/system/list_channels/no_sidebar_spec.rb index 37266c51a2..9f751df047 100644 --- a/plugins/chat/spec/system/list_channels/no_sidebar_spec.rb +++ b/plugins/chat/spec/system/list_channels/no_sidebar_spec.rb @@ -44,8 +44,12 @@ RSpec.describe "List channels | no sidebar", type: :system, js: true do it "sorts them alphabetically" do visit("/chat") - expect(page.find("#public-channels a:nth-child(1)")["data-chat-channel-id"]).to eq(channel_2.id.to_s) - expect(page.find("#public-channels a:nth-child(2)")["data-chat-channel-id"]).to eq(channel_1.id.to_s) + expect(page.find("#public-channels a:nth-child(1)")["data-chat-channel-id"]).to eq( + channel_2.id.to_s, + ) + expect(page.find("#public-channels a:nth-child(2)")["data-chat-channel-id"]).to eq( + channel_1.id.to_s, + ) end end diff --git a/plugins/chat/spec/system/list_channels/sidebar_spec.rb b/plugins/chat/spec/system/list_channels/sidebar_spec.rb index aae03c9c44..f7c4889c56 100644 --- a/plugins/chat/spec/system/list_channels/sidebar_spec.rb +++ b/plugins/chat/spec/system/list_channels/sidebar_spec.rb @@ -53,8 +53,12 @@ RSpec.describe "List channels | sidebar", type: :system, js: true do it "sorts them alphabetically" do visit("/") - expect(page.find("#sidebar-section-content-chat-channels li:nth-child(1)")).to have_css(".channel-#{channel_2.id}") - expect(page.find("#sidebar-section-content-chat-channels li:nth-child(2)")).to have_css(".channel-#{channel_1.id}") + expect(page.find("#sidebar-section-content-chat-channels li:nth-child(1)")).to have_css( + ".channel-#{channel_2.id}", + ) + expect(page.find("#sidebar-section-content-chat-channels li:nth-child(2)")).to have_css( + ".channel-#{channel_1.id}", + ) end end diff --git a/plugins/chat/spec/system/navigating_to_message_spec.rb b/plugins/chat/spec/system/navigating_to_message_spec.rb index f23097373e..7dc60d697e 100644 --- a/plugins/chat/spec/system/navigating_to_message_spec.rb +++ b/plugins/chat/spec/system/navigating_to_message_spec.rb @@ -43,14 +43,20 @@ RSpec.describe "Navigating to message", type: :system, js: true do context "when clicking a link to a message from the current channel" do before do - Fabricate(:chat_message, chat_channel: channel_1, message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})") + Fabricate( + :chat_message, + chat_channel: channel_1, + message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})", + ) end it "highglights the correct message" do chat_page.visit_channel(channel_1) click_link(link) - expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']") + expect(page).to have_css( + ".chat-message-container.highlighted[data-id='#{first_message.id}']", + ) end it "highlights the correct message after using the bottom arrow" do @@ -59,7 +65,9 @@ RSpec.describe "Navigating to message", type: :system, js: true do click_link(I18n.t("js.chat.scroll_to_bottom")) click_link(link) - expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']") + expect(page).to have_css( + ".chat-message-container.highlighted[data-id='#{first_message.id}']", + ) end end @@ -67,7 +75,11 @@ RSpec.describe "Navigating to message", type: :system, js: true do fab!(:channel_2) { Fabricate(:category_channel) } before do - Fabricate(:chat_message, chat_channel: channel_2, message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})") + Fabricate( + :chat_message, + chat_channel: channel_2, + message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})", + ) channel_2.add(current_user) end @@ -75,7 +87,9 @@ RSpec.describe "Navigating to message", type: :system, js: true do chat_page.visit_channel(channel_2) click_link(link) - expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']") + expect(page).to have_css( + ".chat-message-container.highlighted[data-id='#{first_message.id}']", + ) end end @@ -83,7 +97,9 @@ RSpec.describe "Navigating to message", type: :system, js: true do it "highglights the correct message" do visit("/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id}") - expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']") + expect(page).to have_css( + ".chat-message-container.highlighted[data-id='#{first_message.id}']", + ) end end end @@ -113,7 +129,11 @@ RSpec.describe "Navigating to message", type: :system, js: true do context "when clicking a link to a message from the current channel" do before do - Fabricate(:chat_message, chat_channel: channel_1, message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})") + Fabricate( + :chat_message, + chat_channel: channel_1, + message: "[#{link}](/chat/channel/#{channel_1.id}/-?messageId=#{first_message.id})", + ) end it "highglights the correct message" do @@ -122,7 +142,9 @@ RSpec.describe "Navigating to message", type: :system, js: true do chat_drawer_page.open_channel(channel_1) click_link(link) - expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']") + expect(page).to have_css( + ".chat-message-container.highlighted[data-id='#{first_message.id}']", + ) end it "highlights the correct message after using the bottom arrow" do @@ -133,7 +155,9 @@ RSpec.describe "Navigating to message", type: :system, js: true do click_link(I18n.t("js.chat.scroll_to_bottom")) click_link(link) - expect(page).to have_css(".chat-message-container.highlighted[data-id='#{first_message.id}']") + expect(page).to have_css( + ".chat-message-container.highlighted[data-id='#{first_message.id}']", + ) end end end diff --git a/plugins/chat/spec/system/page_objects/chat/chat.rb b/plugins/chat/spec/system/page_objects/chat/chat.rb index a070c700a1..38e341e46d 100644 --- a/plugins/chat/spec/system/page_objects/chat/chat.rb +++ b/plugins/chat/spec/system/page_objects/chat/chat.rb @@ -4,7 +4,9 @@ module PageObjects module Pages class Chat < PageObjects::Pages::Base def prefers_full_page - page.execute_script("window.localStorage.setItem('discourse_chat_preferred_mode', '\"FULL_PAGE_CHAT\"');") + page.execute_script( + "window.localStorage.setItem('discourse_chat_preferred_mode', '\"FULL_PAGE_CHAT\"');", + ) end def open_from_header diff --git a/plugins/discourse-details/plugin.rb b/plugins/discourse-details/plugin.rb index 130bd58a32..6d14a1dba6 100644 --- a/plugins/discourse-details/plugin.rb +++ b/plugins/discourse-details/plugin.rb @@ -12,32 +12,34 @@ hide_plugin if self.respond_to?(:hide_plugin) register_asset "stylesheets/details.scss" after_initialize do - Email::Styles.register_plugin_style do |fragment| # remove all elided content fragment.css("details.elided").each(&:remove) # replace all details with their summary in emails - fragment.css("details").each do |details| - summary = details.css("summary") - if summary && summary[0] - summary = summary[0] - if summary && summary.respond_to?(:name) - summary.name = "p" - details.replace(summary) + fragment + .css("details") + .each do |details| + summary = details.css("summary") + if summary && summary[0] + summary = summary[0] + if summary && summary.respond_to?(:name) + summary.name = "p" + details.replace(summary) + end end end - end end on(:reduce_cooked) do |fragment, post| - fragment.css("details").each do |el| - text = el.css("summary").text - link = fragment.document.create_element("a") - link["href"] = post.url if post - link.content = I18n.t("details.excerpt_details") - el.replace CGI.escapeHTML(text) + " " + link.to_html - end + fragment + .css("details") + .each do |el| + text = el.css("summary").text + link = fragment.document.create_element("a") + link["href"] = post.url if post + link.content = I18n.t("details.excerpt_details") + el.replace CGI.escapeHTML(text) + " " + link.to_html + end end - end diff --git a/plugins/discourse-details/spec/components/pretty_text_spec.rb b/plugins/discourse-details/spec/components/pretty_text_spec.rb index bbbcb6ba27..20b84acfc7 100644 --- a/plugins/discourse-details/spec/components/pretty_text_spec.rb +++ b/plugins/discourse-details/spec/components/pretty_text_spec.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true -require 'rails_helper' -require 'pretty_text' +require "rails_helper" +require "pretty_text" RSpec.describe PrettyText do - let(:post) { Fabricate(:post) } it "supports details tag" do @@ -17,17 +16,19 @@ RSpec.describe PrettyText do HTML expect(cooked_html).to match_html(cooked_html) - expect(PrettyText.cook("[details=foo]\nbar\n[/details]").gsub("\n", "")).to match_html(cooked_html) + expect(PrettyText.cook("[details=foo]\nbar\n[/details]").gsub("\n", "")).to match_html( + cooked_html, + ) end it "deletes elided content" do cooked_html = PrettyText.cook("Hello World\n\n
42
") - mail_html = "

Hello World

\n(click for more details)" + mail_html = "

Hello World

\n(click for more details)" expect(PrettyText.format_for_email(cooked_html)).to match_html(mail_html) end - it 'can replace spoilers in emails' do + it "can replace spoilers in emails" do md = PrettyText.cook(<<~MD) hello @@ -41,7 +42,7 @@ RSpec.describe PrettyText do expect(md).to eq(html) end - it 'properly handles multiple spoiler blocks in a post' do + it "properly handles multiple spoiler blocks in a post" do md = PrettyText.cook(<<~MD) [details="First"] body secret stuff very long @@ -58,13 +59,13 @@ RSpec.describe PrettyText do MD md = PrettyText.format_for_email(md, post) - expect(md).not_to include('secret stuff') + expect(md).not_to include("secret stuff") expect(md.scan(/First/).size).to eq(1) expect(md.scan(/Third/).size).to eq(1) - expect(md.scan(I18n.t('details.excerpt_details')).size).to eq(3) + expect(md.scan(I18n.t("details.excerpt_details")).size).to eq(3) end - it 'escapes summary text' do + it "escapes summary text" do md = PrettyText.cook(<<~MD) [details=""] @@ -73,7 +74,6 @@ RSpec.describe PrettyText do MD md = PrettyText.format_for_email(md, post) - expect(md).not_to include(']\n- A\n- B\n[/poll]" - }, format: :json + post :create, + params: { + title: title, + raw: "[poll name=]\n- A\n- B\n[/poll]", + }, + format: :json expect(response.status).to eq(200) json = response.parsed_body expect(json["cooked"]).to match("data-poll-") expect(json["cooked"]).to include("<script>") - expect(Poll.find_by(post_id: json["id"]).name).to eq("<script>alert('xss')</script>") + expect(Poll.find_by(post_id: json["id"]).name).to eq( + "<script>alert('xss')</script>", + ) end it "also works when there is a link starting with '[poll'" do - post :create, params: { - title: title, raw: "[Polls are awesome](/foobar)\n[poll]\n- A\n- B\n[/poll]" - }, format: :json + post :create, + params: { + title: title, + raw: "[Polls are awesome](/foobar)\n[poll]\n- A\n- B\n[/poll]", + }, + format: :json expect(response.status).to eq(200) json = response.parsed_body @@ -139,9 +151,12 @@ RSpec.describe PostsController do end it "prevents poll-inception" do - post :create, params: { - title: title, raw: "[poll name=1]\n- A\n[poll name=2]\n- B\n- C\n[/poll]\n- D\n[/poll]" - }, format: :json + post :create, + params: { + title: title, + raw: "[poll name=1]\n- A\n[poll name=2]\n- B\n- C\n[/poll]\n- D\n[/poll]", + }, + format: :json expect(response.status).to eq(200) json = response.parsed_body @@ -150,9 +165,12 @@ RSpec.describe PostsController do end it "accepts polls with titles" do - post :create, params: { - title: title, raw: "[poll]\n# What's up?\n- one\n[/poll]" - }, format: :json + post :create, + params: { + title: title, + raw: "[poll]\n# What's up?\n- one\n[/poll]", + }, + format: :json expect(response).to be_successful poll = Poll.last @@ -161,23 +179,24 @@ RSpec.describe PostsController do end describe "edit window" do - describe "within the first 5 minutes" do - let(:post_id) do freeze_time(4.minutes.ago) do - post :create, params: { - title: title, raw: "[poll]\n- A\n- B\n[/poll]" - }, format: :json + post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json response.parsed_body["id"] end end it "can be changed" do - put :update, params: { - id: post_id, post: { raw: "[poll]\n- A\n- B\n- C\n[/poll]" } - }, format: :json + put :update, + params: { + id: post_id, + post: { + raw: "[poll]\n- A\n- B\n- C\n[/poll]", + }, + }, + format: :json expect(response.status).to eq(200) json = response.parsed_body @@ -187,28 +206,29 @@ RSpec.describe PostsController do it "resets the votes" do DiscoursePoll::Poll.vote(user, post_id, "poll", ["5c24fc1df56d764b550ceae1b9319125"]) - put :update, params: { - id: post_id, post: { raw: "[poll]\n- A\n- B\n- C\n[/poll]" } - }, format: :json + put :update, + params: { + id: post_id, + post: { + raw: "[poll]\n- A\n- B\n- C\n[/poll]", + }, + }, + format: :json expect(response.status).to eq(200) json = response.parsed_body expect(json["post"]["polls_votes"]).to_not be end - end describe "after the poll edit window has expired" do - let(:poll) { "[poll]\n- A\n- B\n[/poll]" } let(:new_option) { "[poll]\n- A\n- C\n[/poll]" } let(:updated) { "before\n\n[poll]\n- A\n- B\n[/poll]\n\nafter" } let(:post_id) do freeze_time(6.minutes.ago) do - post :create, params: { - title: title, raw: poll - }, format: :json + post :create, params: { title: title, raw: poll }, format: :json response.parsed_body["id"] end @@ -216,16 +236,11 @@ RSpec.describe PostsController do let(:poll_edit_window_mins) { 6 } - before do - SiteSetting.poll_edit_window_mins = poll_edit_window_mins - end + before { SiteSetting.poll_edit_window_mins = poll_edit_window_mins } describe "with no vote" do - it "can change the options" do - put :update, params: { - id: post_id, post: { raw: new_option } - }, format: :json + put :update, params: { id: post_id, post: { raw: new_option } }, format: :json expect(response.status).to eq(200) json = response.parsed_body @@ -238,26 +253,24 @@ RSpec.describe PostsController do json = response.parsed_body expect(json["post"]["cooked"]).to match("before") end - end describe "with at least one vote" do - before do DiscoursePoll::Poll.vote(user, post_id, "poll", ["5c24fc1df56d764b550ceae1b9319125"]) end it "cannot change the options" do - put :update, params: { - id: post_id, post: { raw: new_option } - }, format: :json + put :update, params: { id: post_id, post: { raw: new_option } }, format: :json expect(response).not_to be_successful json = response.parsed_body - expect(json["errors"][0]).to eq(I18n.t( - "poll.edit_window_expired.cannot_edit_default_poll_with_votes", - minutes: poll_edit_window_mins - )) + expect(json["errors"][0]).to eq( + I18n.t( + "poll.edit_window_expired.cannot_edit_default_poll_with_votes", + minutes: poll_edit_window_mins, + ), + ) end it "support changes on the post" do @@ -266,45 +279,49 @@ RSpec.describe PostsController do json = response.parsed_body expect(json["post"]["cooked"]).to match("before") end - end - end - end - end describe "named polls" do - it "should have different options" do - post :create, params: { - title: title, raw: "[poll name=""foo""]\n- A\n- A\n[/poll]" - }, format: :json + post :create, + params: { + title: title, + raw: + "[poll name=" \ + "foo" \ + "]\n- A\n- A\n[/poll]", + }, + format: :json expect(response).not_to be_successful json = response.parsed_body - expect(json["errors"][0]).to eq(I18n.t("poll.named_poll_must_have_different_options", name: "foo")) + expect(json["errors"][0]).to eq( + I18n.t("poll.named_poll_must_have_different_options", name: "foo"), + ) end it "should have at least 1 option" do - post :create, params: { - title: title, raw: "[poll name='foo']\n[/poll]" - }, format: :json + post :create, params: { title: title, raw: "[poll name='foo']\n[/poll]" }, format: :json expect(response).not_to be_successful json = response.parsed_body - expect(json["errors"][0]).to eq(I18n.t("poll.named_poll_must_have_at_least_1_option", name: "foo")) + expect(json["errors"][0]).to eq( + I18n.t("poll.named_poll_must_have_at_least_1_option", name: "foo"), + ) end - end describe "multiple polls" do - it "works" do - post :create, params: { - title: title, raw: "[poll]\n- A\n- B\n[/poll]\n[poll name=foo]\n- A\n- B\n[/poll]" - }, format: :json + post :create, + params: { + title: title, + raw: "[poll]\n- A\n- B\n[/poll]\n[poll name=foo]\n- A\n- B\n[/poll]", + }, + format: :json expect(response.status).to eq(200) json = response.parsed_body @@ -313,9 +330,12 @@ RSpec.describe PostsController do end it "should have a name" do - post :create, params: { - title: title, raw: "[poll]\n- A\n- B\n[/poll]\n[poll]\n- A\n- B\n[/poll]" - }, format: :json + post :create, + params: { + title: title, + raw: "[poll]\n- A\n- B\n[/poll]\n[poll]\n- A\n- B\n[/poll]", + }, + format: :json expect(response).not_to be_successful json = response.parsed_body @@ -323,46 +343,42 @@ RSpec.describe PostsController do end it "should have unique name" do - post :create, params: { - title: title, raw: "[poll name=foo]\n- A\n- B\n[/poll]\n[poll name=foo]\n- A\n- B\n[/poll]" - }, format: :json + post :create, + params: { + title: title, + raw: "[poll name=foo]\n- A\n- B\n[/poll]\n[poll name=foo]\n- A\n- B\n[/poll]", + }, + format: :json expect(response).not_to be_successful json = response.parsed_body expect(json["errors"][0]).to eq(I18n.t("poll.multiple_polls_with_same_name", name: "foo")) end - end describe "disabled polls" do - before do - SiteSetting.poll_enabled = false - end + before { SiteSetting.poll_enabled = false } it "doesn’t cook the poll" do log_in_user(Fabricate(:user, admin: true, trust_level: 4)) - post :create, params: { - title: title, raw: "[poll]\n- A\n- B\n[/poll]" - }, format: :json + post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json expect(response.status).to eq(200) json = response.parsed_body - expect(json["cooked"]).to eq("

[poll]

\n
    \n
  • A
  • \n
  • B
    \n[/poll]
  • \n
") + expect(json["cooked"]).to eq( + "

[poll]

\n
    \n
  • A
  • \n
  • B
    \n[/poll]
  • \n
", + ) end end describe "regular user with insufficient trust level" do - before do - SiteSetting.poll_minimum_trust_level_to_create = 2 - end + before { SiteSetting.poll_minimum_trust_level_to_create = 2 } it "invalidates the post" do log_in_user(Fabricate(:user, trust_level: 1)) - post :create, params: { - title: title, raw: "[poll]\n- A\n- B\n[/poll]" - }, format: :json + post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json expect(response).not_to be_successful json = response.parsed_body @@ -371,33 +387,31 @@ RSpec.describe PostsController do it "skips the check in PMs with bots" do user = Fabricate(:user, trust_level: 1) - topic = Fabricate(:private_message_topic, topic_allowed_users: [ - Fabricate.build(:topic_allowed_user, user: user), - Fabricate.build(:topic_allowed_user, user: Discourse.system_user) - ]) + topic = + Fabricate( + :private_message_topic, + topic_allowed_users: [ + Fabricate.build(:topic_allowed_user, user: user), + Fabricate.build(:topic_allowed_user, user: Discourse.system_user), + ], + ) Fabricate(:post, topic_id: topic.id, user_id: Discourse::SYSTEM_USER_ID) log_in_user(user) - post :create, params: { - topic_id: topic.id, raw: "[poll]\n- A\n- B\n[/poll]" - }, format: :json + post :create, params: { topic_id: topic.id, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json expect(response.parsed_body["errors"]).to eq(nil) end end describe "regular user with equal trust level" do - before do - SiteSetting.poll_minimum_trust_level_to_create = 2 - end + before { SiteSetting.poll_minimum_trust_level_to_create = 2 } it "validates the post" do log_in_user(Fabricate(:user, trust_level: 2)) - post :create, params: { - title: title, raw: "[poll]\n- A\n- B\n[/poll]" - }, format: :json + post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json expect(response.status).to eq(200) json = response.parsed_body @@ -407,16 +421,12 @@ RSpec.describe PostsController do end describe "regular user with superior trust level" do - before do - SiteSetting.poll_minimum_trust_level_to_create = 2 - end + before { SiteSetting.poll_minimum_trust_level_to_create = 2 } it "validates the post" do log_in_user(Fabricate(:user, trust_level: 3)) - post :create, params: { - title: title, raw: "[poll]\n- A\n- B\n[/poll]" - }, format: :json + post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json expect(response.status).to eq(200) json = response.parsed_body @@ -426,16 +436,12 @@ RSpec.describe PostsController do end describe "staff with insufficient trust level" do - before do - SiteSetting.poll_minimum_trust_level_to_create = 2 - end + before { SiteSetting.poll_minimum_trust_level_to_create = 2 } it "validates the post" do log_in_user(Fabricate(:user, moderator: true, trust_level: 1)) - post :create, params: { - title: title, raw: "[poll]\n- A\n- B\n[/poll]" - }, format: :json + post :create, params: { title: title, raw: "[poll]\n- A\n- B\n[/poll]" }, format: :json expect(response.status).to eq(200) json = response.parsed_body @@ -445,9 +451,7 @@ RSpec.describe PostsController do end describe "staff editing posts of users with insufficient trust level" do - before do - SiteSetting.poll_minimum_trust_level_to_create = 2 - end + before { SiteSetting.poll_minimum_trust_level_to_create = 2 } it "validates the post" do log_in_user(Fabricate(:user, trust_level: 1)) @@ -459,9 +463,14 @@ RSpec.describe PostsController do log_in_user(Fabricate(:admin)) - put :update, params: { - id: post_id, post: { raw: "#{title}\n[poll]\n- A\n- B\n- C\n[/poll]" } - }, format: :json + put :update, + params: { + id: post_id, + post: { + raw: "#{title}\n[poll]\n- A\n- B\n- C\n[/poll]", + }, + }, + format: :json expect(response.status).to eq(200) expect(response.parsed_body["post"]["polls"][0]["options"][2]["html"]).to eq("C") diff --git a/plugins/poll/spec/integration/poll_endpoints_spec.rb b/plugins/poll/spec/integration/poll_endpoints_spec.rb index 9fff1972a8..e8b62dae9f 100644 --- a/plugins/poll/spec/integration/poll_endpoints_spec.rb +++ b/plugins/poll/spec/integration/poll_endpoints_spec.rb @@ -7,31 +7,25 @@ RSpec.describe "DiscoursePoll endpoints" do fab!(:user) { Fabricate(:user) } fab!(:post) { Fabricate(:post, raw: "[poll public=true]\n- A\n- B\n[/poll]") } - fab!(:post_with_multiple_poll) do - Fabricate(:post, raw: <<~SQL) + fab!(:post_with_multiple_poll) { Fabricate(:post, raw: <<~SQL) } [poll type=multiple public=true min=1 max=2] - A - B - C [/poll] SQL - end let(:option_a) { "5c24fc1df56d764b550ceae1b9319125" } let(:option_b) { "e89dec30bbd9bf50fabf6a05b4324edf" } it "should return the right response" do - DiscoursePoll::Poll.vote( - user, - post.id, - DiscoursePoll::DEFAULT_POLL_NAME, - [option_a] - ) + DiscoursePoll::Poll.vote(user, post.id, DiscoursePoll::DEFAULT_POLL_NAME, [option_a]) - get "/polls/voters.json", params: { - post_id: post.id, - poll_name: DiscoursePoll::DEFAULT_POLL_NAME - } + get "/polls/voters.json", + params: { + post_id: post.id, + poll_name: DiscoursePoll::DEFAULT_POLL_NAME, + } expect(response.status).to eq(200) @@ -43,19 +37,20 @@ RSpec.describe "DiscoursePoll endpoints" do expect(option.first["username"]).to eq(user.username) end - it 'should return the right response for a single option' do + it "should return the right response for a single option" do DiscoursePoll::Poll.vote( user, post_with_multiple_poll.id, DiscoursePoll::DEFAULT_POLL_NAME, - [option_a, option_b] + [option_a, option_b], ) - get "/polls/voters.json", params: { - post_id: post_with_multiple_poll.id, - poll_name: DiscoursePoll::DEFAULT_POLL_NAME, - option_id: option_b - } + get "/polls/voters.json", + params: { + post_id: post_with_multiple_poll.id, + poll_name: DiscoursePoll::DEFAULT_POLL_NAME, + option_id: option_b, + } expect(response.status).to eq(200) @@ -70,56 +65,60 @@ RSpec.describe "DiscoursePoll endpoints" do expect(option.first["username"]).to eq(user.username) end - describe 'when post_id is blank' do - it 'should raise the right error' do + describe "when post_id is blank" do + it "should raise the right error" do get "/polls/voters.json", params: { poll_name: DiscoursePoll::DEFAULT_POLL_NAME } expect(response.status).to eq(400) end end - describe 'when post_id is not valid' do - it 'should raise the right error' do - get "/polls/voters.json", params: { - post_id: -1, - poll_name: DiscoursePoll::DEFAULT_POLL_NAME - } + describe "when post_id is not valid" do + it "should raise the right error" do + get "/polls/voters.json", + params: { + post_id: -1, + poll_name: DiscoursePoll::DEFAULT_POLL_NAME, + } expect(response.status).to eq(400) - expect(response.body).to include('post_id') + expect(response.body).to include("post_id") end end - describe 'when poll_name is blank' do - it 'should raise the right error' do + describe "when poll_name is blank" do + it "should raise the right error" do get "/polls/voters.json", params: { post_id: post.id } expect(response.status).to eq(400) end end - describe 'when poll_name is not valid' do - it 'should raise the right error' do - get "/polls/voters.json", params: { post_id: post.id, poll_name: 'wrongpoll' } + describe "when poll_name is not valid" do + it "should raise the right error" do + get "/polls/voters.json", params: { post_id: post.id, poll_name: "wrongpoll" } expect(response.status).to eq(400) - expect(response.body).to include('poll_name') + expect(response.body).to include("poll_name") end end context "with number poll" do - let(:post) { Fabricate(:post, raw: "[poll type=number min=1 max=20 step=1 public=true]\n[/poll]") } + let(:post) do + Fabricate(:post, raw: "[poll type=number min=1 max=20 step=1 public=true]\n[/poll]") + end - it 'should return the right response' do + it "should return the right response" do post DiscoursePoll::Poll.vote( user, post.id, DiscoursePoll::DEFAULT_POLL_NAME, - ["4d8a15e3cc35750f016ce15a43937620"] + ["4d8a15e3cc35750f016ce15a43937620"], ) - get "/polls/voters.json", params: { - post_id: post.id, - poll_name: DiscoursePoll::DEFAULT_POLL_NAME - } + get "/polls/voters.json", + params: { + post_id: post.id, + poll_name: DiscoursePoll::DEFAULT_POLL_NAME, + } expect(response.status).to eq(200) @@ -137,31 +136,25 @@ RSpec.describe "DiscoursePoll endpoints" do fab!(:user3) { Fabricate(:user) } fab!(:user4) { Fabricate(:user) } - fab!(:post) do - Fabricate(:post, raw: <<~SQL) + fab!(:post) { Fabricate(:post, raw: <<~SQL) } [poll type=multiple public=true min=1 max=2] - A - B [/poll] SQL - end let(:option_a) { "5c24fc1df56d764b550ceae1b9319125" } let(:option_b) { "e89dec30bbd9bf50fabf6a05b4324edf" } before do - user_votes = { - user_0: option_a, - user_1: option_a, - user_2: option_b, - } + user_votes = { user_0: option_a, user_1: option_a, user_2: option_b } [user1, user2, user3].each_with_index do |user, index| DiscoursePoll::Poll.vote( user, post.id, DiscoursePoll::DEFAULT_POLL_NAME, - [user_votes["user_#{index}".to_sym]] + [user_votes["user_#{index}".to_sym]], ) UserCustomField.create(user_id: user.id, name: "something", value: "value#{index}") end @@ -171,7 +164,7 @@ RSpec.describe "DiscoursePoll endpoints" do user4, post.id, DiscoursePoll::DEFAULT_POLL_NAME, - [option_a, option_b] + [option_a, option_b], ) UserCustomField.create(user_id: user4.id, name: "something", value: "value1") end @@ -179,32 +172,52 @@ RSpec.describe "DiscoursePoll endpoints" do it "returns grouped poll results based on user field" do SiteSetting.poll_groupable_user_fields = "something" - get "/polls/grouped_poll_results.json", params: { - post_id: post.id, - poll_name: DiscoursePoll::DEFAULT_POLL_NAME, - user_field_name: "something" - } + get "/polls/grouped_poll_results.json", + params: { + post_id: post.id, + poll_name: DiscoursePoll::DEFAULT_POLL_NAME, + user_field_name: "something", + } expect(response.status).to eq(200) expect(response.parsed_body.deep_symbolize_keys).to eq( grouped_results: [ - { group: "Value0", options: [{ digest: option_a, html: "A", votes: 1 }, { digest: option_b, html: "B", votes: 0 }] }, - { group: "Value1", options: [{ digest: option_a, html: "A", votes: 2 }, { digest: option_b, html: "B", votes: 1 }] }, - { group: "Value2", options: [{ digest: option_a, html: "A", votes: 0 }, { digest: option_b, html: "B", votes: 1 }] }, - ] + { + group: "Value0", + options: [ + { digest: option_a, html: "A", votes: 1 }, + { digest: option_b, html: "B", votes: 0 }, + ], + }, + { + group: "Value1", + options: [ + { digest: option_a, html: "A", votes: 2 }, + { digest: option_b, html: "B", votes: 1 }, + ], + }, + { + group: "Value2", + options: [ + { digest: option_a, html: "A", votes: 0 }, + { digest: option_b, html: "B", votes: 1 }, + ], + }, + ], ) end it "returns an error when poll_groupable_user_fields is empty" do SiteSetting.poll_groupable_user_fields = "" - get "/polls/grouped_poll_results.json", params: { - post_id: post.id, - poll_name: DiscoursePoll::DEFAULT_POLL_NAME, - user_field_name: "something" - } + get "/polls/grouped_poll_results.json", + params: { + post_id: post.id, + poll_name: DiscoursePoll::DEFAULT_POLL_NAME, + user_field_name: "something", + } expect(response.status).to eq(400) - expect(response.body).to include('user_field_name') + expect(response.body).to include("user_field_name") end end end diff --git a/plugins/poll/spec/jobs/regular/close_poll_spec.rb b/plugins/poll/spec/jobs/regular/close_poll_spec.rb index 891b35c9c1..08734f9691 100644 --- a/plugins/poll/spec/jobs/regular/close_poll_spec.rb +++ b/plugins/poll/spec/jobs/regular/close_poll_spec.rb @@ -5,15 +5,17 @@ require "rails_helper" RSpec.describe Jobs::ClosePoll do let(:post) { Fabricate(:post, raw: "[poll]\n- A\n- B\n[/poll]") } - describe 'missing arguments' do - it 'should raise the right error' do - expect do - Jobs::ClosePoll.new.execute(post_id: post.id) - end.to raise_error(Discourse::InvalidParameters, "poll_name") + describe "missing arguments" do + it "should raise the right error" do + expect do Jobs::ClosePoll.new.execute(post_id: post.id) end.to raise_error( + Discourse::InvalidParameters, + "poll_name", + ) - expect do - Jobs::ClosePoll.new.execute(poll_name: "poll") - end.to raise_error(Discourse::InvalidParameters, "post_id") + expect do Jobs::ClosePoll.new.execute(poll_name: "poll") end.to raise_error( + Discourse::InvalidParameters, + "post_id", + ) end end @@ -24,5 +26,4 @@ RSpec.describe Jobs::ClosePoll do expect(post.polls.first.closed?).to eq(true) end - end diff --git a/plugins/poll/spec/lib/new_post_manager_spec.rb b/plugins/poll/spec/lib/new_post_manager_spec.rb index fb10d9cf2d..5a051697cb 100644 --- a/plugins/poll/spec/lib/new_post_manager_spec.rb +++ b/plugins/poll/spec/lib/new_post_manager_spec.rb @@ -7,9 +7,7 @@ RSpec.describe NewPostManager do let(:admin) { Fabricate(:admin) } describe "when new post containing a poll is queued for approval" do - before do - SiteSetting.poll_minimum_trust_level_to_create = 0 - end + before { SiteSetting.poll_minimum_trust_level_to_create = 0 } let(:params) do { @@ -23,9 +21,10 @@ RSpec.describe NewPostManager do is_warning: false, title: "This is a test post with a poll", ip_address: "127.0.0.1", - user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", + user_agent: + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36", referrer: "http://localhost:3000/", - first_post_checks: true + first_post_checks: true, } end @@ -38,7 +37,7 @@ RSpec.describe NewPostManager do expect(Poll.where(post: review_result.created_post).exists?).to eq(true) end - it 're-validates the poll when the approve_post event is triggered' do + it "re-validates the poll when the approve_post event is triggered" do invalid_raw_poll = <<~MD [poll type=multiple min=0] * 1 diff --git a/plugins/poll/spec/lib/poll_spec.rb b/plugins/poll/spec/lib/poll_spec.rb index 1b885947f7..9e42fd55f4 100644 --- a/plugins/poll/spec/lib/poll_spec.rb +++ b/plugins/poll/spec/lib/poll_spec.rb @@ -4,17 +4,14 @@ RSpec.describe DiscoursePoll::Poll do fab!(:user) { Fabricate(:user) } fab!(:user_2) { Fabricate(:user) } - fab!(:post_with_regular_poll) do - Fabricate(:post, raw: <<~RAW) + fab!(:post_with_regular_poll) { Fabricate(:post, raw: <<~RAW) } [poll] * 1 * 2 [/poll] RAW - end - fab!(:post_with_multiple_poll) do - Fabricate(:post, raw: <<~RAW) + fab!(:post_with_multiple_poll) { Fabricate(:post, raw: <<~RAW) } [poll type=multiple min=2 max=3] * 1 * 2 @@ -23,10 +20,9 @@ RSpec.describe DiscoursePoll::Poll do * 5 [/poll] RAW - end - describe '.vote' do - it 'should only allow one vote per user for a regular poll' do + describe ".vote" do + it "should only allow one vote per user for a regular poll" do poll = post_with_regular_poll.polls.first expect do @@ -34,46 +30,35 @@ RSpec.describe DiscoursePoll::Poll do user, post_with_regular_poll.id, "poll", - poll.poll_options.map(&:digest) + poll.poll_options.map(&:digest), ) end.to raise_error(DiscoursePoll::Error, I18n.t("poll.one_vote_per_user")) end - it 'should clean up bad votes for a regular poll' do + it "should clean up bad votes for a regular poll" do poll = post_with_regular_poll.polls.first - PollVote.create!( - poll: poll, - poll_option: poll.poll_options.first, - user: user - ) + PollVote.create!(poll: poll, poll_option: poll.poll_options.first, user: user) - PollVote.create!( - poll: poll, - poll_option: poll.poll_options.last, - user: user - ) + PollVote.create!(poll: poll, poll_option: poll.poll_options.last, user: user) DiscoursePoll::Poll.vote( user, post_with_regular_poll.id, "poll", - [poll.poll_options.first.digest] + [poll.poll_options.first.digest], ) - expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)) - .to contain_exactly(poll.poll_options.first.id) + expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)).to contain_exactly( + poll.poll_options.first.id, + ) end - it 'allows user to vote on multiple options correctly for a multiple poll' do + it "allows user to vote on multiple options correctly for a multiple poll" do poll = post_with_multiple_poll.polls.first poll_options = poll.poll_options - [ - poll_options.first, - poll_options.second, - poll_options.third, - ].each do |poll_option| + [poll_options.first, poll_options.second, poll_options.third].each do |poll_option| PollVote.create!(poll: poll, poll_option: poll_option, user: user) end @@ -81,24 +66,28 @@ RSpec.describe DiscoursePoll::Poll do user, post_with_multiple_poll.id, "poll", - [poll_options.first.digest, poll_options.second.digest] + [poll_options.first.digest, poll_options.second.digest], ) DiscoursePoll::Poll.vote( user_2, post_with_multiple_poll.id, "poll", - [poll_options.third.digest, poll_options.fourth.digest] + [poll_options.third.digest, poll_options.fourth.digest], ) - expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)) - .to contain_exactly(poll_options.first.id, poll_options.second.id) + expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)).to contain_exactly( + poll_options.first.id, + poll_options.second.id, + ) - expect(PollVote.where(poll: poll, user: user_2).pluck(:poll_option_id)) - .to contain_exactly(poll_options.third.id, poll_options.fourth.id) + expect(PollVote.where(poll: poll, user: user_2).pluck(:poll_option_id)).to contain_exactly( + poll_options.third.id, + poll_options.fourth.id, + ) end - it 'should respect the min/max votes per user for a multiple poll' do + it "should respect the min/max votes per user for a multiple poll" do poll = post_with_multiple_poll.polls.first expect do @@ -106,27 +95,21 @@ RSpec.describe DiscoursePoll::Poll do user, post_with_multiple_poll.id, "poll", - poll.poll_options.map(&:digest) + poll.poll_options.map(&:digest), ) - end.to raise_error( - DiscoursePoll::Error, - I18n.t("poll.max_vote_per_user", count: poll.max) - ) + end.to raise_error(DiscoursePoll::Error, I18n.t("poll.max_vote_per_user", count: poll.max)) expect do DiscoursePoll::Poll.vote( user, post_with_multiple_poll.id, "poll", - [poll.poll_options.first.digest] + [poll.poll_options.first.digest], ) - end.to raise_error( - DiscoursePoll::Error, - I18n.t("poll.min_vote_per_user", count: poll.min) - ) + end.to raise_error(DiscoursePoll::Error, I18n.t("poll.min_vote_per_user", count: poll.min)) end - it 'should allow user to vote on a multiple poll even if min option is not configured' do + it "should allow user to vote on a multiple poll even if min option is not configured" do post_with_multiple_poll = Fabricate(:post, raw: <<~RAW) [poll type=multiple max=3] * 1 @@ -143,14 +126,15 @@ RSpec.describe DiscoursePoll::Poll do user, post_with_multiple_poll.id, "poll", - [poll.poll_options.first.digest] + [poll.poll_options.first.digest], ) - expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)) - .to contain_exactly(poll.poll_options.first.id) + expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)).to contain_exactly( + poll.poll_options.first.id, + ) end - it 'should allow user to vote on a multiple poll even if max option is not configured' do + it "should allow user to vote on a multiple poll even if max option is not configured" do post_with_multiple_poll = Fabricate(:post, raw: <<~RAW) [poll type=multiple min=1] * 1 @@ -167,11 +151,13 @@ RSpec.describe DiscoursePoll::Poll do user, post_with_multiple_poll.id, "poll", - [poll.poll_options.first.digest, poll.poll_options.second.digest] + [poll.poll_options.first.digest, poll.poll_options.second.digest], ) - expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)) - .to contain_exactly(poll.poll_options.first.id, poll.poll_options.second.id) + expect(PollVote.where(poll: poll, user: user).pluck(:poll_option_id)).to contain_exactly( + poll.poll_options.first.id, + poll.poll_options.second.id, + ) end end @@ -179,19 +165,14 @@ RSpec.describe DiscoursePoll::Poll do it "publishes on message bus if a there are polls" do first_post = Fabricate(:post) topic = first_post.topic - creator = PostCreator.new(user, - topic_id: topic.id, - raw: <<~RAW + creator = PostCreator.new(user, topic_id: topic.id, raw: <<~RAW) [poll] * 1 * 2 [/poll] RAW - ) - messages = MessageBus.track_publish("/polls/#{topic.id}") do - creator.create! - end + messages = MessageBus.track_publish("/polls/#{topic.id}") { creator.create! } expect(messages.count).to eq(1) end @@ -199,20 +180,16 @@ RSpec.describe DiscoursePoll::Poll do it "does not publish on message bus when a post with no polls is created" do first_post = Fabricate(:post) topic = first_post.topic - creator = PostCreator.new(user, - topic_id: topic.id, - raw: "Just a post with definitely no polls" - ) + creator = + PostCreator.new(user, topic_id: topic.id, raw: "Just a post with definitely no polls") - messages = MessageBus.track_publish("/polls/#{topic.id}") do - creator.create! - end + messages = MessageBus.track_publish("/polls/#{topic.id}") { creator.create! } expect(messages.count).to eq(0) end end - describe '.extract' do + describe ".extract" do it "skips the polls inside quote" do raw = <<~RAW [quote="username, post:1, topic:2"] @@ -230,18 +207,17 @@ RSpec.describe DiscoursePoll::Poll do Post with a poll and a quoted poll. RAW - expect(DiscoursePoll::Poll.extract(raw, 2)).to contain_exactly({ - "name" => "poll", - "options" => [{ - "html" => "3", - "id" => "68b434ff88aeae7054e42cd05a4d9056" - }, { - "html" => "4", - "id" => "aa2393b424f2f395abb63bf785760a3b" - }], - "status" => "open", - "type" => "regular" - }) + expect(DiscoursePoll::Poll.extract(raw, 2)).to contain_exactly( + { + "name" => "poll", + "options" => [ + { "html" => "3", "id" => "68b434ff88aeae7054e42cd05a4d9056" }, + { "html" => "4", "id" => "aa2393b424f2f395abb63bf785760a3b" }, + ], + "status" => "open", + "type" => "regular", + }, + ) end end end diff --git a/plugins/poll/spec/lib/polls_updater_spec.rb b/plugins/poll/spec/lib/polls_updater_spec.rb index b6dde6817a..f42ec70186 100644 --- a/plugins/poll/spec/lib/polls_updater_spec.rb +++ b/plugins/poll/spec/lib/polls_updater_spec.rb @@ -1,68 +1,54 @@ # frozen_string_literal: true RSpec.describe DiscoursePoll::PollsUpdater do - def update(post, polls) DiscoursePoll::PollsUpdater.update(post, polls) end let(:user) { Fabricate(:user) } - let(:post) { - Fabricate(:post, raw: <<~RAW) + let(:post) { Fabricate(:post, raw: <<~RAW) } [poll] * 1 * 2 [/poll] RAW - } - let(:post_with_3_options) { - Fabricate(:post, raw: <<~RAW) + let(:post_with_3_options) { Fabricate(:post, raw: <<~RAW) } [poll] - a - b - c [/poll] RAW - } - let(:post_with_some_attributes) { - Fabricate(:post, raw: <<~RAW) + let(:post_with_some_attributes) { Fabricate(:post, raw: <<~RAW) } [poll close=#{1.week.from_now.to_formatted_s(:iso8601)} results=on_close] - A - B - C [/poll] RAW - } - let(:polls) { - DiscoursePoll::PollsValidator.new(post).validate_polls - } + let(:polls) { DiscoursePoll::PollsValidator.new(post).validate_polls } - let(:polls_with_3_options) { + let(:polls_with_3_options) do DiscoursePoll::PollsValidator.new(post_with_3_options).validate_polls - } + end - let(:polls_with_some_attributes) { + let(:polls_with_some_attributes) do DiscoursePoll::PollsValidator.new(post_with_some_attributes).validate_polls - } + end describe "update" do - it "does nothing when there are no changes" do - message = MessageBus.track_publish("/polls/#{post.topic_id}") do - update(post, polls) - end.first + message = MessageBus.track_publish("/polls/#{post.topic_id}") { update(post, polls) }.first expect(message).to be(nil) end describe "when editing" do - - let(:raw) do - <<~RAW + let(:raw) { <<~RAW } This is a new poll with three options. [poll type=multiple results=always min=1 max=2] @@ -71,7 +57,6 @@ RSpec.describe DiscoursePoll::PollsUpdater do * third [/poll] RAW - end let(:post) { Fabricate(:post, raw: raw) } @@ -84,11 +69,9 @@ RSpec.describe DiscoursePoll::PollsUpdater do expect(post.errors[:base].size).to equal(0) end - end describe "deletes polls" do - it "that were removed" do update(post, {}) @@ -97,19 +80,15 @@ RSpec.describe DiscoursePoll::PollsUpdater do expect(Poll.where(post: post).exists?).to eq(false) expect(post.custom_fields[DiscoursePoll::HAS_POLLS]).to eq(nil) end - end describe "creates polls" do - it "that were added" do post = Fabricate(:post) expect(Poll.find_by(post: post)).to_not be - message = MessageBus.track_publish("/polls/#{post.topic_id}") do - update(post, polls) - end.first + message = MessageBus.track_publish("/polls/#{post.topic_id}") { update(post, polls) }.first poll = Poll.find_by(post: post) @@ -121,21 +100,19 @@ RSpec.describe DiscoursePoll::PollsUpdater do expect(message.data[:post_id]).to eq(post.id) expect(message.data[:polls][0][:name]).to eq(poll.name) end - end describe "updates polls" do - describe "when there are no votes" do - it "at any time" do post # create the post freeze_time 1.month.from_now - message = MessageBus.track_publish("/polls/#{post.topic_id}") do - update(post, polls_with_some_attributes) - end.first + message = + MessageBus + .track_publish("/polls/#{post.topic_id}") { update(post, polls_with_some_attributes) } + .first poll = Poll.find_by(post: post) @@ -150,11 +127,9 @@ RSpec.describe DiscoursePoll::PollsUpdater do expect(message.data[:post_id]).to eq(post.id) expect(message.data[:polls][0][:name]).to eq(poll.name) end - end describe "when there are votes" do - before do expect { DiscoursePoll::Poll.vote(user, post.id, "poll", [polls["poll"]["options"][0]["id"]]) @@ -162,11 +137,13 @@ RSpec.describe DiscoursePoll::PollsUpdater do end describe "inside the edit window" do - it "and deletes the votes" do - message = MessageBus.track_publish("/polls/#{post.topic_id}") do - update(post, polls_with_some_attributes) - end.first + message = + MessageBus + .track_publish("/polls/#{post.topic_id}") do + update(post, polls_with_some_attributes) + end + .first poll = Poll.find_by(post: post) @@ -181,11 +158,9 @@ RSpec.describe DiscoursePoll::PollsUpdater do expect(message.data[:post_id]).to eq(post.id) expect(message.data[:polls][0][:name]).to eq(poll.name) end - end describe "outside the edit window" do - it "throws an error" do edit_window = SiteSetting.poll_edit_window_mins @@ -204,17 +179,12 @@ RSpec.describe DiscoursePoll::PollsUpdater do expect(post.errors[:base]).to include( I18n.t( "poll.edit_window_expired.cannot_edit_default_poll_with_votes", - minutes: edit_window - ) + minutes: edit_window, + ), ) end - end - end - end - end - end diff --git a/plugins/poll/spec/lib/polls_validator_spec.rb b/plugins/poll/spec/lib/polls_validator_spec.rb index 2ad690c81c..583a96c80e 100644 --- a/plugins/poll/spec/lib/polls_validator_spec.rb +++ b/plugins/poll/spec/lib/polls_validator_spec.rb @@ -18,9 +18,15 @@ RSpec.describe ::DiscoursePoll::PollsValidator do post.raw = raw expect(post.valid?).to eq(false) - expect(post.errors[:base]).to include(I18n.t("poll.invalid_argument", argument: "type", value: "not_good1")) - expect(post.errors[:base]).to include(I18n.t("poll.invalid_argument", argument: "status", value: "not_good2")) - expect(post.errors[:base]).to include(I18n.t("poll.invalid_argument", argument: "results", value: "not_good3")) + expect(post.errors[:base]).to include( + I18n.t("poll.invalid_argument", argument: "type", value: "not_good1"), + ) + expect(post.errors[:base]).to include( + I18n.t("poll.invalid_argument", argument: "status", value: "not_good2"), + ) + expect(post.errors[:base]).to include( + I18n.t("poll.invalid_argument", argument: "results", value: "not_good3"), + ) end it "ensures that all possible values are valid" do @@ -70,9 +76,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do post.raw = raw expect(post.valid?).to eq(false) - expect(post.errors[:base]).to include( - I18n.t("poll.multiple_polls_without_name") - ) + expect(post.errors[:base]).to include(I18n.t("poll.multiple_polls_without_name")) raw = <<~RAW [poll name=test] @@ -90,7 +94,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.multiple_polls_with_same_name", name: "test") + I18n.t("poll.multiple_polls_with_same_name", name: "test"), ) end @@ -105,9 +109,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do post.raw = raw expect(post.valid?).to eq(false) - expect(post.errors[:base]).to include( - I18n.t("poll.default_poll_must_have_different_options") - ) + expect(post.errors[:base]).to include(I18n.t("poll.default_poll_must_have_different_options")) raw = <<~RAW [poll name=test] @@ -120,7 +122,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.named_poll_must_have_different_options", name: "test") + I18n.t("poll.named_poll_must_have_different_options", name: "test"), ) end @@ -136,7 +138,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.default_poll_must_not_have_any_empty_options") + I18n.t("poll.default_poll_must_not_have_any_empty_options"), ) raw = <<~RAW @@ -150,7 +152,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.named_poll_must_not_have_any_empty_options", name: "test") + I18n.t("poll.named_poll_must_not_have_any_empty_options", name: "test"), ) end @@ -163,9 +165,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do post.raw = raw expect(post.valid?).to eq(false) - expect(post.errors[:base]).to include( - I18n.t("poll.default_poll_must_have_at_least_1_option") - ) + expect(post.errors[:base]).to include(I18n.t("poll.default_poll_must_have_at_least_1_option")) raw = <<~RAW [poll name=test] @@ -176,7 +176,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.named_poll_must_have_at_least_1_option", name: "test") + I18n.t("poll.named_poll_must_have_at_least_1_option", name: "test"), ) end @@ -194,10 +194,9 @@ RSpec.describe ::DiscoursePoll::PollsValidator do post.raw = raw expect(post.valid?).to eq(false) - expect(post.errors[:base]).to include(I18n.t( - "poll.default_poll_must_have_less_options", - count: SiteSetting.poll_maximum_options - )) + expect(post.errors[:base]).to include( + I18n.t("poll.default_poll_must_have_less_options", count: SiteSetting.poll_maximum_options), + ) raw = <<~RAW [poll name=test] @@ -210,10 +209,13 @@ RSpec.describe ::DiscoursePoll::PollsValidator do post.raw = raw expect(post.valid?).to eq(false) - expect(post.errors[:base]).to include(I18n.t( - "poll.named_poll_must_have_less_options", - name: "test", count: SiteSetting.poll_maximum_options - )) + expect(post.errors[:base]).to include( + I18n.t( + "poll.named_poll_must_have_less_options", + name: "test", + count: SiteSetting.poll_maximum_options, + ), + ) end describe "multiple type polls" do @@ -230,7 +232,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters") + I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"), ) raw = <<~RAW @@ -245,7 +247,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.named_poll_with_multiple_choices_has_invalid_parameters", name: "test") + I18n.t("poll.named_poll_with_multiple_choices_has_invalid_parameters", name: "test"), ) end @@ -261,7 +263,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters") + I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"), ) end @@ -277,7 +279,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters") + I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"), ) end @@ -293,7 +295,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters") + I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"), ) end @@ -321,7 +323,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters") + I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"), ) end @@ -337,7 +339,7 @@ RSpec.describe ::DiscoursePoll::PollsValidator do expect(post.valid?).to eq(false) expect(post.errors[:base]).to include( - I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters") + I18n.t("poll.default_poll_with_multiple_choices_has_invalid_parameters"), ) end end @@ -350,9 +352,15 @@ RSpec.describe ::DiscoursePoll::PollsValidator do post.raw = raw expect(post.valid?).to eq(false) - expect(post.errors[:base]).to include("Min #{I18n.t("errors.messages.greater_than", count: 0)}") - expect(post.errors[:base]).to include("Max #{I18n.t("errors.messages.greater_than", count: "min")}") - expect(post.errors[:base]).to include("Step #{I18n.t("errors.messages.greater_than", count: 0)}") + expect(post.errors[:base]).to include( + "Min #{I18n.t("errors.messages.greater_than", count: 0)}", + ) + expect(post.errors[:base]).to include( + "Max #{I18n.t("errors.messages.greater_than", count: "min")}", + ) + expect(post.errors[:base]).to include( + "Step #{I18n.t("errors.messages.greater_than", count: 0)}", + ) raw = <<~RAW [poll type=number min=9999999999 max=9999999999 step=1] @@ -361,8 +369,12 @@ RSpec.describe ::DiscoursePoll::PollsValidator do post.raw = raw expect(post.valid?).to eq(false) - expect(post.errors[:base]).to include("Min #{I18n.t("errors.messages.less_than", count: 2_147_483_647)}") - expect(post.errors[:base]).to include("Max #{I18n.t("errors.messages.less_than", count: 2_147_483_647)}") + expect(post.errors[:base]).to include( + "Min #{I18n.t("errors.messages.less_than", count: 2_147_483_647)}", + ) + expect(post.errors[:base]).to include( + "Max #{I18n.t("errors.messages.less_than", count: 2_147_483_647)}", + ) expect(post.errors[:base]).to include(I18n.t("poll.default_poll_must_have_at_least_1_option")) end end diff --git a/plugins/poll/spec/lib/pretty_text_spec.rb b/plugins/poll/spec/lib/pretty_text_spec.rb index 6c4c48c188..9f6b6763dd 100644 --- a/plugins/poll/spec/lib/pretty_text_spec.rb +++ b/plugins/poll/spec/lib/pretty_text_spec.rb @@ -1,12 +1,11 @@ # frozen_string_literal: true RSpec.describe PrettyText do - def n(html) html.strip end - it 'supports multi choice polls' do + it "supports multi choice polls" do cooked = PrettyText.cook <<~MD [poll type=multiple min=1 max=3 public=true] * option 1 @@ -24,17 +23,16 @@ RSpec.describe PrettyText do expect(cooked).to include('data-poll-public="true"') end - it 'can dynamically generate a poll' do - + it "can dynamically generate a poll" do cooked = PrettyText.cook <<~MD [poll type=number min=1 max=20 step=1] [/poll] MD - expect(cooked.scan('test @@ -83,7 +81,7 @@ RSpec.describe PrettyText do expect(tight_hashes).to eq(loose_hashes) end - it 'can correctly cook polls' do + it "can correctly cook polls" do md = <<~MD [poll type=multiple] 1. test 1 :) test @@ -115,10 +113,9 @@ RSpec.describe PrettyText do # note, hashes should remain stable even if emoji changes cause text content is hashed expect(n cooked).to eq(n expected) - end - it 'can onebox posts' do + it "can onebox posts" do post = Fabricate(:post, raw: <<~MD) A post with a poll @@ -129,13 +126,13 @@ RSpec.describe PrettyText do MD onebox = Oneboxer.onebox_raw(post.full_url, user_id: Fabricate(:user).id) - doc = Nokogiri::HTML5(onebox[:preview]) + doc = Nokogiri.HTML5(onebox[:preview]) expect(onebox[:preview]).to include("A post with a poll") expect(onebox[:preview]).to include("poll") end - it 'can reduce excerpts' do + it "can reduce excerpts" do post = Fabricate(:post, raw: <<~MD) A post with a poll @@ -187,8 +184,12 @@ RSpec.describe PrettyText do
HTML - expect(cooked).to include("

\nPre-heading

") - expect(cooked).to include("

\nPost-heading

") + expect(cooked).to include( + "

\nPre-heading

", + ) + expect(cooked).to include( + "

\nPost-heading

", + ) end it "does not break when there are headings before/after a poll without a title" do @@ -209,7 +210,11 @@ RSpec.describe PrettyText do
HTML - expect(cooked).to include("

\nPre-heading

") - expect(cooked).to include("

\nPost-heading

") + expect(cooked).to include( + "

\nPre-heading

", + ) + expect(cooked).to include( + "

\nPost-heading

", + ) end end diff --git a/plugins/poll/spec/models/poll_spec.rb b/plugins/poll/spec/models/poll_spec.rb index 9b72a4a9ec..2afc37cc59 100644 --- a/plugins/poll/spec/models/poll_spec.rb +++ b/plugins/poll/spec/models/poll_spec.rb @@ -5,13 +5,17 @@ RSpec.describe ::DiscoursePoll::Poll do it "Transforms UserField name if a matching CustomUserField is present" do user_field_name = "Something Cool" user_field = Fabricate(:user_field, name: user_field_name) - expect(::DiscoursePoll::Poll.transform_for_user_field_override(user_field_name)).to eq("user_field_#{user_field.id}") + expect(::DiscoursePoll::Poll.transform_for_user_field_override(user_field_name)).to eq( + "user_field_#{user_field.id}", + ) end it "does not transform UserField name if a matching CustomUserField is not present" do user_field_name = "Something Cool" user_field = Fabricate(:user_field, name: "Something Else!") - expect(::DiscoursePoll::Poll.transform_for_user_field_override(user_field_name)).to eq(user_field_name) + expect(::DiscoursePoll::Poll.transform_for_user_field_override(user_field_name)).to eq( + user_field_name, + ) end end @@ -61,14 +65,14 @@ RSpec.describe ::DiscoursePoll::Poll do option = poll.poll_options.first expect(poll.can_see_results?(user)).to eq(false) - poll.poll_votes.create!(poll_option_id: option.id , user_id: user.id) + poll.poll_votes.create!(poll_option_id: option.id, user_id: user.id) expect(poll.can_see_results?(user)).to eq(false) user.update!(moderator: true) expect(poll.can_see_results?(user)).to eq(true) end end - describe 'when post is trashed' do + describe "when post is trashed" do it "maintains the association" do user = Fabricate(:user) post = Fabricate(:post, raw: "[poll results=staff_only]\n- A\n- B\n[/poll]", user: user) diff --git a/plugins/poll/spec/requests/users_controller_spec.rb b/plugins/poll/spec/requests/users_controller_spec.rb index 78e387de6e..b1a0126729 100644 --- a/plugins/poll/spec/requests/users_controller_spec.rb +++ b/plugins/poll/spec/requests/users_controller_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Admin::UsersController do before { sign_in(admin) } - describe '#destroy' do + describe "#destroy" do let(:delete_me) { Fabricate(:user) } context "when user has voted" do diff --git a/plugins/poll/spec/serializers/poll_option_serializer_spec.rb b/plugins/poll/spec/serializers/poll_option_serializer_spec.rb index ad46e77961..a6eff33c33 100644 --- a/plugins/poll/spec/serializers/poll_option_serializer_spec.rb +++ b/plugins/poll/spec/serializers/poll_option_serializer_spec.rb @@ -4,7 +4,9 @@ def serialize_option(option, user) PollOptionSerializer.new( option, root: false, - scope: { can_see_results: poll.can_see_results?(user) } + scope: { + can_see_results: poll.can_see_results?(user), + }, ) end @@ -12,17 +14,15 @@ RSpec.describe PollOptionSerializer do let(:voter) { Fabricate(:user) } let(:poll) { post.polls.first } - before do - poll.poll_votes.create!(poll_option_id: poll.poll_options.first.id, user_id: voter.id) - end + before { poll.poll_votes.create!(poll_option_id: poll.poll_options.first.id, user_id: voter.id) } - context 'when poll results are public' do + context "when poll results are public" do let(:post) { Fabricate(:post, raw: "[poll]\n- A\n- B\n[/poll]") } - context 'when user is not staff' do + context "when user is not staff" do let(:user) { Fabricate(:user) } - it 'include votes' do + it "include votes" do serializer = serialize_option(poll.poll_options.first, user) expect(serializer.include_votes?).to eq(true) @@ -30,23 +30,23 @@ RSpec.describe PollOptionSerializer do end end - context 'when poll results are staff only' do + context "when poll results are staff only" do let(:post) { Fabricate(:post, raw: "[poll results=staff_only]\n- A\n- B\n[/poll]") } - context 'when user is not staff' do + context "when user is not staff" do let(:user) { Fabricate(:user) } - it 'doesn’t include votes' do + it "doesn’t include votes" do serializer = serialize_option(poll.poll_options.first, user) expect(serializer.include_votes?).to eq(false) end end - context 'when user is staff' do + context "when user is staff" do let(:admin) { Fabricate(:admin) } - it 'includes votes' do + it "includes votes" do serializer = serialize_option(poll.poll_options.first, admin) expect(serializer.include_votes?).to eq(true) diff --git a/plugins/styleguide/app/controllers/styleguide/styleguide_controller.rb b/plugins/styleguide/app/controllers/styleguide/styleguide_controller.rb index 94f853c15f..ebdab9b8f1 100644 --- a/plugins/styleguide/app/controllers/styleguide/styleguide_controller.rb +++ b/plugins/styleguide/app/controllers/styleguide/styleguide_controller.rb @@ -8,7 +8,7 @@ module Styleguide def index ensure_admin if SiteSetting.styleguide_admin_only - render 'default/empty' + render "default/empty" end end end diff --git a/plugins/styleguide/config/routes.rb b/plugins/styleguide/config/routes.rb index 57efad0014..46812dae1c 100644 --- a/plugins/styleguide/config/routes.rb +++ b/plugins/styleguide/config/routes.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true Styleguide::Engine.routes.draw do - get "/" => 'styleguide#index' - get "/:category/:section" => 'styleguide#index' + get "/" => "styleguide#index" + get "/:category/:section" => "styleguide#index" end diff --git a/plugins/styleguide/plugin.rb b/plugins/styleguide/plugin.rb index 4c3244b1f6..0611793c0c 100644 --- a/plugins/styleguide/plugin.rb +++ b/plugins/styleguide/plugin.rb @@ -8,14 +8,12 @@ register_asset "stylesheets/styleguide.scss" enabled_site_setting :styleguide_enabled -load File.expand_path('../lib/styleguide/engine.rb', __FILE__) +load File.expand_path("../lib/styleguide/engine.rb", __FILE__) -Discourse::Application.routes.append do - mount ::Styleguide::Engine, at: '/styleguide' -end +Discourse::Application.routes.append { mount ::Styleguide::Engine, at: "/styleguide" } after_initialize do register_asset_filter do |type, request, opts| - (opts[:path] || '').start_with?("#{Discourse.base_path}/styleguide") + (opts[:path] || "").start_with?("#{Discourse.base_path}/styleguide") end end diff --git a/plugins/styleguide/spec/integration/access_spec.rb b/plugins/styleguide/spec/integration/access_spec.rb index 568d35eafa..96e5a36c39 100644 --- a/plugins/styleguide/spec/integration/access_spec.rb +++ b/plugins/styleguide/spec/integration/access_spec.rb @@ -1,62 +1,48 @@ # frozen_string_literal: true -RSpec.describe 'SiteSetting.styleguide_admin_only' do - before do - SiteSetting.styleguide_enabled = true - end +RSpec.describe "SiteSetting.styleguide_admin_only" do + before { SiteSetting.styleguide_enabled = true } - context 'when styleguide is admin only' do - before do - SiteSetting.styleguide_admin_only = true - end + context "when styleguide is admin only" do + before { SiteSetting.styleguide_admin_only = true } - context 'when user is admin' do - before do - sign_in(Fabricate(:admin)) - end + context "when user is admin" do + before { sign_in(Fabricate(:admin)) } - it 'shows the styleguide' do - get '/styleguide' + it "shows the styleguide" do + get "/styleguide" expect(response.status).to eq(200) end end - context 'when user is not admin' do - before do - sign_in(Fabricate(:user)) - end + context "when user is not admin" do + before { sign_in(Fabricate(:user)) } - it 'doesn’t allow access' do - get '/styleguide' + it "doesn’t allow access" do + get "/styleguide" expect(response.status).to eq(403) end end end end -RSpec.describe 'SiteSetting.styleguide_enabled' do - before do - sign_in(Fabricate(:admin)) - end +RSpec.describe "SiteSetting.styleguide_enabled" do + before { sign_in(Fabricate(:admin)) } - context 'when style is enabled' do - before do - SiteSetting.styleguide_enabled = true - end + context "when style is enabled" do + before { SiteSetting.styleguide_enabled = true } - it 'shows the styleguide' do - get '/styleguide' + it "shows the styleguide" do + get "/styleguide" expect(response.status).to eq(200) end end - context 'when styleguide is disabled' do - before do - SiteSetting.styleguide_enabled = false - end + context "when styleguide is disabled" do + before { SiteSetting.styleguide_enabled = false } - it 'returns a page not found' do - get '/styleguide' + it "returns a page not found" do + get "/styleguide" expect(response.status).to eq(404) end end diff --git a/plugins/styleguide/spec/integration/assets_spec.rb b/plugins/styleguide/spec/integration/assets_spec.rb index e4d6f2cfa0..95fd56df96 100644 --- a/plugins/styleguide/spec/integration/assets_spec.rb +++ b/plugins/styleguide/spec/integration/assets_spec.rb @@ -1,22 +1,22 @@ # frozen_string_literal: true -RSpec.describe 'Styleguide assets' do +RSpec.describe "Styleguide assets" do before do SiteSetting.styleguide_enabled = true sign_in(Fabricate(:admin)) end - context 'when visiting homepage' do - it 'doesn’t load styleguide assets' do - get '/' - expect(response.body).to_not include('styleguide') + context "when visiting homepage" do + it "doesn’t load styleguide assets" do + get "/" + expect(response.body).to_not include("styleguide") end end - context 'when visiting styleguide' do - it 'loads styleguide assets' do - get '/styleguide' - expect(response.body).to include('styleguide') + context "when visiting styleguide" do + it "loads styleguide assets" do + get "/styleguide" + expect(response.body).to include("styleguide") end end end From d82c9f28a5172f8ac61ac0ea58008f3a49efa495 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Jan 2023 23:21:50 +0100 Subject: [PATCH 20/53] Build(deps): Bump rspec-expectations from 3.12.1 to 3.12.2 (#19783) Bumps [rspec-expectations](https://github.com/rspec/rspec-expectations) from 3.12.1 to 3.12.2. - [Release notes](https://github.com/rspec/rspec-expectations/releases) - [Changelog](https://github.com/rspec/rspec-expectations/blob/main/Changelog.md) - [Commits](https://github.com/rspec/rspec-expectations/compare/v3.12.1...v3.12.2) --- updated-dependencies: - dependency-name: rspec-expectations dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9c4fbc7b11..518cd2fee1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -372,7 +372,7 @@ GEM rspec-mocks (~> 3.12.0) rspec-core (3.12.0) rspec-support (~> 3.12.0) - rspec-expectations (3.12.1) + rspec-expectations (3.12.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) rspec-html-matchers (0.10.0) From 15e81b6174427bffd33c83c7e343d7d821670c09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Jan 2023 23:41:14 +0100 Subject: [PATCH 21/53] Build(deps): Bump jsdom from 20.0.3 to 21.0.0 in /app/assets/javascripts (#19786) Bumps [jsdom](https://github.com/jsdom/jsdom) from 20.0.3 to 21.0.0. - [Release notes](https://github.com/jsdom/jsdom/releases) - [Changelog](https://github.com/jsdom/jsdom/blob/master/Changelog.md) - [Commits](https://github.com/jsdom/jsdom/compare/20.0.3...21.0.0) --- updated-dependencies: - dependency-name: jsdom dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- app/assets/javascripts/bootstrap-json/package.json | 2 +- app/assets/javascripts/discourse/package.json | 2 +- app/assets/javascripts/yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/bootstrap-json/package.json b/app/assets/javascripts/bootstrap-json/package.json index ce989fc828..f35b3b0e82 100644 --- a/app/assets/javascripts/bootstrap-json/package.json +++ b/app/assets/javascripts/bootstrap-json/package.json @@ -24,7 +24,7 @@ "discourse-plugins": "1.0.0", "express": "^4.18.2", "html-entities": "^2.3.3", - "jsdom": "^20.0.3", + "jsdom": "^21.0.0", "node-fetch": "^3.3.0" } } diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json index 8a3ac2a158..c4aa0e7d36 100644 --- a/app/assets/javascripts/discourse/package.json +++ b/app/assets/javascripts/discourse/package.json @@ -78,7 +78,7 @@ "html-entities": "^2.3.3", "imports-loader": "^4.0.1", "js-yaml": "^4.1.0", - "jsdom": "^20.0.3", + "jsdom": "^21.0.0", "loader.js": "^4.7.0", "markdown-it": "^13.0.1", "message-bus-client": "^4.3.0", diff --git a/app/assets/javascripts/yarn.lock b/app/assets/javascripts/yarn.lock index 2d13091451..2b8a33d8d7 100644 --- a/app/assets/javascripts/yarn.lock +++ b/app/assets/javascripts/yarn.lock @@ -6267,10 +6267,10 @@ js-yaml@^4.0.0, js-yaml@^4.1.0: dependencies: argparse "^2.0.1" -jsdom@^20.0.3: - version "20.0.3" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-20.0.3.tgz#886a41ba1d4726f67a8858028c99489fed6ad4db" - integrity sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ== +jsdom@^21.0.0: + version "21.0.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-21.0.0.tgz#33e22f2fc44286e50ac853c7b7656c8864a4ea45" + integrity sha512-AIw+3ZakSUtDYvhwPwWHiZsUi3zHugpMEKlNPaurviseYoBqo0zBd3zqoUi3LPCNtPFlEP8FiW9MqCZdjb2IYA== dependencies: abab "^2.0.6" acorn "^8.8.1" From 9a6338c6cb2ac39a30e5a5cfa0b42b7e656ad331 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 06:54:51 +0800 Subject: [PATCH 22/53] Build(deps-dev): Bump rubocop-discourse from 3.0.2 to 3.0.3 (#19785) Bumps [rubocop-discourse](https://github.com/discourse/rubocop-discourse) from 3.0.2 to 3.0.3. - [Release notes](https://github.com/discourse/rubocop-discourse/releases) - [Commits](https://github.com/discourse/rubocop-discourse/compare/v3.0.2...v3.0.3) --- updated-dependencies: - dependency-name: rubocop-discourse dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 518cd2fee1..042ab7aec8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -409,7 +409,7 @@ GEM unicode-display_width (>= 1.4.0, < 3.0) rubocop-ast (1.24.1) parser (>= 3.1.1.0) - rubocop-discourse (3.0.2) + rubocop-discourse (3.0.3) rubocop (>= 1.1.0) rubocop-rspec (>= 2.0.0) rubocop-rspec (2.16.0) From 72318a30ec2166c7e419a6f67a7806b6270371a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Jan 2023 06:55:04 +0800 Subject: [PATCH 23/53] Build(deps): Bump rspec-mocks from 3.12.1 to 3.12.2 (#19784) Bumps [rspec-mocks](https://github.com/rspec/rspec-mocks) from 3.12.1 to 3.12.2. - [Release notes](https://github.com/rspec/rspec-mocks/releases) - [Changelog](https://github.com/rspec/rspec-mocks/blob/main/Changelog.md) - [Commits](https://github.com/rspec/rspec-mocks/compare/v3.12.1...v3.12.2) --- updated-dependencies: - dependency-name: rspec-mocks dependency-type: indirect update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 042ab7aec8..13acb3d7aa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -378,7 +378,7 @@ GEM rspec-html-matchers (0.10.0) nokogiri (~> 1) rspec (>= 3.0.0.a) - rspec-mocks (3.12.1) + rspec-mocks (3.12.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.12.0) rspec-rails (6.0.1) From 673089a6b43d79e8c6aa9ef0a742e55c3bc671cf Mon Sep 17 00:00:00 2001 From: Alan Guo Xiang Tan Date: Mon, 9 Jan 2023 07:01:58 +0800 Subject: [PATCH 24/53] FIX: Error condition in SidebarSiteSettingsBackfiller (#19787) --- app/services/sidebar_site_settings_backfiller.rb | 2 ++ spec/services/sidebar_site_settings_backfiller_spec.rb | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/app/services/sidebar_site_settings_backfiller.rb b/app/services/sidebar_site_settings_backfiller.rb index f24383d84b..884ff76546 100644 --- a/app/services/sidebar_site_settings_backfiller.rb +++ b/app/services/sidebar_site_settings_backfiller.rb @@ -84,6 +84,8 @@ class SidebarSiteSettingsBackfiller SQL end + return 0 if select_statements.blank? + DB.query_single(<<~SQL)[0] SELECT COUNT(*) diff --git a/spec/services/sidebar_site_settings_backfiller_spec.rb b/spec/services/sidebar_site_settings_backfiller_spec.rb index b88bf74394..46459d2895 100644 --- a/spec/services/sidebar_site_settings_backfiller_spec.rb +++ b/spec/services/sidebar_site_settings_backfiller_spec.rb @@ -226,6 +226,16 @@ RSpec.describe SidebarSiteSettingsBackfiller do expect(backfiller.number_of_users_to_backfill).to eq(3) end + + it "returns 0 for the user count when no new category is added or removed" do + backfiller = described_class.new( + "default_sidebar_categories", + previous_value: "", + new_value: "" + ) + + expect(backfiller.number_of_users_to_backfill).to eq(0) + end end context 'for default_sidebar_tags setting' do From c31772879bc9df51b71f75edc529bfed878d65d1 Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Mon, 9 Jan 2023 12:16:02 +1000 Subject: [PATCH 25/53] FIX: Disable image optimization in iOS Safari (#19790) There are various performance issues with the Canvas in iOS Safari that are causing crashes when processing images with spikes of over 100% CPU usage. The cause of this is unknown, but profiling points to CanvasRenderingContext2D.getImageData() and CanvasRenderingContext2D.drawImage(). Until Safari makes some progress with OffscreenCanvas or other alternatives we cannot support this workflow. We will revisit in 6 months. This is gated behind the hidden `composer_ios_media_optimisation_image_enabled` site setting for people who really still want to try using this. --- ...ter-media-optimization-upload-processor.js | 21 ++++++++++++++++++- config/site_settings.yml | 4 ++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/discourse/app/initializers/register-media-optimization-upload-processor.js b/app/assets/javascripts/discourse/app/initializers/register-media-optimization-upload-processor.js index f80b68bde1..f7cd1f6132 100644 --- a/app/assets/javascripts/discourse/app/initializers/register-media-optimization-upload-processor.js +++ b/app/assets/javascripts/discourse/app/initializers/register-media-optimization-upload-processor.js @@ -6,8 +6,27 @@ export default { name: "register-media-optimization-upload-processor", initialize(container) { - let siteSettings = container.lookup("service:site-settings"); + const siteSettings = container.lookup("service:site-settings"); + const capabilities = container.lookup("capabilities:main"); + if (siteSettings.composer_media_optimization_image_enabled) { + // NOTE: There are various performance issues with the Canvas + // in iOS Safari that are causing crashes when processing images + // with spikes of over 100% CPU usage. The cause of this is unknown, + // but profiling points to CanvasRenderingContext2D.getImageData() + // and CanvasRenderingContext2D.drawImage(). + // + // Until Safari makes some progress with OffscreenCanvas or other + // alternatives we cannot support this workflow. + // + // TODO (martin): Revisit around 2022-06-01 to see the state of iOS Safari. + if ( + capabilities.isIOS && + !siteSettings.composer_ios_media_optimisation_image_enabled + ) { + return; + } + addComposerUploadPreProcessor( UppyMediaOptimization, ({ isMobileDevice }) => { diff --git a/config/site_settings.yml b/config/site_settings.yml index 6c7e37640b..599e0de852 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -1549,6 +1549,10 @@ files: default: false client: true hidden: true + composer_ios_media_optimisation_image_enabled: + default: false + client: true + hidden: true trust: default_trust_level: From 56eaf9158960143dd4ea788d5c6fcbc34c23348f Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Mon, 9 Jan 2023 16:12:10 +1000 Subject: [PATCH 26/53] FIX: Do not error when anon user looks at secure upload for deleted post (#19792) If a secure upload's access_control_post was trashed, and an anon user tried to look at that upload, they would get a 500 error rather than the correct 403 because of an error inside the PostGuardian logic. --- lib/guardian/post_guardian.rb | 9 +++- spec/lib/guardian_spec.rb | 40 ++++++++++++++++++ spec/requests/uploads_controller_spec.rb | 52 ++++++++++++++++++------ 3 files changed, 88 insertions(+), 13 deletions(-) diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb index 49205fcc16..35e70305d2 100644 --- a/lib/guardian/post_guardian.rb +++ b/lib/guardian/post_guardian.rb @@ -239,10 +239,17 @@ module PostGuardian return false unless can_see_post_topic?(post) return false unless post.user == @user || Topic.visible_post_types(@user).include?(post.post_type) return true if is_moderator? || is_category_group_moderator?(post.topic.category) - return true if post.deleted_at.blank? || (post.deleted_by_id == @user.id && @user.has_trust_level?(TrustLevel[4])) + return true if !post.trashed? || can_see_deleted_post?(post) false end + def can_see_deleted_post?(post) + return false if !post.trashed? + return false if @user.anonymous? + return true if is_staff? + post.deleted_by_id == @user.id && @user.has_trust_level?(TrustLevel[4]) + end + def can_view_edit_history?(post) return false unless post diff --git a/spec/lib/guardian_spec.rb b/spec/lib/guardian_spec.rb index f386344328..25a0066f74 100644 --- a/spec/lib/guardian_spec.rb +++ b/spec/lib/guardian_spec.rb @@ -785,6 +785,46 @@ RSpec.describe Guardian do end end + describe "can_see_deleted_post?" do + fab!(:post) { Fabricate(:post) } + + before do + post.trash!(user) + end + + it "returns false for post that is not deleted" do + post.recover! + expect(Guardian.new(admin).can_see_deleted_post?(post)).to be_falsey + end + + it "returns false for anon" do + expect(Guardian.new.can_see_deleted_post?(post)).to be_falsey + end + + it "returns true for admin" do + expect(Guardian.new(admin).can_see_deleted_post?(post)).to be_truthy + end + + it "returns true for mods" do + expect(Guardian.new(moderator).can_see_deleted_post?(post)).to be_truthy + end + + it "returns false for < TL4 users" do + user.update!(trust_level: TrustLevel[1]) + expect(Guardian.new(user).can_see_deleted_post?(post)).to be_falsey + end + + it "returns false if not ther person who deleted it" do + post.update!(deleted_by: another_user) + expect(Guardian.new(user).can_see_deleted_post?(post)).to be_falsey + end + + it "returns true for TL4 users' own posts" do + user.update!(trust_level: TrustLevel[4]) + expect(Guardian.new(user).can_see_deleted_post?(post)).to be_truthy + end + end + describe 'can_see?' do it 'returns false with a nil object' do diff --git a/spec/requests/uploads_controller_spec.rb b/spec/requests/uploads_controller_spec.rb index 7c41038a67..dd60bc18b3 100644 --- a/spec/requests/uploads_controller_spec.rb +++ b/spec/requests/uploads_controller_spec.rb @@ -505,29 +505,57 @@ RSpec.describe UploadsController do let!(:private_category) { Fabricate(:private_category, group: Fabricate(:group)) } before do - sign_in(user) upload.update(access_control_post_id: post.id) end - context "when the user has access to the post via guardian" do - it "should return signed url for legitimate request" do - sign_in(user) + context "when the user is anon" do + it "should return signed url for public posts" do get secure_url expect(response.status).to eq(302) expect(response.redirect_url).to match("Amz-Expires") end - end - context "when the user does not have access to the post via guardian" do - before do - post.topic.change_category_to_id(private_category.id) - end - - it "returns a 403" do - sign_in(user) + it "should return 403 for deleted posts" do + post.trash! get secure_url expect(response.status).to eq(403) end + + context "when the user does not have access to the post via guardian" do + before do + post.topic.change_category_to_id(private_category.id) + end + + it "returns a 403" do + get secure_url + expect(response.status).to eq(403) + end + end + end + + context "when the user is logged in" do + before do + sign_in(user) + end + + context "when the user has access to the post via guardian" do + it "should return signed url for legitimate request" do + get secure_url + expect(response.status).to eq(302) + expect(response.redirect_url).to match("Amz-Expires") + end + end + + context "when the user does not have access to the post via guardian" do + before do + post.topic.change_category_to_id(private_category.id) + end + + it "returns a 403" do + get secure_url + expect(response.status).to eq(403) + end + end end end From ff508d1ae53fea9a8fe49b3b0c37ecedbd74530e Mon Sep 17 00:00:00 2001 From: Jarek Radosz Date: Mon, 9 Jan 2023 11:26:39 +0100 Subject: [PATCH 27/53] FIX: Correctly support DiscourseEvent kwargs (#19788) Fixes the support for kwargs in `DiscourseEvent.trigger()` on Ruby 3, e.g. ```rb DiscourseEvent.trigger(:before_system_message_sent, message_type: type, recipient: @recipient, post_creator_args: post_creator_args, params: method_params) ``` Fixes https://github.com/discourse/discourse-local-site-contacts --- lib/discourse_event.rb | 4 ++-- spec/lib/discourse_event_spec.rb | 21 +++++++++++++-------- spec/lib/system_message_spec.rb | 16 ++++++++++++++-- spec/support/discourse_event_helper.rb | 6 +++--- 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/lib/discourse_event.rb b/lib/discourse_event.rb index c2db7d1002..4d7f30e27a 100644 --- a/lib/discourse_event.rb +++ b/lib/discourse_event.rb @@ -9,9 +9,9 @@ class DiscourseEvent @events ||= Hash.new { |hash, key| hash[key] = Set.new } end - def self.trigger(event_name, *params) + def self.trigger(event_name, *args, **kwargs) events[event_name].each do |event| - event.call(*params) + event.call(*args, **kwargs) end end diff --git a/spec/lib/discourse_event_spec.rb b/spec/lib/discourse_event_spec.rb index b3e49e5f96..f48fc5a743 100644 --- a/spec/lib/discourse_event_spec.rb +++ b/spec/lib/discourse_event_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true RSpec.describe DiscourseEvent do - describe "#events" do it "defaults to {}" do begin @@ -21,7 +20,6 @@ RSpec.describe DiscourseEvent do end context 'when calling events' do - let(:harvey) { OpenStruct.new( name: 'Harvey Dent', @@ -42,15 +40,12 @@ RSpec.describe DiscourseEvent do end context 'when event does not exist' do - it "does not raise an error" do DiscourseEvent.trigger(:missing_event) end - end context 'when single event exists' do - it "doesn't raise an error" do DiscourseEvent.trigger(:acid_face, harvey) end @@ -59,11 +54,9 @@ RSpec.describe DiscourseEvent do DiscourseEvent.trigger(:acid_face, harvey) expect(harvey.name).to eq('Two Face') end - end context 'when multiple events exist' do - let(:event_handler_2) do Proc.new { |user| user.job = 'Supervillain' } end @@ -84,7 +77,6 @@ RSpec.describe DiscourseEvent do end describe '#all_off' do - let(:event_handler_2) do Proc.new { |user| user.job = 'Supervillain' } end @@ -99,7 +91,20 @@ RSpec.describe DiscourseEvent do DiscourseEvent.trigger(:acid_face, harvey) # Doesn't change anything expect(harvey.job).to eq('gardening') end + end + end + it "allows using kwargs" do + begin + handler = Proc.new do |name:, message:| + expect(name).to eq("Supervillain") + expect(message).to eq("Two Face") + end + + DiscourseEvent.on(:acid_face, &handler) + DiscourseEvent.trigger(:acid_face, name: "Supervillain", message: "Two Face") + ensure + DiscourseEvent.off(:acid_face, &handler) end end end diff --git a/spec/lib/system_message_spec.rb b/spec/lib/system_message_spec.rb index 65062a0951..160343d97f 100644 --- a/spec/lib/system_message_spec.rb +++ b/spec/lib/system_message_spec.rb @@ -4,9 +4,7 @@ require 'system_message' require 'topic_subtype' RSpec.describe SystemMessage do - describe '#create' do - fab!(:admin) { Fabricate(:admin) } fab!(:user) { Fabricate(:user) } @@ -81,12 +79,26 @@ RSpec.describe SystemMessage do it 'sends event with post object' do system_message = SystemMessage.new(user) + event = DiscourseEvent.track(:system_message_sent) { system_message.create(:tl2_promotion_message) } + expect(event[:event_name]).to eq(:system_message_sent) expect(event[:params].first[:post]).to eq(Post.last) expect(event[:params].first[:message_type]).to eq(:tl2_promotion_message) end + + it 'sends an event before the system message is sent' do + system_message = SystemMessage.new(user) + + event = DiscourseEvent.track(:before_system_message_sent) { + system_message.create(:tl2_promotion_message) + } + + expect(event[:event_name]).to eq(:before_system_message_sent) + expect(event[:params].first[:message_type]).to eq(:tl2_promotion_message) + expect(event[:params].first[:recipient]).to eq(user) + end end end diff --git a/spec/support/discourse_event_helper.rb b/spec/support/discourse_event_helper.rb index 978be3c659..a0531b8ca9 100644 --- a/spec/support/discourse_event_helper.rb +++ b/spec/support/discourse_event_helper.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true module DiscourseEvent::TestHelper - def trigger(event_name, *params) - super(event_name, *params) + def trigger(event_name, *params, **kwargs) + super(event_name, *params, **kwargs) if @events_trigger + params << kwargs if kwargs != {} @events_trigger << { event_name: event_name, params: params } end end @@ -29,7 +30,6 @@ module DiscourseEvent::TestHelper events = track_events(event_name, args: args) { yield } events.first end - end DiscourseEvent.singleton_class.prepend DiscourseEvent::TestHelper From 436b3b392b9c917510d4ff0d73a5167cd3eb936c Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 7 Jan 2023 11:53:14 +0000 Subject: [PATCH 28/53] DEV: Apply syntax_tree formatting to `script/*` --- .streerc | 1 - script/analyse_message_bus.rb | 41 +- script/analyze_sidekiq_queues.rb | 11 +- script/bench.rb | 195 ++-- script/benchmarks/cache/bench.rb | 5 +- script/benchmarks/markdown/bench.rb | 10 +- script/benchmarks/middleware/test.rb | 18 +- script/benchmarks/site_setting/bench.rb | 33 +- script/benchmarks/site_setting/profile.rb | 24 +- script/biggest_objects.rb | 34 +- script/boot_mem.rb | 34 +- script/bulk_import/base.rb | 437 +++++---- script/bulk_import/discourse_merger.rb | 757 ++++++++------- script/bulk_import/phpbb_postgresql.rb | 153 +-- script/bulk_import/vanilla.rb | 474 +++++----- script/bulk_import/vbulletin.rb | 216 +++-- script/bulk_import/vbulletin5.rb | 161 ++-- script/check_forking.rb | 22 +- script/db_timestamps_mover.rb | 32 +- script/diff_heaps.rb | 40 +- script/docker_test.rb | 6 +- script/i18n_lint.rb | 42 +- script/import_scripts/answerbase.rb | 167 ++-- script/import_scripts/answerhub.rb | 211 ++--- script/import_scripts/askbot.rb | 114 ++- script/import_scripts/base.rb | 171 ++-- script/import_scripts/base/csv_helper.rb | 78 +- .../import_scripts/base/generic_database.rb | 38 +- .../import_scripts/base/lookup_container.rb | 42 +- script/import_scripts/base/uploader.rb | 20 +- script/import_scripts/bbpress.rb | 230 ++--- script/import_scripts/bespoke_1.rb | 131 ++- script/import_scripts/csv_importer.rb | 117 ++- .../csv_restore_staged_users.rb | 50 +- script/import_scripts/discuz_x.rb | 877 +++++++++++------- script/import_scripts/disqus.rb | 68 +- script/import_scripts/drupal-6.rb | 150 +-- script/import_scripts/drupal.rb | 225 +++-- script/import_scripts/drupal_json.rb | 9 +- script/import_scripts/drupal_qa.rb | 157 ++-- script/import_scripts/elgg.rb | 106 ++- script/import_scripts/flarum_import.rb | 102 +- script/import_scripts/fluxbb.rb | 159 ++-- script/import_scripts/friendsmegplus.rb | 197 ++-- script/import_scripts/getsatisfaction.rb | 96 +- script/import_scripts/google_groups.rb | 148 +-- script/import_scripts/higher_logic.rb | 96 +- script/import_scripts/ipboard.rb | 734 ++++++++------- script/import_scripts/ipboard3.rb | 121 +-- script/import_scripts/jforum.rb | 91 +- script/import_scripts/jive.rb | 224 +++-- script/import_scripts/jive_api.rb | 265 ++++-- script/import_scripts/json_generic.rb | 62 +- script/import_scripts/kunena.rb | 101 +- script/import_scripts/kunena3.rb | 122 ++- script/import_scripts/lithium.rb | 563 ++++++----- script/import_scripts/mbox.rb | 7 +- script/import_scripts/mbox/importer.rb | 86 +- .../import_scripts/mbox/support/database.rb | 33 +- script/import_scripts/mbox/support/indexer.rb | 55 +- .../import_scripts/mbox/support/settings.rb | 34 +- script/import_scripts/modx.rb | 212 +++-- script/import_scripts/muut.rb | 67 +- script/import_scripts/mybb.rb | 194 ++-- script/import_scripts/mybbru.rb | 62 +- script/import_scripts/mylittleforum.rb | 313 ++++--- script/import_scripts/nabble.rb | 99 +- script/import_scripts/ning.rb | 184 ++-- script/import_scripts/nodebb/mongo.rb | 22 +- script/import_scripts/nodebb/nodebb.rb | 196 ++-- script/import_scripts/nodebb/redis.rb | 18 +- script/import_scripts/phorum.rb | 153 +-- script/import_scripts/phpbb3.rb | 18 +- .../phpbb3/database/database.rb | 12 +- .../phpbb3/database/database_3_0.rb | 4 +- .../phpbb3/database/database_3_1.rb | 19 +- .../phpbb3/database/database_base.rb | 4 +- script/import_scripts/phpbb3/importer.rb | 75 +- .../phpbb3/importers/avatar_importer.rb | 29 +- .../phpbb3/importers/bookmark_importer.rb | 2 +- .../phpbb3/importers/category_importer.rb | 23 +- .../phpbb3/importers/importer_factory.rb | 34 +- .../phpbb3/importers/message_importer.rb | 43 +- .../phpbb3/importers/permalink_importer.rb | 12 +- .../phpbb3/importers/poll_importer.rb | 24 +- .../phpbb3/importers/post_importer.rb | 21 +- .../phpbb3/importers/user_importer.rb | 81 +- .../phpbb3/support/bbcode/markdown_node.rb | 6 +- .../phpbb3/support/bbcode/xml_to_markdown.rb | 107 ++- .../phpbb3/support/constants.rb | 10 +- .../import_scripts/phpbb3/support/settings.rb | 87 +- .../phpbb3/support/smiley_processor.rb | 60 +- .../phpbb3/support/text_processor.rb | 103 +- script/import_scripts/punbb.rb | 121 +-- script/import_scripts/quandora/export.rb | 20 +- script/import_scripts/quandora/import.rb | 25 +- .../import_scripts/quandora/quandora_api.rb | 11 +- .../quandora/quandora_question.rb | 77 +- .../import_scripts/quandora/test/test_data.rb | 11 +- .../quandora/test/test_quandora_api.rb | 59 +- .../quandora/test/test_quandora_question.rb | 115 ++- script/import_scripts/question2answer.rb | 216 +++-- script/import_scripts/sfn.rb | 43 +- script/import_scripts/simplepress.rb | 116 +-- script/import_scripts/smf1.rb | 290 +++--- script/import_scripts/smf2.rb | 448 +++++---- .../import_scripts/socialcast/create_title.rb | 9 +- script/import_scripts/socialcast/export.rb | 30 +- script/import_scripts/socialcast/import.rb | 30 +- .../socialcast/socialcast_api.rb | 9 +- .../socialcast/socialcast_message.rb | 53 +- .../socialcast/socialcast_user.rb | 18 +- .../socialcast/test/test_create_title.rb | 42 +- .../socialcast/test/test_data.rb | 9 +- .../socialcast/test/test_socialcast_api.rb | 45 +- script/import_scripts/socialcast/title.rb | 12 +- script/import_scripts/sourceforge.rb | 45 +- script/import_scripts/stack_overflow.rb | 73 +- .../support/convert_mysql_xml_to_mysql.rb | 19 +- script/import_scripts/telligent.rb | 237 ++--- script/import_scripts/vanilla.rb | 93 +- script/import_scripts/vanilla_body_parser.rb | 92 +- script/import_scripts/vanilla_mysql.rb | 474 ++++++---- script/import_scripts/vbulletin.rb | 368 ++++---- script/import_scripts/vbulletin5.rb | 421 +++++---- script/import_scripts/xenforo.rb | 296 +++--- script/import_scripts/yahoogroup.rb | 73 +- script/import_scripts/zendesk.rb | 113 +-- script/import_scripts/zendesk_api.rb | 282 +++--- script/import_scripts/zoho.rb | 135 ++- script/measure.rb | 39 +- script/memstats.rb | 46 +- script/micro_bench.rb | 26 +- script/profile_db_generator.rb | 79 +- script/redis_memory.rb | 11 +- script/require_profiler.rb | 46 +- script/spawn_backup_restore.rb | 7 +- script/test_email_settings.rb | 24 +- script/test_mem.rb | 23 +- script/test_memory_leak.rb | 88 +- script/test_pretty_text.rb | 2 +- script/thread_detective.rb | 9 +- script/user_simulator.rb | 31 +- 143 files changed, 8905 insertions(+), 7353 deletions(-) diff --git a/.streerc b/.streerc index 8a00459773..612e4b246d 100644 --- a/.streerc +++ b/.streerc @@ -5,5 +5,4 @@ --ignore-files=config/* --ignore-files=db/* --ignore-files=lib/* ---ignore-files=script/* --ignore-files=spec/* diff --git a/script/analyse_message_bus.rb b/script/analyse_message_bus.rb index a65bd53f02..ef3e0eacb3 100644 --- a/script/analyse_message_bus.rb +++ b/script/analyse_message_bus.rb @@ -9,23 +9,24 @@ wait_seconds = ARGV[0]&.to_i || 10 puts "Counting messages for #{wait_seconds} seconds..." -print 'Seen 0 messages' -t = Thread.new do - MessageBus.backend_instance.global_subscribe do |m| - channel = m.channel - if channel.start_with?("/distributed_hash") - payload = JSON.parse(m.data)["data"] - info = payload["hash_key"] - # info += ".#{payload["key"]}" # Uncomment if you need more granular info - channel += " (#{info})" +print "Seen 0 messages" +t = + Thread.new do + MessageBus.backend_instance.global_subscribe do |m| + channel = m.channel + if channel.start_with?("/distributed_hash") + payload = JSON.parse(m.data)["data"] + info = payload["hash_key"] + # info += ".#{payload["key"]}" # Uncomment if you need more granular info + channel += " (#{info})" + end + + channel_counters[channel] += 1 + messages_seen += 1 + + print "\rSeen #{messages_seen} messages from #{channel_counters.size} channels" end - - channel_counters[channel] += 1 - messages_seen += 1 - - print "\rSeen #{messages_seen} messages from #{channel_counters.size} channels" end -end sleep wait_seconds @@ -53,10 +54,12 @@ puts "| #{"channel".ljust(max_channel_name_length)} | #{"message count".rjust(ma puts "|#{"-" * (max_channel_name_length + 2)}|#{"-" * (max_count_length + 2)}|" result_count = 10 -sorted_results.first(result_count).each do |name, value| - name = "`#{name}`" - puts "| #{name.ljust(max_channel_name_length)} | #{value.to_s.rjust(max_count_length)} |" -end +sorted_results + .first(result_count) + .each do |name, value| + name = "`#{name}`" + puts "| #{name.ljust(max_channel_name_length)} | #{value.to_s.rjust(max_count_length)} |" + end other_count = messages_seen - sorted_results.first(result_count).sum { |k, v| v } puts "| #{"(other)".ljust(max_channel_name_length)} | #{other_count.to_s.rjust(max_count_length)} |" puts "|#{" " * (max_channel_name_length + 2)}|#{" " * (max_count_length + 2)}|" diff --git a/script/analyze_sidekiq_queues.rb b/script/analyze_sidekiq_queues.rb index fcb736d762..d5e9c4fd34 100644 --- a/script/analyze_sidekiq_queues.rb +++ b/script/analyze_sidekiq_queues.rb @@ -2,17 +2,14 @@ require File.expand_path("../../config/environment", __FILE__) -queues = %w{default low ultra_low critical}.map { |name| Sidekiq::Queue.new(name) }.lazy.flat_map(&:lazy) +queues = + %w[default low ultra_low critical].map { |name| Sidekiq::Queue.new(name) }.lazy.flat_map(&:lazy) stats = Hash.new(0) -queues.each do |j| - stats[j.klass] += 1 -end +queues.each { |j| stats[j.klass] += 1 } -stats.sort_by { |a, b| -b }.each do |name, count| - puts "#{name}: #{count}" -end +stats.sort_by { |a, b| -b }.each { |name, count| puts "#{name}: #{count}" } dupes = Hash.new([]) queues.each do |j| diff --git a/script/bench.rb b/script/bench.rb index e114cdd2cb..6e10dcd112 100644 --- a/script/bench.rb +++ b/script/bench.rb @@ -19,46 +19,43 @@ require "uri" @skip_asset_bundle = false @unicorn_workers = 3 -opts = OptionParser.new do |o| - o.banner = "Usage: ruby bench.rb [options]" +opts = + OptionParser.new do |o| + o.banner = "Usage: ruby bench.rb [options]" - o.on("-n", "--with_default_env", "Include recommended Discourse env") do - @include_env = true - end - o.on("-o", "--output [FILE]", "Output results to this file") do |f| - @result_file = f - end - o.on("-i", "--iterations [ITERATIONS]", "Number of iterations to run the bench for") do |i| - @iterations = i.to_i - end - o.on("-b", "--best_of [NUM]", "Number of times to run the bench taking best as result") do |i| - @best_of = i.to_i - end - o.on("-d", "--heap_dump") do - @dump_heap = true - # We need an env var for config/boot.rb to enable allocation tracing prior to framework init - ENV['DISCOURSE_DUMP_HEAP'] = "1" - end - o.on("-m", "--memory_stats") do - @mem_stats = true - end - o.on("-u", "--unicorn", "Use unicorn to serve pages as opposed to puma") do - @unicorn = true - end - o.on("-c", "--concurrency [NUM]", "Run benchmark with this number of concurrent requests (default: 1)") do |i| - @concurrency = i.to_i - end - o.on("-w", "--unicorn_workers [NUM]", "Run benchmark with this number of unicorn workers (default: 3)") do |i| - @unicorn_workers = i.to_i - end - o.on("-s", "--skip-bundle-assets", "Skip bundling assets") do - @skip_asset_bundle = true - end + o.on("-n", "--with_default_env", "Include recommended Discourse env") { @include_env = true } + o.on("-o", "--output [FILE]", "Output results to this file") { |f| @result_file = f } + o.on("-i", "--iterations [ITERATIONS]", "Number of iterations to run the bench for") do |i| + @iterations = i.to_i + end + o.on("-b", "--best_of [NUM]", "Number of times to run the bench taking best as result") do |i| + @best_of = i.to_i + end + o.on("-d", "--heap_dump") do + @dump_heap = true + # We need an env var for config/boot.rb to enable allocation tracing prior to framework init + ENV["DISCOURSE_DUMP_HEAP"] = "1" + end + o.on("-m", "--memory_stats") { @mem_stats = true } + o.on("-u", "--unicorn", "Use unicorn to serve pages as opposed to puma") { @unicorn = true } + o.on( + "-c", + "--concurrency [NUM]", + "Run benchmark with this number of concurrent requests (default: 1)", + ) { |i| @concurrency = i.to_i } + o.on( + "-w", + "--unicorn_workers [NUM]", + "Run benchmark with this number of unicorn workers (default: 3)", + ) { |i| @unicorn_workers = i.to_i } + o.on("-s", "--skip-bundle-assets", "Skip bundling assets") { @skip_asset_bundle = true } - o.on("-t", "--tests [STRING]", "List of tests to run. Example: '--tests topic,categories')") do |i| - @tests = i.split(",") + o.on( + "-t", + "--tests [STRING]", + "List of tests to run. Example: '--tests topic,categories')", + ) { |i| @tests = i.split(",") } end -end opts.parse! def run(command, opt = nil) @@ -73,7 +70,7 @@ def run(command, opt = nil) end begin - require 'facter' + require "facter" raise LoadError if Gem::Version.new(Facter.version) < Gem::Version.new("4.0") rescue LoadError run "gem install facter" @@ -113,7 +110,7 @@ end puts "Ensuring config is setup" -%x{which ab > /dev/null 2>&1} +`which ab > /dev/null 2>&1` unless $? == 0 abort "Apache Bench is not installed. Try: apt-get install apache2-utils or brew install ab" end @@ -125,7 +122,7 @@ end ENV["RAILS_ENV"] = "profile" -discourse_env_vars = %w( +discourse_env_vars = %w[ DISCOURSE_DUMP_HEAP RUBY_GC_HEAP_INIT_SLOTS RUBY_GC_HEAP_FREE_SLOTS @@ -140,27 +137,22 @@ discourse_env_vars = %w( RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR RUBY_GLOBAL_METHOD_CACHE_SIZE LD_PRELOAD -) +] if @include_env puts "Running with tuned environment" - discourse_env_vars.each do |v| - ENV.delete v - end - - ENV['RUBY_GLOBAL_METHOD_CACHE_SIZE'] = '131072' - ENV['RUBY_GC_HEAP_GROWTH_MAX_SLOTS'] = '40000' - ENV['RUBY_GC_HEAP_INIT_SLOTS'] = '400000' - ENV['RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR'] = '1.5' + discourse_env_vars.each { |v| ENV.delete v } + ENV["RUBY_GLOBAL_METHOD_CACHE_SIZE"] = "131072" + ENV["RUBY_GC_HEAP_GROWTH_MAX_SLOTS"] = "40000" + ENV["RUBY_GC_HEAP_INIT_SLOTS"] = "400000" + ENV["RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR"] = "1.5" else # clean env puts "Running with the following custom environment" end -discourse_env_vars.each do |w| - puts "#{w}: #{ENV[w]}" if ENV[w].to_s.length > 0 -end +discourse_env_vars.each { |w| puts "#{w}: #{ENV[w]}" if ENV[w].to_s.length > 0 } def port_available?(port) server = TCPServer.open("0.0.0.0", port) @@ -170,20 +162,16 @@ rescue Errno::EADDRINUSE false end -@port = 60079 +@port = 60_079 -while !port_available? @port - @port += 1 -end +@port += 1 while !port_available? @port puts "Ensuring profiling DB exists and is migrated" puts `bundle exec rake db:create` `bundle exec rake db:migrate` puts "Timing loading Rails" -measure("load_rails") do - `bundle exec rake middleware` -end +measure("load_rails") { `bundle exec rake middleware` } puts "Populating Profile DB" run("bundle exec ruby script/profile_db_generator.rb") @@ -223,16 +211,21 @@ begin pid = if @unicorn - ENV['UNICORN_PORT'] = @port.to_s - ENV['UNICORN_WORKERS'] = @unicorn_workers.to_s - FileUtils.mkdir_p(File.join('tmp', 'pids')) + ENV["UNICORN_PORT"] = @port.to_s + ENV["UNICORN_WORKERS"] = @unicorn_workers.to_s + FileUtils.mkdir_p(File.join("tmp", "pids")) unicorn_pid = spawn("bundle exec unicorn -c config/unicorn.conf.rb") - while (unicorn_master_pid = `ps aux | grep "unicorn master" | grep -v "grep" | awk '{print $2}'`.strip.to_i) == 0 + while ( + unicorn_master_pid = + `ps aux | grep "unicorn master" | grep -v "grep" | awk '{print $2}'`.strip.to_i + ) == 0 sleep 1 end - while `ps -f --ppid #{unicorn_master_pid} | grep worker | awk '{ print $2 }'`.split("\n").map(&:to_i).size != @unicorn_workers.to_i + while `ps -f --ppid #{unicorn_master_pid} | grep worker | awk '{ print $2 }'`.split("\n") + .map(&:to_i) + .size != @unicorn_workers.to_i sleep 1 end @@ -241,48 +234,38 @@ begin spawn("bundle exec puma -p #{@port} -e production") end - while port_available? @port - sleep 1 - end + sleep 1 while port_available? @port puts "Starting benchmark..." - admin_headers = { - 'Api-Key' => admin_api_key, - 'Api-Username' => "admin1" - } + admin_headers = { "Api-Key" => admin_api_key, "Api-Username" => "admin1" } - user_headers = { - 'User-Api-Key' => user_api_key - } + user_headers = { "User-Api-Key" => user_api_key } # asset precompilation is a dog, wget to force it run "curl -s -o /dev/null http://127.0.0.1:#{@port}/" redirect_response = `curl -s -I "http://127.0.0.1:#{@port}/t/i-am-a-topic-used-for-perf-tests"` - if redirect_response !~ /301 Moved Permanently/ - raise "Unable to locate topic for perf tests" - end + raise "Unable to locate topic for perf tests" if redirect_response !~ /301 Moved Permanently/ - topic_url = redirect_response.match(/^location: .+(\/t\/i-am-a-topic-used-for-perf-tests\/.+)$/i)[1].strip + topic_url = + redirect_response.match(%r{^location: .+(/t/i-am-a-topic-used-for-perf-tests/.+)$}i)[1].strip all_tests = [ - ["categories", "/categories"], - ["home", "/"], + %w[categories /categories], + %w[home /], ["topic", topic_url], ["topic.json", "#{topic_url}.json"], ["user activity", "/u/admin1/activity"], ] - @tests ||= %w{categories home topic} + @tests ||= %w[categories home topic] - tests_to_run = all_tests.select do |test_name, path| - @tests.include?(test_name) - end + tests_to_run = all_tests.select { |test_name, path| @tests.include?(test_name) } tests_to_run.concat( tests_to_run.map { |k, url| ["#{k} user", "#{url}", user_headers] }, - tests_to_run.map { |k, url| ["#{k} admin", "#{url}", admin_headers] } + tests_to_run.map { |k, url| ["#{k} admin", "#{url}", admin_headers] }, ) tests_to_run.each do |test_name, path, headers_for_path| @@ -290,15 +273,11 @@ begin http = Net::HTTP.new(uri.host, uri.port) request = Net::HTTP::Get.new(uri.request_uri) - headers_for_path&.each do |key, value| - request[key] = value - end + headers_for_path&.each { |key, value| request[key] = value } response = http.request(request) - if response.code != "200" - raise "#{test_name} #{path} returned non 200 response code" - end + raise "#{test_name} #{path} returned non 200 response code" if response.code != "200" end # NOTE: we run the most expensive page first in the bench @@ -335,11 +314,17 @@ begin Facter.reset facts = Facter.to_hash - facts.delete_if { |k, v| - !["operatingsystem", "architecture", "kernelversion", - "memorysize", "physicalprocessorcount", "processor0", - "virtual"].include?(k) - } + facts.delete_if do |k, v| + !%w[ + operatingsystem + architecture + kernelversion + memorysize + physicalprocessorcount + processor0 + virtual + ].include?(k) + end run("RAILS_ENV=profile bundle exec rake assets:clean") @@ -349,10 +334,13 @@ begin mem = get_mem(pid) - results = results.merge("timings" => @timings, - "ruby-version" => "#{RUBY_DESCRIPTION}", - "rss_kb" => mem["rss_kb"], - "pss_kb" => mem["pss_kb"]).merge(facts) + results = + results.merge( + "timings" => @timings, + "ruby-version" => "#{RUBY_DESCRIPTION}", + "rss_kb" => mem["rss_kb"], + "pss_kb" => mem["pss_kb"], + ).merge(facts) if @unicorn child_pids = `ps --ppid #{pid} | awk '{ print $1; }' | grep -v PID`.split("\n") @@ -375,12 +363,7 @@ begin puts open("http://127.0.0.1:#{@port}/admin/dump_heap", headers).read end - if @result_file - File.open(@result_file, "wb") do |f| - f.write(results) - end - end - + File.open(@result_file, "wb") { |f| f.write(results) } if @result_file ensure Process.kill "KILL", pid end diff --git a/script/benchmarks/cache/bench.rb b/script/benchmarks/cache/bench.rb index fad1b73607..87d4a1e4d3 100644 --- a/script/benchmarks/cache/bench.rb +++ b/script/benchmarks/cache/bench.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true -require 'benchmark/ips' -require File.expand_path('../../../../config/environment', __FILE__) +require "benchmark/ips" +require File.expand_path("../../../../config/environment", __FILE__) Benchmark.ips do |x| - x.report("redis setex string") do |times| while times > 0 Discourse.redis.setex("test_key", 60, "test") diff --git a/script/benchmarks/markdown/bench.rb b/script/benchmarks/markdown/bench.rb index 00bd7573d8..5e54d61755 100644 --- a/script/benchmarks/markdown/bench.rb +++ b/script/benchmarks/markdown/bench.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'benchmark/ips' -require File.expand_path('../../../../config/environment', __FILE__) +require "benchmark/ips" +require File.expand_path("../../../../config/environment", __FILE__) # set any flags here # MiniRacer::Platform.set_flags! :noturbo @@ -10,7 +10,7 @@ tests = [ ["tiny post", "**hello**"], ["giant post", File.read("giant_post.md")], ["most features", File.read("most_features.md")], - ["lots of mentions", File.read("lots_of_mentions.md")] + ["lots of mentions", File.read("lots_of_mentions.md")], ] PrettyText.cook("") @@ -31,9 +31,7 @@ PrettyText.v8.eval("window.commonmark = window.markdownit('commonmark')") Benchmark.ips do |x| [true, false].each do |sanitize| tests.each do |test, text| - x.report("#{test} sanitize: #{sanitize}") do - PrettyText.markdown(text, sanitize: sanitize) - end + x.report("#{test} sanitize: #{sanitize}") { PrettyText.markdown(text, sanitize: sanitize) } end end diff --git a/script/benchmarks/middleware/test.rb b/script/benchmarks/middleware/test.rb index 1432b9227e..8b0cca5f6e 100644 --- a/script/benchmarks/middleware/test.rb +++ b/script/benchmarks/middleware/test.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'memory_profiler' -require 'benchmark/ips' +require "memory_profiler" +require "benchmark/ips" ENV["RAILS_ENV"] = "production" @@ -14,12 +14,10 @@ def req "timings[1]" => "1001", "timings[2]" => "1001", "timings[3]" => "1001", - "topic_id" => "490310" + "topic_id" => "490310", } - data = data.map do |k, v| - "#{CGI.escape(k)}=#{v}" - end.join("&") + data = data.map { |k, v| "#{CGI.escape(k)}=#{v}" }.join("&") { "REQUEST_METHOD" => "POST", @@ -33,7 +31,7 @@ def req "HTTP_COOKIE" => "_t=#{_t}", "rack.input" => StringIO.new(data), "rack.version" => [1, 2], - "rack.url_scheme" => "http" + "rack.url_scheme" => "http", } end @@ -45,11 +43,7 @@ end exit # # -StackProf.run(mode: :wall, out: 'report.dump') do - 1000.times do - Rails.application.call(req) - end -end +StackProf.run(mode: :wall, out: "report.dump") { 1000.times { Rails.application.call(req) } } # # MemoryProfiler.start # Rails.application.call(req) diff --git a/script/benchmarks/site_setting/bench.rb b/script/benchmarks/site_setting/bench.rb index 5790b41ffa..02676e8570 100644 --- a/script/benchmarks/site_setting/bench.rb +++ b/script/benchmarks/site_setting/bench.rb @@ -1,37 +1,32 @@ # frozen_string_literal: true -require 'benchmark/ips' -require File.expand_path('../../../../config/environment', __FILE__) +require "benchmark/ips" +require File.expand_path("../../../../config/environment", __FILE__) # Put pre conditions here # Used db but it's OK in the most cases # build the cache SiteSetting.title = SecureRandom.hex -SiteSetting.default_locale = SiteSetting.default_locale == 'en' ? 'zh_CN' : 'en' +SiteSetting.default_locale = SiteSetting.default_locale == "en" ? "zh_CN" : "en" SiteSetting.refresh! tests = [ - ["current cache", lambda do - SiteSetting.title - SiteSetting.enable_discourse_connect - end + [ + "current cache", + lambda do + SiteSetting.title + SiteSetting.enable_discourse_connect + end, ], - ["change default locale with current cache refreshed", lambda do - SiteSetting.default_locale = SiteSetting.default_locale == 'en' ? 'zh_CN' : 'en' - end - ], - ["change site setting", lambda do - SiteSetting.title = SecureRandom.hex - end + [ + "change default locale with current cache refreshed", + lambda { SiteSetting.default_locale = SiteSetting.default_locale == "en" ? "zh_CN" : "en" }, ], + ["change site setting", lambda { SiteSetting.title = SecureRandom.hex }], ] -Benchmark.ips do |x| - tests.each do |test, proc| - x.report(test, proc) - end -end +Benchmark.ips { |x| tests.each { |test, proc| x.report(test, proc) } } # 2017-08-02 - Erick's Site Setting change diff --git a/script/benchmarks/site_setting/profile.rb b/script/benchmarks/site_setting/profile.rb index a849a18a36..fea7977b86 100644 --- a/script/benchmarks/site_setting/profile.rb +++ b/script/benchmarks/site_setting/profile.rb @@ -1,34 +1,26 @@ # frozen_string_literal: true -require 'ruby-prof' +require "ruby-prof" def profile(&blk) result = RubyProf.profile(&blk) printer = RubyProf::GraphHtmlPrinter.new(result) printer.print(STDOUT) end -profile { '' } # loading profiler dependency +profile { "" } # loading profiler dependency -require File.expand_path('../../../../config/environment', __FILE__) +require File.expand_path("../../../../config/environment", __FILE__) # warming up SiteSetting.title SiteSetting.enable_discourse_connect -SiteSetting.default_locale = SiteSetting.default_locale == 'en' ? 'zh_CN' : 'en' +SiteSetting.default_locale = SiteSetting.default_locale == "en" ? "zh_CN" : "en" SiteSetting.title = SecureRandom.hex -profile do - SiteSetting.title -end +profile { SiteSetting.title } -profile do - SiteSetting.enable_discourse_connect -end +profile { SiteSetting.enable_discourse_connect } -profile do - SiteSetting.default_locale = SiteSetting.default_locale == 'en' ? 'zh_CN' : 'en' -end +profile { SiteSetting.default_locale = SiteSetting.default_locale == "en" ? "zh_CN" : "en" } -profile do - SiteSetting.title = SecureRandom.hex -end +profile { SiteSetting.title = SecureRandom.hex } diff --git a/script/biggest_objects.rb b/script/biggest_objects.rb index 3f99cf109b..220fff3c00 100644 --- a/script/biggest_objects.rb +++ b/script/biggest_objects.rb @@ -2,35 +2,41 @@ # simple script to measure largest objects in memory post boot -if ENV['RAILS_ENV'] != "production" - exec "RAILS_ENV=production ruby #{__FILE__}" -end +exec "RAILS_ENV=production ruby #{__FILE__}" if ENV["RAILS_ENV"] != "production" -require 'objspace' +require "objspace" ObjectSpace.trace_object_allocations do - require File.expand_path("../../config/environment", __FILE__) - Rails.application.routes.recognize_path('abc') rescue nil + begin + Rails.application.routes.recognize_path("abc") + rescue StandardError + nil + end # load up the yaml for the localization bits, in master process I18n.t(:posts) RailsMultisite::ConnectionManagement.each_connection do (ActiveRecord::Base.connection.tables - %w[schema_migrations versions]).each do |table| - table.classify.constantize.first rescue nil + begin + table.classify.constantize.first + rescue StandardError + nil + end end end - end -5.times do - GC.start(full_mark: true, immediate_sweep: true) -end +5.times { GC.start(full_mark: true, immediate_sweep: true) } [String, Array, Hash].each do |klass| - ObjectSpace.each_object(klass).sort { |a, b| b.length <=> a.length }.first(50).each do |obj| - puts "#{klass} size: #{obj.length} #{ObjectSpace.allocation_sourcefile(obj)} #{ObjectSpace.allocation_sourceline(obj)}" - end + ObjectSpace + .each_object(klass) + .sort { |a, b| b.length <=> a.length } + .first(50) + .each do |obj| + puts "#{klass} size: #{obj.length} #{ObjectSpace.allocation_sourcefile(obj)} #{ObjectSpace.allocation_sourceline(obj)}" + end end diff --git a/script/boot_mem.rb b/script/boot_mem.rb index 5780ae6058..44fb2b8407 100644 --- a/script/boot_mem.rb +++ b/script/boot_mem.rb @@ -2,22 +2,30 @@ # simple script to measure memory at boot -if ENV['RAILS_ENV'] != "production" - exec "RAILS_ENV=production ruby #{__FILE__}" -end +exec "RAILS_ENV=production ruby #{__FILE__}" if ENV["RAILS_ENV"] != "production" -require 'memory_profiler' +require "memory_profiler" -MemoryProfiler.report do - require File.expand_path("../../config/environment", __FILE__) +MemoryProfiler + .report do + require File.expand_path("../../config/environment", __FILE__) - Rails.application.routes.recognize_path('abc') rescue nil + begin + Rails.application.routes.recognize_path("abc") + rescue StandardError + nil + end - # load up the yaml for the localization bits, in master process - I18n.t(:posts) + # load up the yaml for the localization bits, in master process + I18n.t(:posts) - # load up all models and schema - (ActiveRecord::Base.connection.tables - %w[schema_migrations versions]).each do |table| - table.classify.constantize.first rescue nil + # load up all models and schema + (ActiveRecord::Base.connection.tables - %w[schema_migrations versions]).each do |table| + begin + table.classify.constantize.first + rescue StandardError + nil + end + end end -end.pretty_print + .pretty_print diff --git a/script/bulk_import/base.rb b/script/bulk_import/base.rb index 767469a83b..d92878b705 100644 --- a/script/bulk_import/base.rb +++ b/script/bulk_import/base.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -if ARGV.include?('bbcode-to-md') +if ARGV.include?("bbcode-to-md") # Replace (most) bbcode with markdown before creating posts. # This will dramatically clean up the final posts in Discourse. # @@ -10,7 +10,7 @@ if ARGV.include?('bbcode-to-md') # cd ruby-bbcode-to-md # gem build ruby-bbcode-to-md.gemspec # gem install ruby-bbcode-to-md-*.gem - require 'ruby-bbcode-to-md' + require "ruby-bbcode-to-md" end require "pg" @@ -20,12 +20,12 @@ require "htmlentities" puts "Loading application..." require_relative "../../config/environment" -require_relative '../import_scripts/base/uploader' +require_relative "../import_scripts/base/uploader" -module BulkImport; end +module BulkImport +end class BulkImport::Base - NOW ||= "now()" PRIVATE_OFFSET ||= 2**30 @@ -33,41 +33,41 @@ class BulkImport::Base CHARSET_MAP = { "armscii8" => nil, - "ascii" => Encoding::US_ASCII, - "big5" => Encoding::Big5, - "binary" => Encoding::ASCII_8BIT, - "cp1250" => Encoding::Windows_1250, - "cp1251" => Encoding::Windows_1251, - "cp1256" => Encoding::Windows_1256, - "cp1257" => Encoding::Windows_1257, - "cp850" => Encoding::CP850, - "cp852" => Encoding::CP852, - "cp866" => Encoding::IBM866, - "cp932" => Encoding::Windows_31J, - "dec8" => nil, - "eucjpms" => Encoding::EucJP_ms, - "euckr" => Encoding::EUC_KR, - "gb2312" => Encoding::EUC_CN, - "gbk" => Encoding::GBK, - "geostd8" => nil, - "greek" => Encoding::ISO_8859_7, - "hebrew" => Encoding::ISO_8859_8, - "hp8" => nil, - "keybcs2" => nil, - "koi8r" => Encoding::KOI8_R, - "koi8u" => Encoding::KOI8_U, - "latin1" => Encoding::ISO_8859_1, - "latin2" => Encoding::ISO_8859_2, - "latin5" => Encoding::ISO_8859_9, - "latin7" => Encoding::ISO_8859_13, - "macce" => Encoding::MacCentEuro, + "ascii" => Encoding::US_ASCII, + "big5" => Encoding::Big5, + "binary" => Encoding::ASCII_8BIT, + "cp1250" => Encoding::Windows_1250, + "cp1251" => Encoding::Windows_1251, + "cp1256" => Encoding::Windows_1256, + "cp1257" => Encoding::Windows_1257, + "cp850" => Encoding::CP850, + "cp852" => Encoding::CP852, + "cp866" => Encoding::IBM866, + "cp932" => Encoding::Windows_31J, + "dec8" => nil, + "eucjpms" => Encoding::EucJP_ms, + "euckr" => Encoding::EUC_KR, + "gb2312" => Encoding::EUC_CN, + "gbk" => Encoding::GBK, + "geostd8" => nil, + "greek" => Encoding::ISO_8859_7, + "hebrew" => Encoding::ISO_8859_8, + "hp8" => nil, + "keybcs2" => nil, + "koi8r" => Encoding::KOI8_R, + "koi8u" => Encoding::KOI8_U, + "latin1" => Encoding::ISO_8859_1, + "latin2" => Encoding::ISO_8859_2, + "latin5" => Encoding::ISO_8859_9, + "latin7" => Encoding::ISO_8859_13, + "macce" => Encoding::MacCentEuro, "macroman" => Encoding::MacRoman, - "sjis" => Encoding::SHIFT_JIS, - "swe7" => nil, - "tis620" => Encoding::TIS_620, - "ucs2" => Encoding::UTF_16BE, - "ujis" => Encoding::EucJP_ms, - "utf8" => Encoding::UTF_8, + "sjis" => Encoding::SHIFT_JIS, + "swe7" => nil, + "tis620" => Encoding::TIS_620, + "ucs2" => Encoding::UTF_16BE, + "ujis" => Encoding::EucJP_ms, + "utf8" => Encoding::UTF_8, } # rubocop:enable Layout/HashAlignment @@ -82,12 +82,13 @@ class BulkImport::Base @encoding = CHARSET_MAP[charset] @bbcode_to_md = true if use_bbcode_to_md? - @markdown = Redcarpet::Markdown.new( - Redcarpet::Render::HTML.new(hard_wrap: true), - no_intra_emphasis: true, - fenced_code_blocks: true, - autolink: true - ) + @markdown = + Redcarpet::Markdown.new( + Redcarpet::Render::HTML.new(hard_wrap: true), + no_intra_emphasis: true, + fenced_code_blocks: true, + autolink: true, + ) end def run @@ -132,7 +133,9 @@ class BulkImport::Base map = [] ids = [] - @raw_connection.send_query("SELECT value, #{name}_id FROM #{name}_custom_fields WHERE name = 'import_id'") + @raw_connection.send_query( + "SELECT value, #{name}_id FROM #{name}_custom_fields WHERE name = 'import_id'", + ) @raw_connection.set_single_row_mode @raw_connection.get_result.stream_each do |row| @@ -163,12 +166,14 @@ class BulkImport::Base puts "Loading imported topic ids..." @topics, imported_topic_ids = imported_ids("topic") @last_imported_topic_id = imported_topic_ids.select { |id| id < PRIVATE_OFFSET }.max || -1 - @last_imported_private_topic_id = imported_topic_ids.select { |id| id > PRIVATE_OFFSET }.max || (PRIVATE_OFFSET - 1) + @last_imported_private_topic_id = + imported_topic_ids.select { |id| id > PRIVATE_OFFSET }.max || (PRIVATE_OFFSET - 1) puts "Loading imported post ids..." @posts, imported_post_ids = imported_ids("post") @last_imported_post_id = imported_post_ids.select { |id| id < PRIVATE_OFFSET }.max || -1 - @last_imported_private_post_id = imported_post_ids.select { |id| id > PRIVATE_OFFSET }.max || (PRIVATE_OFFSET - 1) + @last_imported_private_post_id = + imported_post_ids.select { |id| id > PRIVATE_OFFSET }.max || (PRIVATE_OFFSET - 1) end def last_id(klass) @@ -182,9 +187,7 @@ class BulkImport::Base @raw_connection.send_query("SELECT id, #{column} FROM #{name}") @raw_connection.set_single_row_mode - @raw_connection.get_result.stream_each do |row| - map[row["id"].to_i] = row[column].to_i - end + @raw_connection.get_result.stream_each { |row| map[row["id"].to_i] = row[column].to_i } @raw_connection.get_result @@ -199,13 +202,24 @@ class BulkImport::Base puts "Loading users indexes..." @last_user_id = last_id(User) @last_user_email_id = last_id(UserEmail) - @emails = User.unscoped.joins(:user_emails).pluck(:"user_emails.email", :"user_emails.user_id").to_h + @emails = + User.unscoped.joins(:user_emails).pluck(:"user_emails.email", :"user_emails.user_id").to_h @usernames_lower = User.unscoped.pluck(:username_lower).to_set - @mapped_usernames = UserCustomField.joins(:user).where(name: "import_username").pluck("user_custom_fields.value", "users.username").to_h + @mapped_usernames = + UserCustomField + .joins(:user) + .where(name: "import_username") + .pluck("user_custom_fields.value", "users.username") + .to_h puts "Loading categories indexes..." @last_category_id = last_id(Category) - @category_names = Category.unscoped.pluck(:parent_category_id, :name).map { |pci, name| "#{pci}-#{name}" }.to_set + @category_names = + Category + .unscoped + .pluck(:parent_category_id, :name) + .map { |pci, name| "#{pci}-#{name}" } + .to_set puts "Loading topics indexes..." @last_topic_id = last_id(Topic) @@ -233,13 +247,27 @@ class BulkImport::Base def fix_primary_keys puts "Updating primary key sequences..." - @raw_connection.exec("SELECT setval('#{Group.sequence_name}', #{@last_group_id})") if @last_group_id > 0 - @raw_connection.exec("SELECT setval('#{User.sequence_name}', #{@last_user_id})") if @last_user_id > 0 - @raw_connection.exec("SELECT setval('#{UserEmail.sequence_name}', #{@last_user_email_id})") if @last_user_email_id > 0 - @raw_connection.exec("SELECT setval('#{Category.sequence_name}', #{@last_category_id})") if @last_category_id > 0 - @raw_connection.exec("SELECT setval('#{Topic.sequence_name}', #{@last_topic_id})") if @last_topic_id > 0 - @raw_connection.exec("SELECT setval('#{Post.sequence_name}', #{@last_post_id})") if @last_post_id > 0 - @raw_connection.exec("SELECT setval('#{PostAction.sequence_name}', #{@last_post_action_id})") if @last_post_action_id > 0 + if @last_group_id > 0 + @raw_connection.exec("SELECT setval('#{Group.sequence_name}', #{@last_group_id})") + end + if @last_user_id > 0 + @raw_connection.exec("SELECT setval('#{User.sequence_name}', #{@last_user_id})") + end + if @last_user_email_id > 0 + @raw_connection.exec("SELECT setval('#{UserEmail.sequence_name}', #{@last_user_email_id})") + end + if @last_category_id > 0 + @raw_connection.exec("SELECT setval('#{Category.sequence_name}', #{@last_category_id})") + end + if @last_topic_id > 0 + @raw_connection.exec("SELECT setval('#{Topic.sequence_name}', #{@last_topic_id})") + end + if @last_post_id > 0 + @raw_connection.exec("SELECT setval('#{Post.sequence_name}', #{@last_post_id})") + end + if @last_post_action_id > 0 + @raw_connection.exec("SELECT setval('#{PostAction.sequence_name}', #{@last_post_action_id})") + end end def group_id_from_imported_id(id) @@ -272,63 +300,124 @@ class BulkImport::Base post_id && @topic_id_by_post_id[post_id] end - GROUP_COLUMNS ||= %i{ - id name title bio_raw bio_cooked created_at updated_at - } + GROUP_COLUMNS ||= %i[id name title bio_raw bio_cooked created_at updated_at] - USER_COLUMNS ||= %i{ - id username username_lower name active trust_level admin moderator - date_of_birth ip_address registration_ip_address primary_group_id - suspended_at suspended_till last_emailed_at created_at updated_at - } + USER_COLUMNS ||= %i[ + id + username + username_lower + name + active + trust_level + admin + moderator + date_of_birth + ip_address + registration_ip_address + primary_group_id + suspended_at + suspended_till + last_emailed_at + created_at + updated_at + ] - USER_EMAIL_COLUMNS ||= %i{ - id user_id email primary created_at updated_at - } + USER_EMAIL_COLUMNS ||= %i[id user_id email primary created_at updated_at] - USER_STAT_COLUMNS ||= %i{ - user_id topics_entered time_read days_visited posts_read_count - likes_given likes_received new_since read_faq - first_post_created_at post_count topic_count bounce_score - reset_bounce_score_after digest_attempted_at - } + USER_STAT_COLUMNS ||= %i[ + user_id + topics_entered + time_read + days_visited + posts_read_count + likes_given + likes_received + new_since + read_faq + first_post_created_at + post_count + topic_count + bounce_score + reset_bounce_score_after + digest_attempted_at + ] - USER_PROFILE_COLUMNS ||= %i{ - user_id location website bio_raw bio_cooked views - } + USER_PROFILE_COLUMNS ||= %i[user_id location website bio_raw bio_cooked views] - GROUP_USER_COLUMNS ||= %i{ - group_id user_id created_at updated_at - } + GROUP_USER_COLUMNS ||= %i[group_id user_id created_at updated_at] - CATEGORY_COLUMNS ||= %i{ - id name name_lower slug user_id description position parent_category_id - created_at updated_at - } + CATEGORY_COLUMNS ||= %i[ + id + name + name_lower + slug + user_id + description + position + parent_category_id + created_at + updated_at + ] - TOPIC_COLUMNS ||= %i{ - id archetype title fancy_title slug user_id last_post_user_id category_id - visible closed pinned_at views created_at bumped_at updated_at - } + TOPIC_COLUMNS ||= %i[ + id + archetype + title + fancy_title + slug + user_id + last_post_user_id + category_id + visible + closed + pinned_at + views + created_at + bumped_at + updated_at + ] - POST_COLUMNS ||= %i{ - id user_id last_editor_id topic_id post_number sort_order reply_to_post_number - like_count raw cooked hidden word_count created_at last_version_at updated_at - } + POST_COLUMNS ||= %i[ + id + user_id + last_editor_id + topic_id + post_number + sort_order + reply_to_post_number + like_count + raw + cooked + hidden + word_count + created_at + last_version_at + updated_at + ] - POST_ACTION_COLUMNS ||= %i{ - id post_id user_id post_action_type_id deleted_at created_at updated_at - deleted_by_id related_post_id staff_took_action deferred_by_id targets_topic - agreed_at agreed_by_id deferred_at disagreed_at disagreed_by_id - } + POST_ACTION_COLUMNS ||= %i[ + id + post_id + user_id + post_action_type_id + deleted_at + created_at + updated_at + deleted_by_id + related_post_id + staff_took_action + deferred_by_id + targets_topic + agreed_at + agreed_by_id + deferred_at + disagreed_at + disagreed_by_id + ] - TOPIC_ALLOWED_USER_COLUMNS ||= %i{ - topic_id user_id created_at updated_at - } + TOPIC_ALLOWED_USER_COLUMNS ||= %i[topic_id user_id created_at updated_at] - TOPIC_TAG_COLUMNS ||= %i{ - topic_id tag_id created_at updated_at - } + TOPIC_TAG_COLUMNS ||= %i[topic_id tag_id created_at updated_at] def create_groups(rows, &block) create_records(rows, "group", GROUP_COLUMNS, &block) @@ -340,10 +429,7 @@ class BulkImport::Base create_records(rows, "user", USER_COLUMNS, &block) create_custom_fields("user", "username", @imported_usernames.keys) do |username| - { - record_id: @imported_usernames[username], - value: username, - } + { record_id: @imported_usernames[username], value: username } end end @@ -389,8 +475,8 @@ class BulkImport::Base group[:name] = group_name end - group[:title] = group[:title].scrub.strip.presence if group[:title].present? - group[:bio_raw] = group[:bio_raw].scrub.strip.presence if group[:bio_raw].present? + group[:title] = group[:title].scrub.strip.presence if group[:title].present? + group[:bio_raw] = group[:bio_raw].scrub.strip.presence if group[:bio_raw].present? group[:bio_cooked] = pre_cook(group[:bio_raw]) if group[:bio_raw].present? group[:created_at] ||= NOW group[:updated_at] ||= group[:created_at] @@ -456,7 +542,9 @@ class BulkImport::Base user_email[:email] ||= random_email user_email[:email].downcase! # unique email - user_email[:email] = random_email until EmailAddressValidator.valid_value?(user_email[:email]) && !@emails.has_key?(user_email[:email]) + user_email[:email] = random_email until EmailAddressValidator.valid_value?( + user_email[:email], + ) && !@emails.has_key?(user_email[:email]) user_email end @@ -539,7 +627,11 @@ class BulkImport::Base post[:raw] = (post[:raw] || "").scrub.strip.presence || "" post[:raw] = process_raw post[:raw] if @bbcode_to_md - post[:raw] = post[:raw].bbcode_to_md(false, {}, :disable, :quote) rescue post[:raw] + post[:raw] = begin + post[:raw].bbcode_to_md(false, {}, :disable, :quote) + rescue StandardError + post[:raw] + end end post[:like_count] ||= 0 post[:cooked] = pre_cook post[:raw] @@ -580,22 +672,22 @@ class BulkImport::Base # [HTML]...[/HTML] raw.gsub!(/\[HTML\]/i, "\n\n```html\n") - raw.gsub!(/\[\/HTML\]/i, "\n```\n\n") + raw.gsub!(%r{\[/HTML\]}i, "\n```\n\n") # [PHP]...[/PHP] raw.gsub!(/\[PHP\]/i, "\n\n```php\n") - raw.gsub!(/\[\/PHP\]/i, "\n```\n\n") + raw.gsub!(%r{\[/PHP\]}i, "\n```\n\n") # [HIGHLIGHT="..."] raw.gsub!(/\[HIGHLIGHT="?(\w+)"?\]/i) { "\n\n```#{$1.downcase}\n" } # [CODE]...[/CODE] # [HIGHLIGHT]...[/HIGHLIGHT] - raw.gsub!(/\[\/?CODE\]/i, "\n\n```\n\n") - raw.gsub!(/\[\/?HIGHLIGHT\]/i, "\n\n```\n\n") + raw.gsub!(%r{\[/?CODE\]}i, "\n\n```\n\n") + raw.gsub!(%r{\[/?HIGHLIGHT\]}i, "\n\n```\n\n") # [SAMP]...[/SAMP] - raw.gsub!(/\[\/?SAMP\]/i, "`") + raw.gsub!(%r{\[/?SAMP\]}i, "`") # replace all chevrons with HTML entities # /!\ must be done /!\ @@ -609,61 +701,61 @@ class BulkImport::Base raw.gsub!(">", ">") raw.gsub!("\u2603", ">") - raw.gsub!(/\[\/?I\]/i, "*") - raw.gsub!(/\[\/?B\]/i, "**") - raw.gsub!(/\[\/?U\]/i, "") + raw.gsub!(%r{\[/?I\]}i, "*") + raw.gsub!(%r{\[/?B\]}i, "**") + raw.gsub!(%r{\[/?U\]}i, "") - raw.gsub!(/\[\/?RED\]/i, "") - raw.gsub!(/\[\/?BLUE\]/i, "") + raw.gsub!(%r{\[/?RED\]}i, "") + raw.gsub!(%r{\[/?BLUE\]}i, "") - raw.gsub!(/\[AUTEUR\].+?\[\/AUTEUR\]/im, "") - raw.gsub!(/\[VOIRMSG\].+?\[\/VOIRMSG\]/im, "") - raw.gsub!(/\[PSEUDOID\].+?\[\/PSEUDOID\]/im, "") + raw.gsub!(%r{\[AUTEUR\].+?\[/AUTEUR\]}im, "") + raw.gsub!(%r{\[VOIRMSG\].+?\[/VOIRMSG\]}im, "") + raw.gsub!(%r{\[PSEUDOID\].+?\[/PSEUDOID\]}im, "") # [IMG]...[/IMG] - raw.gsub!(/(?:\s*\[IMG\]\s*)+(.+?)(?:\s*\[\/IMG\]\s*)+/im) { "\n\n#{$1}\n\n" } + raw.gsub!(%r{(?:\s*\[IMG\]\s*)+(.+?)(?:\s*\[/IMG\]\s*)+}im) { "\n\n#{$1}\n\n" } # [IMG=url] raw.gsub!(/\[IMG=([^\]]*)\]/im) { "\n\n#{$1}\n\n" } # [URL=...]...[/URL] - raw.gsub!(/\[URL="?(.+?)"?\](.+?)\[\/URL\]/im) { "[#{$2.strip}](#{$1})" } + raw.gsub!(%r{\[URL="?(.+?)"?\](.+?)\[/URL\]}im) { "[#{$2.strip}](#{$1})" } # [URL]...[/URL] # [MP3]...[/MP3] # [EMAIL]...[/EMAIL] # [LEFT]...[/LEFT] - raw.gsub!(/\[\/?URL\]/i, "") - raw.gsub!(/\[\/?MP3\]/i, "") - raw.gsub!(/\[\/?EMAIL\]/i, "") - raw.gsub!(/\[\/?LEFT\]/i, "") + raw.gsub!(%r{\[/?URL\]}i, "") + raw.gsub!(%r{\[/?MP3\]}i, "") + raw.gsub!(%r{\[/?EMAIL\]}i, "") + raw.gsub!(%r{\[/?LEFT\]}i, "") # [FONT=blah] and [COLOR=blah] - raw.gsub!(/\[FONT=.*?\](.*?)\[\/FONT\]/im, "\\1") - raw.gsub!(/\[COLOR=.*?\](.*?)\[\/COLOR\]/im, "\\1") + raw.gsub!(%r{\[FONT=.*?\](.*?)\[/FONT\]}im, "\\1") + raw.gsub!(%r{\[COLOR=.*?\](.*?)\[/COLOR\]}im, "\\1") - raw.gsub!(/\[SIZE=.*?\](.*?)\[\/SIZE\]/im, "\\1") - raw.gsub!(/\[H=.*?\](.*?)\[\/H\]/im, "\\1") + raw.gsub!(%r{\[SIZE=.*?\](.*?)\[/SIZE\]}im, "\\1") + raw.gsub!(%r{\[H=.*?\](.*?)\[/H\]}im, "\\1") # [CENTER]...[/CENTER] - raw.gsub!(/\[CENTER\](.*?)\[\/CENTER\]/im, "\\1") + raw.gsub!(%r{\[CENTER\](.*?)\[/CENTER\]}im, "\\1") # [INDENT]...[/INDENT] - raw.gsub!(/\[INDENT\](.*?)\[\/INDENT\]/im, "\\1") - raw.gsub!(/\[TABLE\](.*?)\[\/TABLE\]/im, "\\1") - raw.gsub!(/\[TR\](.*?)\[\/TR\]/im, "\\1") - raw.gsub!(/\[TD\](.*?)\[\/TD\]/im, "\\1") - raw.gsub!(/\[TD="?.*?"?\](.*?)\[\/TD\]/im, "\\1") + raw.gsub!(%r{\[INDENT\](.*?)\[/INDENT\]}im, "\\1") + raw.gsub!(%r{\[TABLE\](.*?)\[/TABLE\]}im, "\\1") + raw.gsub!(%r{\[TR\](.*?)\[/TR\]}im, "\\1") + raw.gsub!(%r{\[TD\](.*?)\[/TD\]}im, "\\1") + raw.gsub!(%r{\[TD="?.*?"?\](.*?)\[/TD\]}im, "\\1") # [STRIKE] raw.gsub!(/\[STRIKE\]/i, "") - raw.gsub!(/\[\/STRIKE\]/i, "") + raw.gsub!(%r{\[/STRIKE\]}i, "") # [QUOTE]...[/QUOTE] raw.gsub!(/\[QUOTE="([^\]]+)"\]/i) { "[QUOTE=#{$1}]" } # Nested Quotes - raw.gsub!(/(\[\/?QUOTE.*?\])/mi) { |q| "\n#{q}\n" } + raw.gsub!(%r{(\[/?QUOTE.*?\])}mi) { |q| "\n#{q}\n" } # raw.gsub!(/\[QUOTE\](.+?)\[\/QUOTE\]/im) { |quote| # quote.gsub!(/\[QUOTE\](.+?)\[\/QUOTE\]/im) { "\n#{$1}\n" } @@ -686,28 +778,36 @@ class BulkImport::Base end # [YOUTUBE][/YOUTUBE] - raw.gsub!(/\[YOUTUBE\](.+?)\[\/YOUTUBE\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } - raw.gsub!(/\[DAILYMOTION\](.+?)\[\/DAILYMOTION\]/i) { "\nhttps://www.dailymotion.com/video/#{$1}\n" } + raw.gsub!(%r{\[YOUTUBE\](.+?)\[/YOUTUBE\]}i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } + raw.gsub!(%r{\[DAILYMOTION\](.+?)\[/DAILYMOTION\]}i) do + "\nhttps://www.dailymotion.com/video/#{$1}\n" + end # [VIDEO=youtube;]...[/VIDEO] - raw.gsub!(/\[VIDEO=YOUTUBE;([^\]]+)\].*?\[\/VIDEO\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } - raw.gsub!(/\[VIDEO=DAILYMOTION;([^\]]+)\].*?\[\/VIDEO\]/i) { "\nhttps://www.dailymotion.com/video/#{$1}\n" } + raw.gsub!(%r{\[VIDEO=YOUTUBE;([^\]]+)\].*?\[/VIDEO\]}i) do + "\nhttps://www.youtube.com/watch?v=#{$1}\n" + end + raw.gsub!(%r{\[VIDEO=DAILYMOTION;([^\]]+)\].*?\[/VIDEO\]}i) do + "\nhttps://www.dailymotion.com/video/#{$1}\n" + end # [SPOILER=Some hidden stuff]SPOILER HERE!![/SPOILER] - raw.gsub!(/\[SPOILER="?(.+?)"?\](.+?)\[\/SPOILER\]/im) { "\n#{$1}\n[spoiler]#{$2}[/spoiler]\n" } + raw.gsub!(%r{\[SPOILER="?(.+?)"?\](.+?)\[/SPOILER\]}im) do + "\n#{$1}\n[spoiler]#{$2}[/spoiler]\n" + end # convert list tags to ul and list=1 tags to ol # (basically, we're only missing list=a here...) # (https://meta.discourse.org/t/phpbb-3-importer-old/17397) - raw.gsub!(/\[list\](.*?)\[\/list\]/im, '[ul]\1[/ul]') - raw.gsub!(/\[list=1\|?[^\]]*\](.*?)\[\/list\]/im, '[ol]\1[/ol]') - raw.gsub!(/\[list\](.*?)\[\/list:u\]/im, '[ul]\1[/ul]') - raw.gsub!(/\[list=1\|?[^\]]*\](.*?)\[\/list:o\]/im, '[ol]\1[/ol]') + raw.gsub!(%r{\[list\](.*?)\[/list\]}im, '[ul]\1[/ul]') + raw.gsub!(%r{\[list=1\|?[^\]]*\](.*?)\[/list\]}im, '[ol]\1[/ol]') + raw.gsub!(%r{\[list\](.*?)\[/list:u\]}im, '[ul]\1[/ul]') + raw.gsub!(%r{\[list=1\|?[^\]]*\](.*?)\[/list:o\]}im, '[ol]\1[/ol]') # convert *-tags to li-tags so bbcode-to-md can do its magic on phpBB's lists: - raw.gsub!(/\[\*\]\n/, '') - raw.gsub!(/\[\*\](.*?)\[\/\*:m\]/, '[li]\1[/li]') + raw.gsub!(/\[\*\]\n/, "") + raw.gsub!(%r{\[\*\](.*?)\[/\*:m\]}, '[li]\1[/li]') raw.gsub!(/\[\*\](.*?)\n/, '[li]\1[/li]') - raw.gsub!(/\[\*=1\]/, '') + raw.gsub!(/\[\*=1\]/, "") raw end @@ -728,7 +828,9 @@ class BulkImport::Base imported_ids |= mapped[:imported_ids] unless mapped[:imported_ids].nil? @raw_connection.put_copy_data columns.map { |c| processed[c] } unless processed[:skip] rows_created += 1 - print "\r%7d - %6d/sec" % [rows_created, rows_created.to_f / (Time.now - start)] if rows_created % 100 == 0 + if rows_created % 100 == 0 + print "\r%7d - %6d/sec" % [rows_created, rows_created.to_f / (Time.now - start)] + end rescue => e puts "\n" puts "ERROR: #{e.message}" @@ -737,15 +839,14 @@ class BulkImport::Base end end - print "\r%7d - %6d/sec\n" % [rows_created, rows_created.to_f / (Time.now - start)] if rows_created > 0 + if rows_created > 0 + print "\r%7d - %6d/sec\n" % [rows_created, rows_created.to_f / (Time.now - start)] + end id_mapping_method_name = "#{name}_id_from_imported_id".freeze return unless respond_to?(id_mapping_method_name) create_custom_fields(name, "id", imported_ids) do |imported_id| - { - record_id: send(id_mapping_method_name, imported_id), - value: imported_id, - } + { record_id: send(id_mapping_method_name, imported_id), value: imported_id } end rescue => e # FIXME: errors catched here stop the rest of the COPY @@ -755,7 +856,8 @@ class BulkImport::Base def create_custom_fields(table, name, rows) name = "import_#{name}" - sql = "COPY #{table}_custom_fields (#{table}_id, name, value, created_at, updated_at) FROM STDIN" + sql = + "COPY #{table}_custom_fields (#{table}_id, name, value, created_at, updated_at) FROM STDIN" @raw_connection.copy_data(sql, @encoder) do rows.each do |row| next unless cf = yield(row) @@ -797,7 +899,7 @@ class BulkImport::Base cooked = raw # Convert YouTube URLs to lazyYT DOMs before being transformed into links - cooked.gsub!(/\nhttps\:\/\/www.youtube.com\/watch\?v=(\w+)\n/) do + cooked.gsub!(%r{\nhttps\://www.youtube.com/watch\?v=(\w+)\n}) do video_id = $1 result = <<-HTML
@@ -807,7 +909,7 @@ class BulkImport::Base cooked = @markdown.render(cooked).scrub.strip - cooked.gsub!(/\[QUOTE="?([^,"]+)(?:, post:(\d+), topic:(\d+))?"?\](.+?)\[\/QUOTE\]/im) do + cooked.gsub!(%r{\[QUOTE="?([^,"]+)(?:, post:(\d+), topic:(\d+))?"?\](.+?)\[/QUOTE\]}im) do username, post_id, topic_id, quote = $1, $2, $3, $4 quote = quote.scrub.strip @@ -860,5 +962,4 @@ class BulkImport::Base return text if @encoding == Encoding::UTF_8 text && text.encode(@encoding).force_encoding(Encoding::UTF_8) end - end diff --git a/script/bulk_import/discourse_merger.rb b/script/bulk_import/discourse_merger.rb index dc555850b8..61106e6bf3 100644 --- a/script/bulk_import/discourse_merger.rb +++ b/script/bulk_import/discourse_merger.rb @@ -3,9 +3,8 @@ require_relative "base" class BulkImport::DiscourseMerger < BulkImport::Base - NOW ||= "now()" - CUSTOM_FIELDS = ['category', 'group', 'post', 'topic', 'user'] + CUSTOM_FIELDS = %w[category group post topic user] # DB_NAME: name of database being merged into the current local db # DB_HOST: hostname of database being merged @@ -17,31 +16,36 @@ class BulkImport::DiscourseMerger < BulkImport::Base # e.g. https://discourse-cdn-sjc1.com/business4 def initialize - db_password = ENV["DB_PASS"] || 'import_password' + db_password = ENV["DB_PASS"] || "import_password" local_db = ActiveRecord::Base.connection_db_config.configuration_hash - @raw_connection = PG.connect(dbname: local_db[:database], host: 'localhost', port: local_db[:port], user: 'postgres', password: db_password) + @raw_connection = + PG.connect( + dbname: local_db[:database], + host: "localhost", + port: local_db[:port], + user: "postgres", + password: db_password, + ) @source_db_config = { - dbname: ENV["DB_NAME"] || 'dd_demo', - host: ENV["DB_HOST"] || 'localhost', - user: 'postgres', - password: db_password + dbname: ENV["DB_NAME"] || "dd_demo", + host: ENV["DB_HOST"] || "localhost", + user: "postgres", + password: db_password, } - raise "SOURCE_BASE_URL missing!" unless ENV['SOURCE_BASE_URL'] + raise "SOURCE_BASE_URL missing!" unless ENV["SOURCE_BASE_URL"] @source_base_url = ENV["SOURCE_BASE_URL"] - @uploads_path = ENV['UPLOADS_PATH'] + @uploads_path = ENV["UPLOADS_PATH"] @uploader = ImportScripts::Uploader.new - if ENV['SOURCE_CDN'] - @source_cdn = ENV['SOURCE_CDN'] - end + @source_cdn = ENV["SOURCE_CDN"] if ENV["SOURCE_CDN"] local_version = @raw_connection.exec("select max(version) from schema_migrations") - local_version = local_version.first['max'] + local_version = local_version.first["max"] source_version = source_raw_connection.exec("select max(version) from schema_migrations") - source_version = source_version.first['max'] + source_version = source_version.first["max"] if local_version != source_version raise "DB schema mismatch. Databases must be at the same migration version. Local is #{local_version}, other is #{source_version}" @@ -62,7 +66,7 @@ class BulkImport::DiscourseMerger < BulkImport::Base @auto_group_ids = Group::AUTO_GROUPS.values # add your authorized extensions here: - SiteSetting.authorized_extensions = ['jpg', 'jpeg', 'png', 'gif'].join('|') + SiteSetting.authorized_extensions = %w[jpg jpeg png gif].join("|") @sequences = {} end @@ -99,7 +103,7 @@ class BulkImport::DiscourseMerger < BulkImport::Base end def copy_users - puts '', "merging users..." + puts "", "merging users..." imported_ids = [] @@ -109,34 +113,38 @@ class BulkImport::DiscourseMerger < BulkImport::Base sql = "COPY users (#{columns.map { |c| "\"#{c}\"" }.join(",")}) FROM STDIN" @raw_connection.copy_data(sql, @encoder) do - source_raw_connection.exec("SELECT #{columns.map { |c| "u.\"#{c}\"" }.join(",")}, e.email FROM users u INNER JOIN user_emails e ON (u.id = e.user_id AND e.primary = TRUE) WHERE u.id > 0").each do |row| - old_user_id = row['id']&.to_i - if existing = UserEmail.where(email: row.delete('email')).first&.user - # Merge these users - @users[old_user_id] = existing.id - @merged_user_ids << old_user_id - next - else - # New user - unless usernames_lower.add?(row['username_lower']) - username = row['username'] + "_1" - username.next! until usernames_lower.add?(username.downcase) - row['username'] = username - row['username_lower'] = row['username'].downcase + source_raw_connection + .exec( + "SELECT #{columns.map { |c| "u.\"#{c}\"" }.join(",")}, e.email FROM users u INNER JOIN user_emails e ON (u.id = e.user_id AND e.primary = TRUE) WHERE u.id > 0", + ) + .each do |row| + old_user_id = row["id"]&.to_i + if existing = UserEmail.where(email: row.delete("email")).first&.user + # Merge these users + @users[old_user_id] = existing.id + @merged_user_ids << old_user_id + next + else + # New user + unless usernames_lower.add?(row["username_lower"]) + username = row["username"] + "_1" + username.next! until usernames_lower.add?(username.downcase) + row["username"] = username + row["username_lower"] = row["username"].downcase + end + + row["id"] = (@last_user_id += 1) + @users[old_user_id] = row["id"] + + @raw_connection.put_copy_data row.values end - - row['id'] = (@last_user_id += 1) - @users[old_user_id] = row['id'] - - @raw_connection.put_copy_data row.values + imported_ids << old_user_id end - imported_ids << old_user_id - end end @sequences[User.sequence_name] = @last_user_id + 1 if @last_user_id - create_custom_fields('user', 'id', imported_ids) do |old_user_id| + create_custom_fields("user", "id", imported_ids) do |old_user_id| { value: old_user_id, record_id: user_id_from_imported_id(old_user_id) } end end @@ -147,28 +155,32 @@ class BulkImport::DiscourseMerger < BulkImport::Base skip_if_merged: true, is_a_user_model: true, skip_processing: true, - mapping: @email_tokens + mapping: @email_tokens, ) [ - UserEmail, UserStat, UserOption, UserProfile, - UserVisit, UserSearchData, GivenDailyLike, UserSecondFactor - ].each do |c| - copy_model(c, skip_if_merged: true, is_a_user_model: true, skip_processing: true) - end + UserEmail, + UserStat, + UserOption, + UserProfile, + UserVisit, + UserSearchData, + GivenDailyLike, + UserSecondFactor, + ].each { |c| copy_model(c, skip_if_merged: true, is_a_user_model: true, skip_processing: true) } - [UserAssociatedAccount, Oauth2UserInfo, - SingleSignOnRecord, EmailChangeRequest - ].each do |c| + [UserAssociatedAccount, Oauth2UserInfo, SingleSignOnRecord, EmailChangeRequest].each do |c| copy_model(c, skip_if_merged: true, is_a_user_model: true) end end def copy_groups - copy_model(Group, + copy_model( + Group, mapping: @groups, skip_processing: true, - select_sql: "SELECT #{Group.columns.map { |c| "\"#{c.name}\"" }.join(', ')} FROM groups WHERE automatic = false" + select_sql: + "SELECT #{Group.columns.map { |c| "\"#{c.name}\"" }.join(", ")} FROM groups WHERE automatic = false", ) copy_model(GroupUser, skip_if_merged: true) @@ -181,11 +193,12 @@ class BulkImport::DiscourseMerger < BulkImport::Base imported_ids = [] last_id = Category.unscoped.maximum(:id) || 1 - sql = "COPY categories (#{columns.map { |c| "\"#{c}\"" }.join(', ')}) FROM STDIN" + sql = "COPY categories (#{columns.map { |c| "\"#{c}\"" }.join(", ")}) FROM STDIN" @raw_connection.copy_data(sql, @encoder) do - source_raw_connection.exec( + source_raw_connection + .exec( "SELECT concat('/c/', x.parent_slug, '/', x.slug) as path, - #{columns.map { |c| "c.\"#{c}\"" }.join(', ')} + #{columns.map { |c| "c.\"#{c}\"" }.join(", ")} FROM categories c INNER JOIN ( SELECT c1.id AS id, @@ -194,61 +207,55 @@ class BulkImport::DiscourseMerger < BulkImport::Base FROM categories c1 LEFT OUTER JOIN categories c2 ON c1.parent_category_id = c2.id ) x ON c.id = x.id - ORDER BY c.id" - ).each do |row| + ORDER BY c.id", + ) + .each do |row| + # using ORDER BY id to import categories in order of creation. + # this assumes parent categories were created prior to child categories + # and have a lower category id. + # + # without this definition, categories import in different orders in subsequent imports + # and can potentially mess up parent/child structure - # using ORDER BY id to import categories in order of creation. - # this assumes parent categories were created prior to child categories - # and have a lower category id. - # - # without this definition, categories import in different orders in subsequent imports - # and can potentially mess up parent/child structure + source_category_path = row.delete("path")&.squeeze("/") - source_category_path = row.delete('path')&.squeeze('/') + existing = Category.where(slug: row["slug"]).first + parent_slug = existing&.parent_category&.slug + if existing && source_category_path == "/c/#{parent_slug}/#{existing.slug}".squeeze("/") + @categories[row["id"].to_i] = existing.id + next + elsif existing + # if not the exact path as the source, + # we still need to avoid a unique index conflict on the slug when importing + # if that's the case, we'll append the imported id + row["slug"] = "#{row["slug"]}-#{row["id"]}" + end - existing = Category.where(slug: row['slug']).first - parent_slug = existing&.parent_category&.slug - if existing && - source_category_path == "/c/#{parent_slug}/#{existing.slug}".squeeze('/') - @categories[row['id'].to_i] = existing.id - next - elsif existing - # if not the exact path as the source, - # we still need to avoid a unique index conflict on the slug when importing - # if that's the case, we'll append the imported id - row['slug'] = "#{row['slug']}-#{row['id']}" + old_user_id = row["user_id"].to_i + row["user_id"] = user_id_from_imported_id(old_user_id) || -1 if old_user_id >= 1 + + if row["parent_category_id"] + row["parent_category_id"] = category_id_from_imported_id(row["parent_category_id"]) + end + + old_id = row["id"].to_i + row["id"] = (last_id += 1) + imported_ids << old_id + @categories[old_id] = row["id"] + + @raw_connection.put_copy_data(row.values) end - - old_user_id = row['user_id'].to_i - if old_user_id >= 1 - row['user_id'] = user_id_from_imported_id(old_user_id) || -1 - end - - if row['parent_category_id'] - row['parent_category_id'] = category_id_from_imported_id(row['parent_category_id']) - end - - old_id = row['id'].to_i - row['id'] = (last_id += 1) - imported_ids << old_id - @categories[old_id] = row['id'] - - @raw_connection.put_copy_data(row.values) - end end @sequences[Category.sequence_name] = last_id + 1 - create_custom_fields('category', 'id', imported_ids) do |imported_id| - { - record_id: category_id_from_imported_id(imported_id), - value: imported_id, - } + create_custom_fields("category", "id", imported_ids) do |imported_id| + { record_id: category_id_from_imported_id(imported_id), value: imported_id } end end def fix_category_descriptions - puts 'updating category description topic ids...' + puts "updating category description topic ids..." @categories.each do |old_id, new_id| category = Category.find(new_id) if new_id.present? @@ -261,19 +268,21 @@ class BulkImport::DiscourseMerger < BulkImport::Base def copy_topics copy_model(Topic, mapping: @topics) - [TopicAllowedGroup, TopicAllowedUser, TopicEmbed, TopicSearchData, - TopicTimer, TopicUser, TopicViewItem - ].each do |k| - copy_model(k, skip_processing: true) - end + [ + TopicAllowedGroup, + TopicAllowedUser, + TopicEmbed, + TopicSearchData, + TopicTimer, + TopicUser, + TopicViewItem, + ].each { |k| copy_model(k, skip_processing: true) } end def copy_posts copy_model(Post, skip_processing: true, mapping: @posts) copy_model(PostAction, mapping: @post_actions) - [PostReply, TopicLink, UserAction, QuotedPost].each do |k| - copy_model(k) - end + [PostReply, TopicLink, UserAction, QuotedPost].each { |k| copy_model(k) } [PostStat, IncomingEmail, PostDetail, PostRevision].each do |k| copy_model(k, skip_processing: true) end @@ -286,99 +295,101 @@ class BulkImport::DiscourseMerger < BulkImport::Base imported_ids = [] last_id = Tag.unscoped.maximum(:id) || 1 - sql = "COPY tags (#{columns.map { |c| "\"#{c}\"" }.join(', ')}) FROM STDIN" + sql = "COPY tags (#{columns.map { |c| "\"#{c}\"" }.join(", ")}) FROM STDIN" @raw_connection.copy_data(sql, @encoder) do - source_raw_connection.exec("SELECT #{columns.map { |c| "\"#{c}\"" }.join(', ')} FROM tags").each do |row| + source_raw_connection + .exec("SELECT #{columns.map { |c| "\"#{c}\"" }.join(", ")} FROM tags") + .each do |row| + if existing = Tag.where_name(row["name"]).first + @tags[row["id"]] = existing.id + next + end - if existing = Tag.where_name(row['name']).first - @tags[row['id']] = existing.id - next + old_id = row["id"] + row["id"] = (last_id += 1) + @tags[old_id.to_s] = row["id"] + + @raw_connection.put_copy_data(row.values) end - - old_id = row['id'] - row['id'] = (last_id += 1) - @tags[old_id.to_s] = row['id'] - - @raw_connection.put_copy_data(row.values) - end end @sequences[Tag.sequence_name] = last_id + 1 - [TagUser, TopicTag, CategoryTag, CategoryTagStat].each do |k| - copy_model(k) - end + [TagUser, TopicTag, CategoryTag, CategoryTagStat].each { |k| copy_model(k) } copy_model(TagGroup, mapping: @tag_groups) - [TagGroupMembership, CategoryTagGroup].each do |k| - copy_model(k, skip_processing: true) - end + [TagGroupMembership, CategoryTagGroup].each { |k| copy_model(k, skip_processing: true) } - col_list = TagGroupPermission.columns.map { |c| "\"#{c.name}\"" }.join(', ') - copy_model(TagGroupPermission, + col_list = TagGroupPermission.columns.map { |c| "\"#{c.name}\"" }.join(", ") + copy_model( + TagGroupPermission, skip_processing: true, - select_sql: "SELECT #{col_list} FROM tag_group_permissions WHERE group_id NOT IN (#{@auto_group_ids.join(', ')})" + select_sql: + "SELECT #{col_list} FROM tag_group_permissions WHERE group_id NOT IN (#{@auto_group_ids.join(", ")})", ) end def copy_uploads - puts '' + puts "" print "copying uploads..." FileUtils.cp_r( - File.join(@uploads_path, '.'), - File.join(Rails.root, 'public', 'uploads', 'default') + File.join(@uploads_path, "."), + File.join(Rails.root, "public", "uploads", "default"), ) columns = Upload.columns.map(&:name) last_id = Upload.unscoped.maximum(:id) || 1 - sql = "COPY uploads (#{columns.map { |c| "\"#{c}\"" }.join(', ')}) FROM STDIN" + sql = "COPY uploads (#{columns.map { |c| "\"#{c}\"" }.join(", ")}) FROM STDIN" @raw_connection.copy_data(sql, @encoder) do - source_raw_connection.exec("SELECT #{columns.map { |c| "\"#{c}\"" }.join(', ')} FROM uploads").each do |row| + source_raw_connection + .exec("SELECT #{columns.map { |c| "\"#{c}\"" }.join(", ")} FROM uploads") + .each do |row| + next if Upload.where(sha1: row["sha1"]).exists? - next if Upload.where(sha1: row['sha1']).exists? + # make sure to get a backup with uploads then convert them to local. + # when the backup is restored to a site with s3 uploads, it will upload the items + # to the bucket + rel_filename = row["url"].gsub(%r{^/uploads/[^/]+/}, "") + # assumes if coming from amazonaws.com that we want to remove everything + # but the text after the last `/`, which should leave us the filename + rel_filename = rel_filename.gsub(%r{^//[^/]+\.amazonaws\.com/\S+/}, "") + absolute_filename = File.join(@uploads_path, rel_filename) - # make sure to get a backup with uploads then convert them to local. - # when the backup is restored to a site with s3 uploads, it will upload the items - # to the bucket - rel_filename = row['url'].gsub(/^\/uploads\/[^\/]+\//, '') - # assumes if coming from amazonaws.com that we want to remove everything - # but the text after the last `/`, which should leave us the filename - rel_filename = rel_filename.gsub(/^\/\/[^\/]+\.amazonaws\.com\/\S+\//, '') - absolute_filename = File.join(@uploads_path, rel_filename) + old_id = row["id"] + if old_id && last_id + row["id"] = (last_id += 1) + @uploads[old_id.to_s] = row["id"] + end - old_id = row['id'] - if old_id && last_id - row['id'] = (last_id += 1) - @uploads[old_id.to_s] = row['id'] + old_user_id = row["user_id"].to_i + if old_user_id >= 1 + row["user_id"] = user_id_from_imported_id(old_user_id) + next if row["user_id"].nil? + end + + row["url"] = "/uploads/default/#{rel_filename}" if File.exist?(absolute_filename) + + @raw_connection.put_copy_data(row.values) end - - old_user_id = row['user_id'].to_i - if old_user_id >= 1 - row['user_id'] = user_id_from_imported_id(old_user_id) - next if row['user_id'].nil? - end - - row['url'] = "/uploads/default/#{rel_filename}" if File.exist?(absolute_filename) - - @raw_connection.put_copy_data(row.values) - end end @sequences[Upload.sequence_name] = last_id + 1 - puts '' + puts "" copy_model(PostUpload) copy_model(UserAvatar) # Users have a column "uploaded_avatar_id" which needs to be mapped now. - User.where("id >= ?", @first_new_user_id).find_each do |u| - if u.uploaded_avatar_id - u.uploaded_avatar_id = upload_id_from_imported_id(u.uploaded_avatar_id) - u.save! unless u.uploaded_avatar_id.nil? + User + .where("id >= ?", @first_new_user_id) + .find_each do |u| + if u.uploaded_avatar_id + u.uploaded_avatar_id = upload_id_from_imported_id(u.uploaded_avatar_id) + u.save! unless u.uploaded_avatar_id.nil? + end end - end end def copy_everything_else @@ -386,16 +397,16 @@ class BulkImport::DiscourseMerger < BulkImport::Base copy_model(k, skip_processing: true) end - [UserHistory, UserWarning, GroupArchivedMessage].each do |k| - copy_model(k) - end + [UserHistory, UserWarning, GroupArchivedMessage].each { |k| copy_model(k) } copy_model(Notification, mapping: @notifications) [CategoryGroup, GroupHistory].each do |k| - col_list = k.columns.map { |c| "\"#{c.name}\"" }.join(', ') - copy_model(k, - select_sql: "SELECT #{col_list} FROM #{k.table_name} WHERE group_id NOT IN (#{@auto_group_ids.join(', ')})" + col_list = k.columns.map { |c| "\"#{c.name}\"" }.join(", ") + copy_model( + k, + select_sql: + "SELECT #{col_list} FROM #{k.table_name} WHERE group_id NOT IN (#{@auto_group_ids.join(", ")})", ) end end @@ -408,23 +419,26 @@ class BulkImport::DiscourseMerger < BulkImport::Base imported_ids = [] last_id = Badge.unscoped.maximum(:id) || 1 - sql = "COPY badges (#{columns.map { |c| "\"#{c}\"" }.join(', ')}) FROM STDIN" + sql = "COPY badges (#{columns.map { |c| "\"#{c}\"" }.join(", ")}) FROM STDIN" @raw_connection.copy_data(sql, @encoder) do - source_raw_connection.exec("SELECT #{columns.map { |c| "\"#{c}\"" }.join(', ')} FROM badges").each do |row| + source_raw_connection + .exec("SELECT #{columns.map { |c| "\"#{c}\"" }.join(", ")} FROM badges") + .each do |row| + if existing = Badge.where(name: row["name"]).first + @badges[row["id"]] = existing.id + next + end - if existing = Badge.where(name: row['name']).first - @badges[row['id']] = existing.id - next + old_id = row["id"] + row["id"] = (last_id += 1) + @badges[old_id.to_s] = row["id"] + + row["badge_grouping_id"] = @badge_groupings[row["badge_grouping_id"]] if row[ + "badge_grouping_id" + ] + + @raw_connection.put_copy_data(row.values) end - - old_id = row['id'] - row['id'] = (last_id += 1) - @badges[old_id.to_s] = row['id'] - - row['badge_grouping_id'] = @badge_groupings[row['badge_grouping_id']] if row['badge_grouping_id'] - - @raw_connection.put_copy_data(row.values) - end end @sequences[Badge.sequence_name] = last_id + 1 @@ -432,72 +446,94 @@ class BulkImport::DiscourseMerger < BulkImport::Base copy_model(UserBadge, is_a_user_model: true) end - def copy_model(klass, skip_if_merged: false, is_a_user_model: false, skip_processing: false, mapping: nil, select_sql: nil) - + def copy_model( + klass, + skip_if_merged: false, + is_a_user_model: false, + skip_processing: false, + mapping: nil, + select_sql: nil + ) puts "copying #{klass.table_name}..." columns = klass.columns.map(&:name) has_custom_fields = CUSTOM_FIELDS.include?(klass.name.downcase) imported_ids = [] - last_id = columns.include?('id') ? (klass.unscoped.maximum(:id) || 1) : nil + last_id = columns.include?("id") ? (klass.unscoped.maximum(:id) || 1) : nil - sql = "COPY #{klass.table_name} (#{columns.map { |c| "\"#{c}\"" }.join(', ')}) FROM STDIN" + sql = "COPY #{klass.table_name} (#{columns.map { |c| "\"#{c}\"" }.join(", ")}) FROM STDIN" @raw_connection.copy_data(sql, @encoder) do - source_raw_connection.exec(select_sql || "SELECT #{columns.map { |c| "\"#{c}\"" }.join(', ')} FROM #{klass.table_name}").each do |row| - if row['user_id'] - old_user_id = row['user_id'].to_i + source_raw_connection + .exec( + select_sql || + "SELECT #{columns.map { |c| "\"#{c}\"" }.join(", ")} FROM #{klass.table_name}", + ) + .each do |row| + if row["user_id"] + old_user_id = row["user_id"].to_i - next if skip_if_merged && @merged_user_ids.include?(old_user_id) + next if skip_if_merged && @merged_user_ids.include?(old_user_id) - if is_a_user_model - next if old_user_id < 1 - next if user_id_from_imported_id(old_user_id).nil? - end - - if old_user_id >= 1 - row['user_id'] = user_id_from_imported_id(old_user_id) - if is_a_user_model && row['user_id'].nil? - raise "user_id nil for user id '#{old_user_id}'" + if is_a_user_model + next if old_user_id < 1 + next if user_id_from_imported_id(old_user_id).nil? end - next if row['user_id'].nil? # associated record for a deleted user + + if old_user_id >= 1 + row["user_id"] = user_id_from_imported_id(old_user_id) + if is_a_user_model && row["user_id"].nil? + raise "user_id nil for user id '#{old_user_id}'" + end + next if row["user_id"].nil? # associated record for a deleted user + end + end + + row["group_id"] = group_id_from_imported_id(row["group_id"]) if row["group_id"] + row["category_id"] = category_id_from_imported_id(row["category_id"]) if row[ + "category_id" + ] + if row["topic_id"] && klass != Category + row["topic_id"] = topic_id_from_imported_id(row["topic_id"]) + next if row["topic_id"].nil? + end + if row["post_id"] + row["post_id"] = post_id_from_imported_id(row["post_id"]) + next if row["post_id"].nil? + end + row["tag_id"] = tag_id_from_imported_id(row["tag_id"]) if row["tag_id"] + row["tag_group_id"] = tag_group_id_from_imported_id(row["tag_group_id"]) if row[ + "tag_group_id" + ] + row["upload_id"] = upload_id_from_imported_id(row["upload_id"]) if row["upload_id"] + row["deleted_by_id"] = user_id_from_imported_id(row["deleted_by_id"]) if row[ + "deleted_by_id" + ] + row["badge_id"] = badge_id_from_imported_id(row["badge_id"]) if row["badge_id"] + + old_id = row["id"].to_i + if old_id && last_id + row["id"] = (last_id += 1) + imported_ids << old_id if has_custom_fields + mapping[old_id] = row["id"] if mapping + end + + if skip_processing + @raw_connection.put_copy_data(row.values) + else + process_method_name = "process_#{klass.name.underscore}" + + processed = + ( + if respond_to?(process_method_name) + send(process_method_name, HashWithIndifferentAccess.new(row)) + else + row + end + ) + + @raw_connection.put_copy_data columns.map { |c| processed[c] } if processed end end - - row['group_id'] = group_id_from_imported_id(row['group_id']) if row['group_id'] - row['category_id'] = category_id_from_imported_id(row['category_id']) if row['category_id'] - if row['topic_id'] && klass != Category - row['topic_id'] = topic_id_from_imported_id(row['topic_id']) - next if row['topic_id'].nil? - end - if row['post_id'] - row['post_id'] = post_id_from_imported_id(row['post_id']) - next if row['post_id'].nil? - end - row['tag_id'] = tag_id_from_imported_id(row['tag_id']) if row['tag_id'] - row['tag_group_id'] = tag_group_id_from_imported_id(row['tag_group_id']) if row['tag_group_id'] - row['upload_id'] = upload_id_from_imported_id(row['upload_id']) if row['upload_id'] - row['deleted_by_id'] = user_id_from_imported_id(row['deleted_by_id']) if row['deleted_by_id'] - row['badge_id'] = badge_id_from_imported_id(row['badge_id']) if row['badge_id'] - - old_id = row['id'].to_i - if old_id && last_id - row['id'] = (last_id += 1) - imported_ids << old_id if has_custom_fields - mapping[old_id] = row['id'] if mapping - end - - if skip_processing - @raw_connection.put_copy_data(row.values) - else - process_method_name = "process_#{klass.name.underscore}" - - processed = respond_to?(process_method_name) ? send(process_method_name, HashWithIndifferentAccess.new(row)) : row - - if processed - @raw_connection.put_copy_data columns.map { |c| processed[c] } - end - end - end end @sequences[klass.sequence_name] = last_id + 1 if last_id @@ -506,192 +542,248 @@ class BulkImport::DiscourseMerger < BulkImport::Base id_mapping_method_name = "#{klass.name.downcase}_id_from_imported_id".freeze return unless respond_to?(id_mapping_method_name) create_custom_fields(klass.name.downcase, "id", imported_ids) do |imported_id| - { - record_id: send(id_mapping_method_name, imported_id), - value: imported_id, - } + { record_id: send(id_mapping_method_name, imported_id), value: imported_id } end end end def process_topic(topic) - return nil if topic['category_id'].nil? && topic['archetype'] != Archetype.private_message - topic['last_post_user_id'] = user_id_from_imported_id(topic['last_post_user_id']) || -1 - topic['featured_user1_id'] = user_id_from_imported_id(topic['featured_user1_id']) || -1 - topic['featured_user2_id'] = user_id_from_imported_id(topic['featured_user2_id']) || -1 - topic['featured_user3_id'] = user_id_from_imported_id(topic['featured_user3_id']) || -1 - topic['featured_user4_id'] = user_id_from_imported_id(topic['featured_user4_id']) || -1 + return nil if topic["category_id"].nil? && topic["archetype"] != Archetype.private_message + topic["last_post_user_id"] = user_id_from_imported_id(topic["last_post_user_id"]) || -1 + topic["featured_user1_id"] = user_id_from_imported_id(topic["featured_user1_id"]) || -1 + topic["featured_user2_id"] = user_id_from_imported_id(topic["featured_user2_id"]) || -1 + topic["featured_user3_id"] = user_id_from_imported_id(topic["featured_user3_id"]) || -1 + topic["featured_user4_id"] = user_id_from_imported_id(topic["featured_user4_id"]) || -1 topic end def process_post(post) - post['last_editor_id'] = user_id_from_imported_id(post['last_editor_id']) || -1 - post['reply_to_user_id'] = user_id_from_imported_id(post['reply_to_user_id']) || -1 - post['locked_by_id'] = user_id_from_imported_id(post['locked_by_id']) || -1 + post["last_editor_id"] = user_id_from_imported_id(post["last_editor_id"]) || -1 + post["reply_to_user_id"] = user_id_from_imported_id(post["reply_to_user_id"]) || -1 + post["locked_by_id"] = user_id_from_imported_id(post["locked_by_id"]) || -1 @topic_id_by_post_id[post[:id]] = post[:topic_id] post end def process_post_reply(post_reply) - post_reply['reply_post_id'] = post_id_from_imported_id(post_reply['reply_post_id']) if post_reply['reply_post_id'] + post_reply["reply_post_id"] = post_id_from_imported_id( + post_reply["reply_post_id"], + ) if post_reply["reply_post_id"] post_reply end def process_quoted_post(quoted_post) - quoted_post['quoted_post_id'] = post_id_from_imported_id(quoted_post['quoted_post_id']) if quoted_post['quoted_post_id'] - return nil if quoted_post['quoted_post_id'].nil? + quoted_post["quoted_post_id"] = post_id_from_imported_id( + quoted_post["quoted_post_id"], + ) if quoted_post["quoted_post_id"] + return nil if quoted_post["quoted_post_id"].nil? quoted_post end def process_topic_link(topic_link) - old_topic_id = topic_link['link_topic_id'] - topic_link['link_topic_id'] = topic_id_from_imported_id(topic_link['link_topic_id']) if topic_link['link_topic_id'] - topic_link['link_post_id'] = post_id_from_imported_id(topic_link['link_post_id']) if topic_link['link_post_id'] - return nil if topic_link['link_topic_id'].nil? + old_topic_id = topic_link["link_topic_id"] + topic_link["link_topic_id"] = topic_id_from_imported_id( + topic_link["link_topic_id"], + ) if topic_link["link_topic_id"] + topic_link["link_post_id"] = post_id_from_imported_id(topic_link["link_post_id"]) if topic_link[ + "link_post_id" + ] + return nil if topic_link["link_topic_id"].nil? r = Regexp.new("^#{@source_base_url}/t/([^\/]+)/#{old_topic_id}(.*)") - if m = r.match(topic_link['url']) - topic_link['url'] = "#{@source_base_url}/t/#{m[1]}/#{topic_link['link_topic_id']}#{m[2]}" + if m = r.match(topic_link["url"]) + topic_link["url"] = "#{@source_base_url}/t/#{m[1]}/#{topic_link["link_topic_id"]}#{m[2]}" end topic_link end def process_post_action(post_action) - return nil unless post_action['post_id'].present? - post_action['related_post_id'] = post_id_from_imported_id(post_action['related_post_id']) - post_action['deferred_by_id'] = user_id_from_imported_id(post_action['deferred_by_id']) - post_action['agreed_by_id'] = user_id_from_imported_id(post_action['agreed_by_id']) - post_action['disagreed_by_id'] = user_id_from_imported_id(post_action['disagreed_by_id']) + return nil unless post_action["post_id"].present? + post_action["related_post_id"] = post_id_from_imported_id(post_action["related_post_id"]) + post_action["deferred_by_id"] = user_id_from_imported_id(post_action["deferred_by_id"]) + post_action["agreed_by_id"] = user_id_from_imported_id(post_action["agreed_by_id"]) + post_action["disagreed_by_id"] = user_id_from_imported_id(post_action["disagreed_by_id"]) post_action end def process_user_action(user_action) - user_action['target_topic_id'] = topic_id_from_imported_id(user_action['target_topic_id']) if user_action['target_topic_id'] - user_action['target_post_id'] = post_id_from_imported_id(user_action['target_post_id']) if user_action['target_post_id'] - user_action['target_user_id'] = user_id_from_imported_id(user_action['target_user_id']) if user_action['target_user_id'] - user_action['acting_user_id'] = user_id_from_imported_id(user_action['acting_user_id']) if user_action['acting_user_id'] - user_action['queued_post_id'] = post_id_from_imported_id(user_action['queued_post_id']) if user_action['queued_post_id'] + user_action["target_topic_id"] = topic_id_from_imported_id( + user_action["target_topic_id"], + ) if user_action["target_topic_id"] + user_action["target_post_id"] = post_id_from_imported_id( + user_action["target_post_id"], + ) if user_action["target_post_id"] + user_action["target_user_id"] = user_id_from_imported_id( + user_action["target_user_id"], + ) if user_action["target_user_id"] + user_action["acting_user_id"] = user_id_from_imported_id( + user_action["acting_user_id"], + ) if user_action["acting_user_id"] + user_action["queued_post_id"] = post_id_from_imported_id( + user_action["queued_post_id"], + ) if user_action["queued_post_id"] user_action end def process_tag_group(tag_group) - tag_group['parent_tag_id'] = tag_id_from_imported_id(tag_group['parent_tag_id']) if tag_group['parent_tag_id'] + tag_group["parent_tag_id"] = tag_id_from_imported_id(tag_group["parent_tag_id"]) if tag_group[ + "parent_tag_id" + ] tag_group end def process_category_group(category_group) - return nil if category_group['category_id'].nil? || category_group['group_id'].nil? + return nil if category_group["category_id"].nil? || category_group["group_id"].nil? category_group end def process_group_user(group_user) - if @auto_group_ids.include?(group_user['group_id'].to_i) && - @merged_user_ids.include?(group_user['user_id'].to_i) + if @auto_group_ids.include?(group_user["group_id"].to_i) && + @merged_user_ids.include?(group_user["user_id"].to_i) return nil end - return nil if group_user['user_id'].to_i < 1 + return nil if group_user["user_id"].to_i < 1 group_user end def process_group_history(group_history) - group_history['acting_user_id'] = user_id_from_imported_id(group_history['acting_user_id']) if group_history['acting_user_id'] - group_history['target_user_id'] = user_id_from_imported_id(group_history['target_user_id']) if group_history['target_user_id'] + group_history["acting_user_id"] = user_id_from_imported_id( + group_history["acting_user_id"], + ) if group_history["acting_user_id"] + group_history["target_user_id"] = user_id_from_imported_id( + group_history["target_user_id"], + ) if group_history["target_user_id"] group_history end def process_group_archived_message(gam) - return nil unless gam['topic_id'].present? && gam['group_id'].present? + return nil unless gam["topic_id"].present? && gam["group_id"].present? gam end def process_topic_link(topic_link) - topic_link['link_topic_id'] = topic_id_from_imported_id(topic_link['link_topic_id']) if topic_link['link_topic_id'] - topic_link['link_post_id'] = post_id_from_imported_id(topic_link['link_post_id']) if topic_link['link_post_id'] + topic_link["link_topic_id"] = topic_id_from_imported_id( + topic_link["link_topic_id"], + ) if topic_link["link_topic_id"] + topic_link["link_post_id"] = post_id_from_imported_id(topic_link["link_post_id"]) if topic_link[ + "link_post_id" + ] topic_link end def process_user_avatar(user_avatar) - user_avatar['custom_upload_id'] = upload_id_from_imported_id(user_avatar['custom_upload_id']) if user_avatar['custom_upload_id'] - user_avatar['gravatar_upload_id'] = upload_id_from_imported_id(user_avatar['gravatar_upload_id']) if user_avatar['gravatar_upload_id'] - return nil unless user_avatar['custom_upload_id'].present? || user_avatar['gravatar_upload_id'].present? + user_avatar["custom_upload_id"] = upload_id_from_imported_id( + user_avatar["custom_upload_id"], + ) if user_avatar["custom_upload_id"] + user_avatar["gravatar_upload_id"] = upload_id_from_imported_id( + user_avatar["gravatar_upload_id"], + ) if user_avatar["gravatar_upload_id"] + unless user_avatar["custom_upload_id"].present? || user_avatar["gravatar_upload_id"].present? + return nil + end user_avatar end def process_user_history(user_history) - user_history['acting_user_id'] = user_id_from_imported_id(user_history['acting_user_id']) if user_history['acting_user_id'] - user_history['target_user_id'] = user_id_from_imported_id(user_history['target_user_id']) if user_history['target_user_id'] + user_history["acting_user_id"] = user_id_from_imported_id( + user_history["acting_user_id"], + ) if user_history["acting_user_id"] + user_history["target_user_id"] = user_id_from_imported_id( + user_history["target_user_id"], + ) if user_history["target_user_id"] user_history end def process_user_warning(user_warning) - user_warning['created_by_id'] = user_id_from_imported_id(user_warning['created_by_id']) if user_warning['created_by_id'] + user_warning["created_by_id"] = user_id_from_imported_id( + user_warning["created_by_id"], + ) if user_warning["created_by_id"] user_warning end def process_post_upload(post_upload) - return nil unless post_upload['upload_id'].present? + return nil unless post_upload["upload_id"].present? @imported_post_uploads ||= {} - return nil if @imported_post_uploads[post_upload['post_id']]&.include?(post_upload['upload_id']) - @imported_post_uploads[post_upload['post_id']] ||= [] - @imported_post_uploads[post_upload['post_id']] << post_upload['upload_id'] + return nil if @imported_post_uploads[post_upload["post_id"]]&.include?(post_upload["upload_id"]) + @imported_post_uploads[post_upload["post_id"]] ||= [] + @imported_post_uploads[post_upload["post_id"]] << post_upload["upload_id"] - return nil if PostUpload.where(post_id: post_upload['post_id'], upload_id: post_upload['upload_id']).exists? + if PostUpload.where( + post_id: post_upload["post_id"], + upload_id: post_upload["upload_id"], + ).exists? + return nil + end post_upload end def process_notification(notification) - notification['post_action_id'] = post_action_id_from_imported_id(notification['post_action_id']) if notification['post_action_id'] + notification["post_action_id"] = post_action_id_from_imported_id( + notification["post_action_id"], + ) if notification["post_action_id"] notification end def process_oauth2_user_info(r) - return nil if Oauth2UserInfo.where(uid: r['uid'], provider: r['provider']).exists? + return nil if Oauth2UserInfo.where(uid: r["uid"], provider: r["provider"]).exists? r end def process_user_associated_account(r) - return nil if UserAssociatedAccount.where(provider_uid: r['uid'], provider_name: r['provider']).exists? + if UserAssociatedAccount.where(provider_uid: r["uid"], provider_name: r["provider"]).exists? + return nil + end r end def process_single_sign_on_record(r) - return nil if SingleSignOnRecord.where(external_id: r['external_id']).exists? + return nil if SingleSignOnRecord.where(external_id: r["external_id"]).exists? r end def process_user_badge(user_badge) - user_badge['granted_by_id'] = user_id_from_imported_id(user_badge['granted_by_id']) if user_badge['granted_by_id'] - user_badge['notification_id'] = notification_id_from_imported_id(user_badge['notification_id']) if user_badge['notification_id'] - return nil if UserBadge.where(user_id: user_badge['user_id'], badge_id: user_badge['badge_id']).exists? + user_badge["granted_by_id"] = user_id_from_imported_id( + user_badge["granted_by_id"], + ) if user_badge["granted_by_id"] + user_badge["notification_id"] = notification_id_from_imported_id( + user_badge["notification_id"], + ) if user_badge["notification_id"] + if UserBadge.where(user_id: user_badge["user_id"], badge_id: user_badge["badge_id"]).exists? + return nil + end user_badge end def process_email_change_request(ecr) - ecr['old_email_token_id'] = email_token_id_from_imported_id(ecr['old_email_token_id']) if ecr['old_email_token_id'] - ecr['new_email_token_id'] = email_token_id_from_imported_id(ecr['new_email_token_id']) if ecr['new_email_token_id'] + ecr["old_email_token_id"] = email_token_id_from_imported_id(ecr["old_email_token_id"]) if ecr[ + "old_email_token_id" + ] + ecr["new_email_token_id"] = email_token_id_from_imported_id(ecr["new_email_token_id"]) if ecr[ + "new_email_token_id" + ] ecr end def process_tag_user(x) - return nil if TagUser.where(tag_id: x['tag_id'], user_id: x['user_id']).exists? + return nil if TagUser.where(tag_id: x["tag_id"], user_id: x["user_id"]).exists? x end def process_topic_tag(x) - return nil if TopicTag.where(topic_id: x['topic_id'], tag_id: x['tag_id']).exists? + return nil if TopicTag.where(topic_id: x["topic_id"], tag_id: x["tag_id"]).exists? x end def process_category_tag(x) - return nil if CategoryTag.where(category_id: x['category_id'], tag_id: x['tag_id']).exists? + return nil if CategoryTag.where(category_id: x["category_id"], tag_id: x["tag_id"]).exists? x end def process_category_tag_stat(x) - return nil if CategoryTagStat.where(category_id: x['category_id'], tag_id: x['tag_id']).exists? + return nil if CategoryTagStat.where(category_id: x["category_id"], tag_id: x["tag_id"]).exists? x end @@ -744,27 +836,29 @@ class BulkImport::DiscourseMerger < BulkImport::Base def fix_user_columns puts "updating foreign keys in the users table..." - User.where('id >= ?', @first_new_user_id).find_each do |u| - arr = [] - sql = "UPDATE users SET".dup + User + .where("id >= ?", @first_new_user_id) + .find_each do |u| + arr = [] + sql = "UPDATE users SET".dup - if new_approved_by_id = user_id_from_imported_id(u.approved_by_id) - arr << " approved_by_id = #{new_approved_by_id}" + if new_approved_by_id = user_id_from_imported_id(u.approved_by_id) + arr << " approved_by_id = #{new_approved_by_id}" + end + if new_primary_group_id = group_id_from_imported_id(u.primary_group_id) + arr << " primary_group_id = #{new_primary_group_id}" + end + if new_notification_id = notification_id_from_imported_id(u.seen_notification_id) + arr << " seen_notification_id = #{new_notification_id}" + end + + next if arr.empty? + + sql << arr.join(", ") + sql << " WHERE id = #{u.id}" + + @raw_connection.exec(sql) end - if new_primary_group_id = group_id_from_imported_id(u.primary_group_id) - arr << " primary_group_id = #{new_primary_group_id}" - end - if new_notification_id = notification_id_from_imported_id(u.seen_notification_id) - arr << " seen_notification_id = #{new_notification_id}" - end - - next if arr.empty? - - sql << arr.join(', ') - sql << " WHERE id = #{u.id}" - - @raw_connection.exec(sql) - end end def fix_topic_links @@ -777,33 +871,37 @@ class BulkImport::DiscourseMerger < BulkImport::Base @topics.each do |old_topic_id, new_topic_id| current += 1 percent = (current * 100) / total - puts "#{current} (#{percent}\%) completed. #{update_count} rows updated." if current % 200 == 0 + if current % 200 == 0 + puts "#{current} (#{percent}\%) completed. #{update_count} rows updated." + end if topic = Topic.find_by_id(new_topic_id) replace_arg = [ "#{@source_base_url}/t/#{topic.slug}/#{old_topic_id}", - "#{@source_base_url}/t/#{topic.slug}/#{new_topic_id}" + "#{@source_base_url}/t/#{topic.slug}/#{new_topic_id}", ] - r = @raw_connection.async_exec( - "UPDATE posts + r = + @raw_connection.async_exec( + "UPDATE posts SET raw = replace(raw, $1, $2) WHERE NOT raw IS NULL AND topic_id >= #{@first_new_topic_id} AND raw <> replace(raw, $1, $2)", - replace_arg - ) + replace_arg, + ) update_count += r.cmd_tuples - r = @raw_connection.async_exec( - "UPDATE posts + r = + @raw_connection.async_exec( + "UPDATE posts SET cooked = replace(cooked, $1, $2) WHERE NOT cooked IS NULL AND topic_id >= #{@first_new_topic_id} AND cooked <> replace(cooked, $1, $2)", - replace_arg - ) + replace_arg, + ) update_count += r.cmd_tuples end @@ -811,7 +909,6 @@ class BulkImport::DiscourseMerger < BulkImport::Base puts "updated #{update_count} rows" end - end BulkImport::DiscourseMerger.new.start diff --git a/script/bulk_import/phpbb_postgresql.rb b/script/bulk_import/phpbb_postgresql.rb index cd5fa626fd..70def55fb8 100644 --- a/script/bulk_import/phpbb_postgresql.rb +++ b/script/bulk_import/phpbb_postgresql.rb @@ -3,17 +3,16 @@ require_relative "base" require "pg" require "htmlentities" -require 'ruby-bbcode-to-md' +require "ruby-bbcode-to-md" class BulkImport::PhpBB < BulkImport::Base - SUSPENDED_TILL ||= Date.new(3000, 1, 1) - TABLE_PREFIX ||= ENV['TABLE_PREFIX'] || "phpbb_" + TABLE_PREFIX ||= ENV["TABLE_PREFIX"] || "phpbb_" def initialize super - charset = ENV["DB_CHARSET"] || "utf8" + charset = ENV["DB_CHARSET"] || "utf8" database = ENV["DB_NAME"] || "flightaware" password = ENV["DB_PASSWORD"] || "discourse" @@ -57,7 +56,7 @@ class BulkImport::PhpBB < BulkImport::Base { imported_id: row["group_id"], name: normalize_text(row["group_name"]), - bio_raw: normalize_text(row["group_desc"]) + bio_raw: normalize_text(row["group_desc"]), } end end @@ -85,15 +84,28 @@ class BulkImport::PhpBB < BulkImport::Base username: normalize_text(row["username"]), email: row["user_email"], created_at: Time.zone.at(row["user_regdate"].to_i), - last_seen_at: row["user_lastvisit"] == 0 ? Time.zone.at(row["user_regdate"].to_i) : Time.zone.at(row["user_lastvisit"].to_i), + last_seen_at: + ( + if row["user_lastvisit"] == 0 + Time.zone.at(row["user_regdate"].to_i) + else + Time.zone.at(row["user_lastvisit"].to_i) + end + ), trust_level: row["user_posts"] == 0 ? TrustLevel[0] : TrustLevel[1], date_of_birth: parse_birthday(row["user_birthday"]), - primary_group_id: group_id_from_imported_id(row["group_id"]) + primary_group_id: group_id_from_imported_id(row["group_id"]), } u[:ip_address] = row["user_ip"][/\b(?:\d{1,3}\.){3}\d{1,3}\b/] if row["user_ip"].present? if row["ban_start"] u[:suspended_at] = Time.zone.at(row["ban_start"].to_i) - u[:suspended_till] = row["ban_end"].to_i > 0 ? Time.zone.at(row["ban_end"].to_i) : SUSPENDED_TILL + u[:suspended_till] = ( + if row["ban_end"].to_i > 0 + Time.zone.at(row["ban_end"].to_i) + else + SUSPENDED_TILL + end + ) end u end @@ -114,7 +126,7 @@ class BulkImport::PhpBB < BulkImport::Base imported_id: row["user_id"], imported_user_id: row["user_id"], email: row["user_email"], - created_at: Time.zone.at(row["user_regdate"].to_i) + created_at: Time.zone.at(row["user_regdate"].to_i), } end end @@ -149,7 +161,14 @@ class BulkImport::PhpBB < BulkImport::Base create_user_profiles(user_profiles) do |row| { user_id: user_id_from_imported_id(row["user_id"]), - website: (URI.parse(row["user_website"]).to_s rescue nil), + website: + ( + begin + URI.parse(row["user_website"]).to_s + rescue StandardError + nil + end + ), location: row["user_from"], } end @@ -158,17 +177,16 @@ class BulkImport::PhpBB < BulkImport::Base def import_categories puts "Importing categories..." - categories = psql_query(<<-SQL + categories = psql_query(<<-SQL).to_a SELECT forum_id, parent_id, forum_name, forum_desc FROM #{TABLE_PREFIX}forums WHERE forum_id > #{@last_imported_category_id} ORDER BY parent_id, left_id SQL - ).to_a return if categories.empty? - parent_categories = categories.select { |c| c["parent_id"].to_i == 0 } + parent_categories = categories.select { |c| c["parent_id"].to_i == 0 } children_categories = categories.select { |c| c["parent_id"].to_i != 0 } puts "Importing parent categories..." @@ -176,7 +194,7 @@ class BulkImport::PhpBB < BulkImport::Base { imported_id: row["forum_id"], name: normalize_text(row["forum_name"]), - description: normalize_text(row["forum_desc"]) + description: normalize_text(row["forum_desc"]), } end @@ -186,7 +204,7 @@ class BulkImport::PhpBB < BulkImport::Base imported_id: row["forum_id"], name: normalize_text(row["forum_name"]), description: normalize_text(row["forum_desc"]), - parent_category_id: category_id_from_imported_id(row["parent_id"]) + parent_category_id: category_id_from_imported_id(row["parent_id"]), } end end @@ -209,7 +227,7 @@ class BulkImport::PhpBB < BulkImport::Base category_id: category_id_from_imported_id(row["forum_id"]), user_id: user_id_from_imported_id(row["topic_poster"]), created_at: Time.zone.at(row["topic_time"].to_i), - views: row["topic_views"] + views: row["topic_views"], } end end @@ -261,7 +279,7 @@ class BulkImport::PhpBB < BulkImport::Base imported_id: row["msg_id"].to_i + PRIVATE_OFFSET, title: normalize_text(title), user_id: user_id_from_imported_id(row["author_id"].to_i), - created_at: Time.zone.at(row["message_time"].to_i) + created_at: Time.zone.at(row["message_time"].to_i), } end end @@ -271,13 +289,12 @@ class BulkImport::PhpBB < BulkImport::Base allowed_users = [] - psql_query(<<-SQL + psql_query(<<-SQL).each do |row| SELECT msg_id, author_id, to_address FROM #{TABLE_PREFIX}privmsgs WHERE msg_id > (#{@last_imported_private_topic_id - PRIVATE_OFFSET}) ORDER BY msg_id SQL - ).each do |row| next unless topic_id = topic_id_from_imported_id(row["msg_id"].to_i + PRIVATE_OFFSET) user_ids = get_message_recipients(row["author_id"], row["to_address"]) @@ -287,12 +304,7 @@ class BulkImport::PhpBB < BulkImport::Base end end - create_topic_allowed_users(allowed_users) do |row| - { - topic_id: row[0], - user_id: row[1] - } - end + create_topic_allowed_users(allowed_users) { |row| { topic_id: row[0], user_id: row[1] } } end def import_private_posts @@ -316,13 +328,13 @@ class BulkImport::PhpBB < BulkImport::Base topic_id: topic_id, user_id: user_id_from_imported_id(row["author_id"].to_i), created_at: Time.zone.at(row["message_time"].to_i), - raw: process_raw_text(row["message_text"]) + raw: process_raw_text(row["message_text"]), } end end def get_message_recipients(from, to) - user_ids = to.split(':') + user_ids = to.split(":") user_ids.map! { |u| u[2..-1].to_i } user_ids.push(from.to_i) user_ids.uniq! @@ -332,15 +344,29 @@ class BulkImport::PhpBB < BulkImport::Base def extract_pm_title(title) pm_title = CGI.unescapeHTML(title) - pm_title = title.gsub(/^Re\s*:\s*/i, "") rescue nil + pm_title = + begin + title.gsub(/^Re\s*:\s*/i, "") + rescue StandardError + nil + end pm_title end def parse_birthday(birthday) return if birthday.blank? - date_of_birth = Date.strptime(birthday.gsub(/[^\d-]+/, ""), "%m-%d-%Y") rescue nil + date_of_birth = + begin + Date.strptime(birthday.gsub(/[^\d-]+/, ""), "%m-%d-%Y") + rescue StandardError + nil + end return if date_of_birth.nil? - date_of_birth.year < 1904 ? Date.new(1904, date_of_birth.month, date_of_birth.day) : date_of_birth + if date_of_birth.year < 1904 + Date.new(1904, date_of_birth.month, date_of_birth.day) + else + date_of_birth + end end def psql_query(sql) @@ -352,34 +378,36 @@ class BulkImport::PhpBB < BulkImport::Base text = raw.dup text = CGI.unescapeHTML(text) - text.gsub!(/:(?:\w{8})\]/, ']') + text.gsub!(/:(?:\w{8})\]/, "]") # Some links look like this: http://www.onegameamonth.com - text.gsub!(/(.+)<\/a>/i, '[\2](\1)') + text.gsub!(%r{(.+)}i, '[\2](\1)') # phpBB shortens link text like this, which breaks our markdown processing: # [http://answers.yahoo.com/question/index ... 223AAkkPli](http://answers.yahoo.com/question/index?qid=20070920134223AAkkPli) # # Work around it for now: - text.gsub!(/\[http(s)?:\/\/(www\.)?/i, '[') + text.gsub!(%r{\[http(s)?://(www\.)?}i, "[") # convert list tags to ul and list=1 tags to ol # list=a is not supported, so handle it like list=1 # list=9 and list=x have the same result as list=1 and list=a - text.gsub!(/\[list\](.*?)\[\/list:u\]/mi, '[ul]\1[/ul]') - text.gsub!(/\[list=.*?\](.*?)\[\/list:o\]/mi, '[ol]\1[/ol]') + text.gsub!(%r{\[list\](.*?)\[/list:u\]}mi, '[ul]\1[/ul]') + text.gsub!(%r{\[list=.*?\](.*?)\[/list:o\]}mi, '[ol]\1[/ol]') # convert *-tags to li-tags so bbcode-to-md can do its magic on phpBB's lists: - text.gsub!(/\[\*\](.*?)\[\/\*:m\]/mi, '[li]\1[/li]') + text.gsub!(%r{\[\*\](.*?)\[/\*:m\]}mi, '[li]\1[/li]') # [QUOTE=""] -- add newline text.gsub!(/(\[quote="[a-zA-Z\d]+"\])/i) { "#{$1}\n" } # [/QUOTE] -- add newline - text.gsub!(/(\[\/quote\])/i) { "\n#{$1}" } + text.gsub!(%r{(\[/quote\])}i) { "\n#{$1}" } # :) is encoded as :) - text.gsub!(/(.*?)/) do + text.gsub!( + /(.*?)/, + ) do smiley = $1 @smiley_map.fetch(smiley) do # upload_smiley(smiley, $2, $3, $4) || smiley_as_text(smiley) @@ -405,33 +433,30 @@ class BulkImport::PhpBB < BulkImport::Base def add_default_smilies { - [':D', ':-D', ':grin:'] => ':smiley:', - [':)', ':-)', ':smile:'] => ':slight_smile:', - [';)', ';-)', ':wink:'] => ':wink:', - [':(', ':-(', ':sad:'] => ':frowning:', - [':o', ':-o', ':eek:'] => ':astonished:', - [':shock:'] => ':open_mouth:', - [':?', ':-?', ':???:'] => ':confused:', - ['8-)', ':cool:'] => ':sunglasses:', - [':lol:'] => ':laughing:', - [':x', ':-x', ':mad:'] => ':angry:', - [':P', ':-P', ':razz:'] => ':stuck_out_tongue:', - [':oops:'] => ':blush:', - [':cry:'] => ':cry:', - [':evil:'] => ':imp:', - [':twisted:'] => ':smiling_imp:', - [':roll:'] => ':unamused:', - [':!:'] => ':exclamation:', - [':?:'] => ':question:', - [':idea:'] => ':bulb:', - [':arrow:'] => ':arrow_right:', - [':|', ':-|'] => ':neutral_face:', - [':geek:'] => ':nerd:' - }.each do |smilies, emoji| - smilies.each { |smiley| @smiley_map[smiley] = emoji } - end + %w[:D :-D :grin:] => ":smiley:", + %w[:) :-) :smile:] => ":slight_smile:", + %w[;) ;-) :wink:] => ":wink:", + %w[:( :-( :sad:] => ":frowning:", + %w[:o :-o :eek:] => ":astonished:", + [":shock:"] => ":open_mouth:", + %w[:? :-? :???:] => ":confused:", + %w[8-) :cool:] => ":sunglasses:", + [":lol:"] => ":laughing:", + %w[:x :-x :mad:] => ":angry:", + %w[:P :-P :razz:] => ":stuck_out_tongue:", + [":oops:"] => ":blush:", + [":cry:"] => ":cry:", + [":evil:"] => ":imp:", + [":twisted:"] => ":smiling_imp:", + [":roll:"] => ":unamused:", + [":!:"] => ":exclamation:", + [":?:"] => ":question:", + [":idea:"] => ":bulb:", + [":arrow:"] => ":arrow_right:", + %w[:| :-|] => ":neutral_face:", + [":geek:"] => ":nerd:", + }.each { |smilies, emoji| smilies.each { |smiley| @smiley_map[smiley] = emoji } } end - end BulkImport::PhpBB.new.run diff --git a/script/bulk_import/vanilla.rb b/script/bulk_import/vanilla.rb index d01eed3af0..827f57c3fd 100644 --- a/script/bulk_import/vanilla.rb +++ b/script/bulk_import/vanilla.rb @@ -8,7 +8,6 @@ require "htmlentities" # NOTE: this importer expects a MySQL DB to directly connect to class BulkImport::Vanilla < BulkImport::Base - VANILLA_DB = "dbname" TABLE_PREFIX = "GDN_" ATTACHMENTS_BASE_DIR = "/my/absolute/path/to/from_vanilla/uploads" @@ -20,13 +19,14 @@ class BulkImport::Vanilla < BulkImport::Base def initialize super @htmlentities = HTMLEntities.new - @client = Mysql2::Client.new( - host: "localhost", - username: "root", - database: VANILLA_DB, - password: "", - reconnect: true - ) + @client = + Mysql2::Client.new( + host: "localhost", + username: "root", + database: VANILLA_DB, + password: "", + reconnect: true, + ) @import_tags = false begin @@ -88,10 +88,10 @@ class BulkImport::Vanilla < BulkImport::Base end def import_users - puts '', "Importing users..." + puts "", "Importing users..." username = nil - total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}User;").first['count'] + total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}User;").first["count"] users = mysql_stream <<-SQL SELECT UserID, Name, Title, Location, Email, @@ -103,26 +103,32 @@ class BulkImport::Vanilla < BulkImport::Base SQL create_users(users) do |row| - next if row['Email'].blank? - next if row['Name'].blank? + next if row["Email"].blank? + next if row["Name"].blank? - if ip_address = row['InsertIPAddress']&.split(',').try(:[], 0) - ip_address = nil unless (IPAddr.new(ip_address) rescue false) + if ip_address = row["InsertIPAddress"]&.split(",").try(:[], 0) + ip_address = nil unless ( + begin + IPAddr.new(ip_address) + rescue StandardError + false + end + ) end u = { - imported_id: row['UserID'], - email: row['Email'], - username: row['Name'], - name: row['Name'], - created_at: row['DateInserted'] == nil ? 0 : Time.zone.at(row['DateInserted']), + imported_id: row["UserID"], + email: row["Email"], + username: row["Name"], + name: row["Name"], + created_at: row["DateInserted"] == nil ? 0 : Time.zone.at(row["DateInserted"]), registration_ip_address: ip_address, - last_seen_at: row['DateLastActive'] == nil ? 0 : Time.zone.at(row['DateLastActive']), - location: row['Location'], - admin: row['Admin'] > 0 + last_seen_at: row["DateLastActive"] == nil ? 0 : Time.zone.at(row["DateLastActive"]), + location: row["Location"], + admin: row["Admin"] > 0, } if row["Banned"] > 0 - u[:suspended_at] = Time.zone.at(row['DateInserted']) + u[:suspended_at] = Time.zone.at(row["DateInserted"]) u[:suspended_till] = SUSPENDED_TILL end u @@ -130,7 +136,7 @@ class BulkImport::Vanilla < BulkImport::Base end def import_user_emails - puts '', 'Importing user emails...' + puts "", "Importing user emails..." users = mysql_stream <<-SQL SELECT UserID, Name, Email, DateInserted @@ -141,20 +147,20 @@ class BulkImport::Vanilla < BulkImport::Base SQL create_user_emails(users) do |row| - next if row['Email'].blank? - next if row['Name'].blank? + next if row["Email"].blank? + next if row["Name"].blank? { imported_id: row["UserID"], imported_user_id: row["UserID"], email: row["Email"], - created_at: Time.zone.at(row["DateInserted"]) + created_at: Time.zone.at(row["DateInserted"]), } end end def import_user_profiles - puts '', 'Importing user profiles...' + puts "", "Importing user profiles..." user_profiles = mysql_stream <<-SQL SELECT UserID, Name, Email, Location, About @@ -165,19 +171,19 @@ class BulkImport::Vanilla < BulkImport::Base SQL create_user_profiles(user_profiles) do |row| - next if row['Email'].blank? - next if row['Name'].blank? + next if row["Email"].blank? + next if row["Name"].blank? { user_id: user_id_from_imported_id(row["UserID"]), location: row["Location"], - bio_raw: row["About"] + bio_raw: row["About"], } end end def import_user_stats - puts '', "Importing user stats..." + puts "", "Importing user stats..." users = mysql_stream <<-SQL SELECT UserID, CountDiscussions, CountComments, DateInserted @@ -190,14 +196,14 @@ class BulkImport::Vanilla < BulkImport::Base now = Time.zone.now create_user_stats(users) do |row| - next unless @users[row['UserID'].to_i] # shouldn't need this but it can be NULL :< + next unless @users[row["UserID"].to_i] # shouldn't need this but it can be NULL :< { - imported_id: row['UserID'], - imported_user_id: row['UserID'], - new_since: Time.zone.at(row['DateInserted'] || now), - post_count: row['CountComments'] || 0, - topic_count: row['CountDiscussions'] || 0 + imported_id: row["UserID"], + imported_user_id: row["UserID"], + new_since: Time.zone.at(row["DateInserted"] || now), + post_count: row["CountComments"] || 0, + topic_count: row["CountDiscussions"] || 0, } end end @@ -215,7 +221,10 @@ class BulkImport::Vanilla < BulkImport::Base next unless u.custom_fields["import_id"] - r = mysql_query("SELECT photo FROM #{TABLE_PREFIX}User WHERE UserID = #{u.custom_fields['import_id']};").first + r = + mysql_query( + "SELECT photo FROM #{TABLE_PREFIX}User WHERE UserID = #{u.custom_fields["import_id"]};", + ).first next if r.nil? photo = r["photo"] next unless photo.present? @@ -229,9 +238,9 @@ class BulkImport::Vanilla < BulkImport::Base photo_real_filename = nil parts = photo.squeeze("/").split("/") if parts[0] =~ /^[a-z0-9]{2}:/ - photo_path = "#{ATTACHMENTS_BASE_DIR}/#{parts[2..-2].join('/')}".squeeze("/") + photo_path = "#{ATTACHMENTS_BASE_DIR}/#{parts[2..-2].join("/")}".squeeze("/") elsif parts[0] == "~cf" - photo_path = "#{ATTACHMENTS_BASE_DIR}/#{parts[1..-2].join('/')}".squeeze("/") + photo_path = "#{ATTACHMENTS_BASE_DIR}/#{parts[1..-2].join("/")}".squeeze("/") else puts "UNKNOWN FORMAT: #{photo}" next @@ -272,75 +281,86 @@ class BulkImport::Vanilla < BulkImport::Base count = 0 # https://us.v-cdn.net/1234567/uploads/editor/xyz/image.jpg - cdn_regex = /https:\/\/us.v-cdn.net\/1234567\/uploads\/(\S+\/(\w|-)+.\w+)/i + cdn_regex = %r{https://us.v-cdn.net/1234567/uploads/(\S+/(\w|-)+.\w+)}i # [attachment=10109:Screen Shot 2012-04-01 at 3.47.35 AM.png] attachment_regex = /\[attachment=(\d+):(.*?)\]/i - Post.where("raw LIKE '%/us.v-cdn.net/%' OR raw LIKE '%[attachment%'").find_each do |post| - count += 1 - print "\r%7d - %6d/sec" % [count, count.to_f / (Time.now - start)] - new_raw = post.raw.dup + Post + .where("raw LIKE '%/us.v-cdn.net/%' OR raw LIKE '%[attachment%'") + .find_each do |post| + count += 1 + print "\r%7d - %6d/sec" % [count, count.to_f / (Time.now - start)] + new_raw = post.raw.dup - new_raw.gsub!(attachment_regex) do |s| - matches = attachment_regex.match(s) - attachment_id = matches[1] - file_name = matches[2] - next unless attachment_id + new_raw.gsub!(attachment_regex) do |s| + matches = attachment_regex.match(s) + attachment_id = matches[1] + file_name = matches[2] + next unless attachment_id - r = mysql_query("SELECT Path, Name FROM #{TABLE_PREFIX}Media WHERE MediaID = #{attachment_id};").first - next if r.nil? - path = r["Path"] - name = r["Name"] - next unless path.present? + r = + mysql_query( + "SELECT Path, Name FROM #{TABLE_PREFIX}Media WHERE MediaID = #{attachment_id};", + ).first + next if r.nil? + path = r["Path"] + name = r["Name"] + next unless path.present? - path.gsub!("s3://content/", "") - path.gsub!("s3://uploads/", "") - file_path = "#{ATTACHMENTS_BASE_DIR}/#{path}" + path.gsub!("s3://content/", "") + path.gsub!("s3://uploads/", "") + file_path = "#{ATTACHMENTS_BASE_DIR}/#{path}" - if File.exist?(file_path) - upload = create_upload(post.user.id, file_path, File.basename(file_path)) - if upload && upload.errors.empty? - # upload.url - filename = name || file_name || File.basename(file_path) - html_for_upload(upload, normalize_text(filename)) + if File.exist?(file_path) + upload = create_upload(post.user.id, file_path, File.basename(file_path)) + if upload && upload.errors.empty? + # upload.url + filename = name || file_name || File.basename(file_path) + html_for_upload(upload, normalize_text(filename)) + else + puts "Error: Upload did not persist for #{post.id} #{attachment_id}!" + end else - puts "Error: Upload did not persist for #{post.id} #{attachment_id}!" + puts "Couldn't find file for #{attachment_id}. Skipping." + next end - else - puts "Couldn't find file for #{attachment_id}. Skipping." - next end - end - new_raw.gsub!(cdn_regex) do |s| - matches = cdn_regex.match(s) - attachment_id = matches[1] + new_raw.gsub!(cdn_regex) do |s| + matches = cdn_regex.match(s) + attachment_id = matches[1] - file_path = "#{ATTACHMENTS_BASE_DIR}/#{attachment_id}" + file_path = "#{ATTACHMENTS_BASE_DIR}/#{attachment_id}" - if File.exist?(file_path) - upload = create_upload(post.user.id, file_path, File.basename(file_path)) - if upload && upload.errors.empty? - upload.url + if File.exist?(file_path) + upload = create_upload(post.user.id, file_path, File.basename(file_path)) + if upload && upload.errors.empty? + upload.url + else + puts "Error: Upload did not persist for #{post.id} #{attachment_id}!" + end else - puts "Error: Upload did not persist for #{post.id} #{attachment_id}!" + puts "Couldn't find file for #{attachment_id}. Skipping." + next end - else - puts "Couldn't find file for #{attachment_id}. Skipping." - next end - end - if new_raw != post.raw - begin - PostRevisor.new(post).revise!(post.user, { raw: new_raw }, skip_revision: true, skip_validations: true, bypass_bump: true) - rescue - puts "PostRevisor error for #{post.id}" - post.raw = new_raw - post.save(validate: false) + if new_raw != post.raw + begin + PostRevisor.new(post).revise!( + post.user, + { raw: new_raw }, + skip_revision: true, + skip_validations: true, + bypass_bump: true, + ) + rescue StandardError + puts "PostRevisor error for #{post.id}" + post.raw = new_raw + post.save(validate: false) + end end end - end end end @@ -352,7 +372,7 @@ class BulkImport::Vanilla < BulkImport::Base # Otherwise, the file exists but with a prefix: # The p prefix seems to be the full file, so try to find that one first. - ['p', 't', 'n'].each do |prefix| + %w[p t n].each do |prefix| full_guess = File.join(path, "#{prefix}#{base_guess}") return full_guess if File.exist?(full_guess) end @@ -364,26 +384,30 @@ class BulkImport::Vanilla < BulkImport::Base def import_categories puts "", "Importing categories..." - categories = mysql_query(" + categories = + mysql_query( + " SELECT CategoryID, ParentCategoryID, Name, Description, Sort FROM #{TABLE_PREFIX}Category WHERE CategoryID > 0 ORDER BY Sort, CategoryID - ").to_a + ", + ).to_a # Throw the -1 level categories away since they contain no topics. # Use the next level as root categories. - top_level_categories = categories.select { |c| c["ParentCategoryID"].blank? || c['ParentCategoryID'] == -1 } + top_level_categories = + categories.select { |c| c["ParentCategoryID"].blank? || c["ParentCategoryID"] == -1 } # Depth = 2 create_categories(top_level_categories) do |category| - next if category_id_from_imported_id(category['CategoryID']) + next if category_id_from_imported_id(category["CategoryID"]) { - imported_id: category['CategoryID'], - name: CGI.unescapeHTML(category['Name']), - description: category['Description'] ? CGI.unescapeHTML(category['Description']) : nil, - position: category['Sort'] + imported_id: category["CategoryID"], + name: CGI.unescapeHTML(category["Name"]), + description: category["Description"] ? CGI.unescapeHTML(category["Description"]) : nil, + position: category["Sort"], } end @@ -393,39 +417,39 @@ class BulkImport::Vanilla < BulkImport::Base # Depth = 3 create_categories(subcategories) do |category| - next if category_id_from_imported_id(category['CategoryID']) + next if category_id_from_imported_id(category["CategoryID"]) { - imported_id: category['CategoryID'], - parent_category_id: category_id_from_imported_id(category['ParentCategoryID']), - name: CGI.unescapeHTML(category['Name']), - description: category['Description'] ? CGI.unescapeHTML(category['Description']) : nil, - position: category['Sort'] + imported_id: category["CategoryID"], + parent_category_id: category_id_from_imported_id(category["ParentCategoryID"]), + name: CGI.unescapeHTML(category["Name"]), + description: category["Description"] ? CGI.unescapeHTML(category["Description"]) : nil, + position: category["Sort"], } end - subcategory_ids = Set.new(subcategories.map { |c| c['CategoryID'] }) + subcategory_ids = Set.new(subcategories.map { |c| c["CategoryID"] }) # Depth 4 and 5 need to be tags categories.each do |c| - next if c['ParentCategoryID'] == -1 - next if top_level_category_ids.include?(c['CategoryID']) - next if subcategory_ids.include?(c['CategoryID']) + next if c["ParentCategoryID"] == -1 + next if top_level_category_ids.include?(c["CategoryID"]) + next if subcategory_ids.include?(c["CategoryID"]) # Find a depth 3 category for topics in this category parent = c - while !parent.nil? && !subcategory_ids.include?(parent['CategoryID']) - parent = categories.find { |subcat| subcat['CategoryID'] == parent['ParentCategoryID'] } + while !parent.nil? && !subcategory_ids.include?(parent["CategoryID"]) + parent = categories.find { |subcat| subcat["CategoryID"] == parent["ParentCategoryID"] } end if parent - tag_name = DiscourseTagging.clean_tag(c['Name']) - @category_mappings[c['CategoryID']] = { - category_id: category_id_from_imported_id(parent['CategoryID']), - tag: Tag.find_by_name(tag_name) || Tag.create(name: tag_name) + tag_name = DiscourseTagging.clean_tag(c["Name"]) + @category_mappings[c["CategoryID"]] = { + category_id: category_id_from_imported_id(parent["CategoryID"]), + tag: Tag.find_by_name(tag_name) || Tag.create(name: tag_name), } else - puts '', "Couldn't find a category for #{c['CategoryID']} '#{c['Name']}'!" + puts "", "Couldn't find a category for #{c["CategoryID"]} '#{c["Name"]}'!" end end end @@ -433,7 +457,8 @@ class BulkImport::Vanilla < BulkImport::Base def import_topics puts "", "Importing topics..." - topics_sql = "SELECT DiscussionID, CategoryID, Name, Body, DateInserted, InsertUserID, Announce, Format + topics_sql = + "SELECT DiscussionID, CategoryID, Name, Body, DateInserted, InsertUserID, Announce, Format FROM #{TABLE_PREFIX}Discussion WHERE DiscussionID > #{@last_imported_topic_id} ORDER BY DiscussionID ASC" @@ -442,11 +467,12 @@ class BulkImport::Vanilla < BulkImport::Base data = { imported_id: row["DiscussionID"], title: normalize_text(row["Name"]), - category_id: category_id_from_imported_id(row["CategoryID"]) || - @category_mappings[row["CategoryID"]].try(:[], :category_id), + category_id: + category_id_from_imported_id(row["CategoryID"]) || + @category_mappings[row["CategoryID"]].try(:[], :category_id), user_id: user_id_from_imported_id(row["InsertUserID"]), - created_at: Time.zone.at(row['DateInserted']), - pinned_at: row['Announce'] == 0 ? nil : Time.zone.at(row['DateInserted']) + created_at: Time.zone.at(row["DateInserted"]), + pinned_at: row["Announce"] == 0 ? nil : Time.zone.at(row["DateInserted"]), } (data[:user_id].present? && data[:title].present?) ? data : false end @@ -455,46 +481,45 @@ class BulkImport::Vanilla < BulkImport::Base create_posts(mysql_stream(topics_sql)) do |row| data = { - imported_id: "d-" + row['DiscussionID'].to_s, - topic_id: topic_id_from_imported_id(row['DiscussionID']), + imported_id: "d-" + row["DiscussionID"].to_s, + topic_id: topic_id_from_imported_id(row["DiscussionID"]), user_id: user_id_from_imported_id(row["InsertUserID"]), - created_at: Time.zone.at(row['DateInserted']), - raw: clean_up(row['Body'], row['Format']) + created_at: Time.zone.at(row["DateInserted"]), + raw: clean_up(row["Body"], row["Format"]), } data[:topic_id].present? ? data : false end - puts '', 'converting deep categories to tags...' + puts "", "converting deep categories to tags..." create_topic_tags(mysql_stream(topics_sql)) do |row| - next unless mapping = @category_mappings[row['CategoryID']] + next unless mapping = @category_mappings[row["CategoryID"]] - { - tag_id: mapping[:tag].id, - topic_id: topic_id_from_imported_id(row["DiscussionID"]) - } + { tag_id: mapping[:tag].id, topic_id: topic_id_from_imported_id(row["DiscussionID"]) } end end def import_posts puts "", "Importing posts..." - posts = mysql_stream( - "SELECT CommentID, DiscussionID, Body, DateInserted, InsertUserID, Format + posts = + mysql_stream( + "SELECT CommentID, DiscussionID, Body, DateInserted, InsertUserID, Format FROM #{TABLE_PREFIX}Comment WHERE CommentID > #{@last_imported_post_id} - ORDER BY CommentID ASC") + ORDER BY CommentID ASC", + ) create_posts(posts) do |row| - next unless topic_id = topic_id_from_imported_id(row['DiscussionID']) - next if row['Body'].blank? + next unless topic_id = topic_id_from_imported_id(row["DiscussionID"]) + next if row["Body"].blank? { - imported_id: row['CommentID'], + imported_id: row["CommentID"], topic_id: topic_id, - user_id: user_id_from_imported_id(row['InsertUserID']), - created_at: Time.zone.at(row['DateInserted']), - raw: clean_up(row['Body'], row['Format']) + user_id: user_id_from_imported_id(row["InsertUserID"]), + created_at: Time.zone.at(row["DateInserted"]), + raw: clean_up(row["Body"], row["Format"]), } end end @@ -505,31 +530,31 @@ class BulkImport::Vanilla < BulkImport::Base tag_mapping = {} mysql_query("SELECT TagID, Name FROM #{TABLE_PREFIX}Tag").each do |row| - tag_name = DiscourseTagging.clean_tag(row['Name']) + tag_name = DiscourseTagging.clean_tag(row["Name"]) tag = Tag.find_by_name(tag_name) || Tag.create(name: tag_name) - tag_mapping[row['TagID']] = tag.id + tag_mapping[row["TagID"]] = tag.id end - tags = mysql_query( - "SELECT TagID, DiscussionID + tags = + mysql_query( + "SELECT TagID, DiscussionID FROM #{TABLE_PREFIX}TagDiscussion WHERE DiscussionID > #{@last_imported_topic_id} - ORDER BY DateInserted") + ORDER BY DateInserted", + ) create_topic_tags(tags) do |row| - next unless topic_id = topic_id_from_imported_id(row['DiscussionID']) + next unless topic_id = topic_id_from_imported_id(row["DiscussionID"]) - { - topic_id: topic_id, - tag_id: tag_mapping[row['TagID']] - } + { topic_id: topic_id, tag_id: tag_mapping[row["TagID"]] } end end def import_private_topics puts "", "Importing private topics..." - topics_sql = "SELECT c.ConversationID, c.Subject, m.MessageID, m.Body, c.DateInserted, c.InsertUserID + topics_sql = + "SELECT c.ConversationID, c.Subject, m.MessageID, m.Body, c.DateInserted, c.InsertUserID FROM #{TABLE_PREFIX}Conversation c, #{TABLE_PREFIX}ConversationMessage m WHERE c.FirstMessageID = m.MessageID AND c.ConversationID > #{@last_imported_private_topic_id - PRIVATE_OFFSET} @@ -539,9 +564,10 @@ class BulkImport::Vanilla < BulkImport::Base { archetype: Archetype.private_message, imported_id: row["ConversationID"] + PRIVATE_OFFSET, - title: row["Subject"] ? normalize_text(row["Subject"]) : "Conversation #{row["ConversationID"]}", + title: + row["Subject"] ? normalize_text(row["Subject"]) : "Conversation #{row["ConversationID"]}", user_id: user_id_from_imported_id(row["InsertUserID"]), - created_at: Time.zone.at(row['DateInserted']) + created_at: Time.zone.at(row["DateInserted"]), } end end @@ -549,7 +575,8 @@ class BulkImport::Vanilla < BulkImport::Base def import_topic_allowed_users puts "", "importing topic_allowed_users..." - topic_allowed_users_sql = " + topic_allowed_users_sql = + " SELECT ConversationID, UserID FROM #{TABLE_PREFIX}UserConversation WHERE Deleted = 0 @@ -559,45 +586,43 @@ class BulkImport::Vanilla < BulkImport::Base added = 0 create_topic_allowed_users(mysql_stream(topic_allowed_users_sql)) do |row| - next unless topic_id = topic_id_from_imported_id(row['ConversationID'] + PRIVATE_OFFSET) + next unless topic_id = topic_id_from_imported_id(row["ConversationID"] + PRIVATE_OFFSET) next unless user_id = user_id_from_imported_id(row["UserID"]) added += 1 - { - topic_id: topic_id, - user_id: user_id, - } + { topic_id: topic_id, user_id: user_id } end - puts '', "Added #{added} topic_allowed_users records." + puts "", "Added #{added} topic_allowed_users records." end def import_private_posts puts "", "importing private replies..." - private_posts_sql = " + private_posts_sql = + " SELECT ConversationID, MessageID, Body, InsertUserID, DateInserted, Format FROM GDN_ConversationMessage WHERE ConversationID > #{@last_imported_private_topic_id - PRIVATE_OFFSET} ORDER BY ConversationID ASC, MessageID ASC" create_posts(mysql_stream(private_posts_sql)) do |row| - next unless topic_id = topic_id_from_imported_id(row['ConversationID'] + PRIVATE_OFFSET) + next unless topic_id = topic_id_from_imported_id(row["ConversationID"] + PRIVATE_OFFSET) { - imported_id: row['MessageID'] + PRIVATE_OFFSET, + imported_id: row["MessageID"] + PRIVATE_OFFSET, topic_id: topic_id, - user_id: user_id_from_imported_id(row['InsertUserID']), - created_at: Time.zone.at(row['DateInserted']), - raw: clean_up(row['Body'], row['Format']) + user_id: user_id_from_imported_id(row["InsertUserID"]), + created_at: Time.zone.at(row["DateInserted"]), + raw: clean_up(row["Body"], row["Format"]), } end end # TODO: too slow def create_permalinks - puts '', 'Creating permalinks...', '' + puts "", "Creating permalinks...", "" - puts ' User pages...' + puts " User pages..." start = Time.now count = 0 @@ -606,21 +631,23 @@ class BulkImport::Vanilla < BulkImport::Base sql = "COPY permalinks (url, created_at, updated_at, external_url) FROM STDIN" @raw_connection.copy_data(sql, @encoder) do - User.includes(:_custom_fields).find_each do |u| - count += 1 - ucf = u.custom_fields - if ucf && ucf["import_id"] - vanilla_username = ucf["import_username"] || u.username - @raw_connection.put_copy_data( - ["profile/#{vanilla_username}", now, now, "/users/#{u.username}"] - ) - end + User + .includes(:_custom_fields) + .find_each do |u| + count += 1 + ucf = u.custom_fields + if ucf && ucf["import_id"] + vanilla_username = ucf["import_username"] || u.username + @raw_connection.put_copy_data( + ["profile/#{vanilla_username}", now, now, "/users/#{u.username}"], + ) + end - print "\r%7d - %6d/sec" % [count, count.to_f / (Time.now - start)] if count % 5000 == 0 - end + print "\r%7d - %6d/sec" % [count, count.to_f / (Time.now - start)] if count % 5000 == 0 + end end - puts '', '', ' Topics and posts...' + puts "", "", " Topics and posts..." start = Time.now count = 0 @@ -628,38 +655,36 @@ class BulkImport::Vanilla < BulkImport::Base sql = "COPY permalinks (url, topic_id, post_id, created_at, updated_at) FROM STDIN" @raw_connection.copy_data(sql, @encoder) do - Post.includes(:_custom_fields).find_each do |post| - count += 1 - pcf = post.custom_fields - if pcf && pcf["import_id"] - topic = post.topic - if topic.present? - id = pcf["import_id"].split('-').last - if post.post_number == 1 - slug = Slug.for(topic.title) # probably matches what vanilla would do... - @raw_connection.put_copy_data( - ["discussion/#{id}/#{slug}", topic.id, nil, now, now] - ) - else - @raw_connection.put_copy_data( - ["discussion/comment/#{id}", nil, post.id, now, now] - ) + Post + .includes(:_custom_fields) + .find_each do |post| + count += 1 + pcf = post.custom_fields + if pcf && pcf["import_id"] + topic = post.topic + if topic.present? + id = pcf["import_id"].split("-").last + if post.post_number == 1 + slug = Slug.for(topic.title) # probably matches what vanilla would do... + @raw_connection.put_copy_data(["discussion/#{id}/#{slug}", topic.id, nil, now, now]) + else + @raw_connection.put_copy_data(["discussion/comment/#{id}", nil, post.id, now, now]) + end end end - end - print "\r%7d - %6d/sec" % [count, count.to_f / (Time.now - start)] if count % 5000 == 0 - end + print "\r%7d - %6d/sec" % [count, count.to_f / (Time.now - start)] if count % 5000 == 0 + end end end def clean_up(raw, format) raw.encode!("utf-8", "utf-8", invalid: :replace, undef: :replace, replace: "") - raw.gsub!(/<(.+)> <\/\1>/, "\n\n") + raw.gsub!(%r{<(.+)> }, "\n\n") html = - if format == 'Html' + if format == "Html" raw else markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true) @@ -668,29 +693,23 @@ class BulkImport::Vanilla < BulkImport::Base doc = Nokogiri::HTML5.fragment(html) - doc.css("blockquote").each do |bq| - name = bq["rel"] - user = User.find_by(name: name) - bq.replace %{
[QUOTE="#{user&.username || name}"]\n#{bq.inner_html}\n[/QUOTE]
} - end + doc + .css("blockquote") + .each do |bq| + name = bq["rel"] + user = User.find_by(name: name) + bq.replace %{
[QUOTE="#{user&.username || name}"]\n#{bq.inner_html}\n[/QUOTE]
} + end - doc.css("font").reverse.each do |f| - f.replace f.inner_html - end + doc.css("font").reverse.each { |f| f.replace f.inner_html } - doc.css("span").reverse.each do |f| - f.replace f.inner_html - end + doc.css("span").reverse.each { |f| f.replace f.inner_html } - doc.css("sub").reverse.each do |f| - f.replace f.inner_html - end + doc.css("sub").reverse.each { |f| f.replace f.inner_html } - doc.css("u").reverse.each do |f| - f.replace f.inner_html - end + doc.css("u").reverse.each { |f| f.replace f.inner_html } - markdown = format == 'Html' ? ReverseMarkdown.convert(doc.to_html) : doc.to_html + markdown = format == "Html" ? ReverseMarkdown.convert(doc.to_html) : doc.to_html markdown.gsub!(/\[QUOTE="([^;]+);c-(\d+)"\]/i) { "[QUOTE=#{$1};#{$2}]" } markdown = process_raw_text(markdown) @@ -702,31 +721,31 @@ class BulkImport::Vanilla < BulkImport::Base text = raw.dup text = CGI.unescapeHTML(text) - text.gsub!(/:(?:\w{8})\]/, ']') + text.gsub!(/:(?:\w{8})\]/, "]") # Some links look like this: http://www.onegameamonth.com - text.gsub!(/(.+)<\/a>/i, '[\2](\1)') + text.gsub!(%r{(.+)}i, '[\2](\1)') # phpBB shortens link text like this, which breaks our markdown processing: # [http://answers.yahoo.com/question/index ... 223AAkkPli](http://answers.yahoo.com/question/index?qid=20070920134223AAkkPli) # # Work around it for now: - text.gsub!(/\[http(s)?:\/\/(www\.)?/i, '[') + text.gsub!(%r{\[http(s)?://(www\.)?}i, "[") # convert list tags to ul and list=1 tags to ol # list=a is not supported, so handle it like list=1 # list=9 and list=x have the same result as list=1 and list=a - text.gsub!(/\[list\](.*?)\[\/list:u\]/mi, '[ul]\1[/ul]') - text.gsub!(/\[list=.*?\](.*?)\[\/list:o\]/mi, '[ol]\1[/ol]') + text.gsub!(%r{\[list\](.*?)\[/list:u\]}mi, '[ul]\1[/ul]') + text.gsub!(%r{\[list=.*?\](.*?)\[/list:o\]}mi, '[ol]\1[/ol]') # convert *-tags to li-tags so bbcode-to-md can do its magic on phpBB's lists: - text.gsub!(/\[\*\](.*?)\[\/\*:m\]/mi, '[li]\1[/li]') + text.gsub!(%r{\[\*\](.*?)\[/\*:m\]}mi, '[li]\1[/li]') # [QUOTE=""] -- add newline text.gsub!(/(\[quote="[a-zA-Z\d]+"\])/i) { "#{$1}\n" } # [/QUOTE] -- add newline - text.gsub!(/(\[\/quote\])/i) { "\n#{$1}" } + text.gsub!(%r{(\[/quote\])}i) { "\n#{$1}" } text end @@ -742,7 +761,6 @@ class BulkImport::Vanilla < BulkImport::Base def mysql_query(sql) @client.query(sql) end - end BulkImport::Vanilla.new.start diff --git a/script/bulk_import/vbulletin.rb b/script/bulk_import/vbulletin.rb index fde0fbe7d7..b836338a51 100644 --- a/script/bulk_import/vbulletin.rb +++ b/script/bulk_import/vbulletin.rb @@ -7,43 +7,42 @@ require "htmlentities" require "parallel" class BulkImport::VBulletin < BulkImport::Base - - TABLE_PREFIX ||= ENV['TABLE_PREFIX'] || "vb_" + TABLE_PREFIX ||= ENV["TABLE_PREFIX"] || "vb_" SUSPENDED_TILL ||= Date.new(3000, 1, 1) - ATTACHMENT_DIR ||= ENV['ATTACHMENT_DIR'] || '/shared/import/data/attachments' - AVATAR_DIR ||= ENV['AVATAR_DIR'] || '/shared/import/data/customavatars' + ATTACHMENT_DIR ||= ENV["ATTACHMENT_DIR"] || "/shared/import/data/attachments" + AVATAR_DIR ||= ENV["AVATAR_DIR"] || "/shared/import/data/customavatars" def initialize super - host = ENV["DB_HOST"] || "localhost" + host = ENV["DB_HOST"] || "localhost" username = ENV["DB_USERNAME"] || "root" password = ENV["DB_PASSWORD"] database = ENV["DB_NAME"] || "vbulletin" - charset = ENV["DB_CHARSET"] || "utf8" + charset = ENV["DB_CHARSET"] || "utf8" @html_entities = HTMLEntities.new @encoding = CHARSET_MAP[charset] - @client = Mysql2::Client.new( - host: host, - username: username, - password: password, - database: database, - encoding: charset, - reconnect: true - ) + @client = + Mysql2::Client.new( + host: host, + username: username, + password: password, + database: database, + encoding: charset, + reconnect: true, + ) @client.query_options.merge!(as: :array, cache_rows: false) - @has_post_thanks = mysql_query(<<-SQL + @has_post_thanks = mysql_query(<<-SQL).to_a.count > 0 SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA`='#{database}' AND `TABLE_NAME`='user' AND `COLUMN_NAME` LIKE 'post_thanks_%' SQL - ).to_a.count > 0 @user_ids_by_email = {} end @@ -95,7 +94,7 @@ class BulkImport::VBulletin < BulkImport::Base end def import_groups - puts '', "Importing groups..." + puts "", "Importing groups..." groups = mysql_stream <<-SQL SELECT usergroupid, title, description, usertitle @@ -115,7 +114,7 @@ class BulkImport::VBulletin < BulkImport::Base end def import_users - puts '', "Importing users..." + puts "", "Importing users..." users = mysql_stream <<-SQL SELECT u.userid, username, email, joindate, birthday, ipaddress, u.usergroupid, bandate, liftdate @@ -145,7 +144,7 @@ class BulkImport::VBulletin < BulkImport::Base end def import_user_emails - puts '', "Importing user emails..." + puts "", "Importing user emails..." users = mysql_stream <<-SQL SELECT u.userid, email, joindate @@ -155,7 +154,7 @@ class BulkImport::VBulletin < BulkImport::Base SQL create_user_emails(users) do |row| - user_id, email = row[0 .. 1] + user_id, email = row[0..1] @user_ids_by_email[email.downcase] ||= [] user_ids = @user_ids_by_email[email.downcase] << user_id @@ -170,7 +169,7 @@ class BulkImport::VBulletin < BulkImport::Base imported_id: user_id, imported_user_id: user_id, email: email, - created_at: Time.zone.at(row[2]) + created_at: Time.zone.at(row[2]), } end @@ -179,7 +178,7 @@ class BulkImport::VBulletin < BulkImport::Base end def import_user_stats - puts '', "Importing user stats..." + puts "", "Importing user stats..." users = mysql_stream <<-SQL SELECT u.userid, joindate, posts, COUNT(t.threadid) AS threads, p.dateline @@ -199,7 +198,7 @@ class BulkImport::VBulletin < BulkImport::Base new_since: Time.zone.at(row[1]), post_count: row[2], topic_count: row[3], - first_post_created_at: row[4] && Time.zone.at(row[4]) + first_post_created_at: row[4] && Time.zone.at(row[4]), } if @has_post_thanks @@ -212,7 +211,7 @@ class BulkImport::VBulletin < BulkImport::Base end def import_group_users - puts '', "Importing group users..." + puts "", "Importing group users..." group_users = mysql_stream <<-SQL SELECT usergroupid, userid @@ -221,15 +220,12 @@ class BulkImport::VBulletin < BulkImport::Base SQL create_group_users(group_users) do |row| - { - group_id: group_id_from_imported_id(row[0]), - user_id: user_id_from_imported_id(row[1]), - } + { group_id: group_id_from_imported_id(row[0]), user_id: user_id_from_imported_id(row[1]) } end end def import_user_passwords - puts '', "Importing user passwords..." + puts "", "Importing user passwords..." user_passwords = mysql_stream <<-SQL SELECT userid, password @@ -239,15 +235,12 @@ class BulkImport::VBulletin < BulkImport::Base SQL create_custom_fields("user", "password", user_passwords) do |row| - { - record_id: user_id_from_imported_id(row[0]), - value: row[1], - } + { record_id: user_id_from_imported_id(row[0]), value: row[1] } end end def import_user_salts - puts '', "Importing user salts..." + puts "", "Importing user salts..." user_salts = mysql_stream <<-SQL SELECT userid, salt @@ -258,15 +251,12 @@ class BulkImport::VBulletin < BulkImport::Base SQL create_custom_fields("user", "salt", user_salts) do |row| - { - record_id: user_id_from_imported_id(row[0]), - value: row[1], - } + { record_id: user_id_from_imported_id(row[0]), value: row[1] } end end def import_user_profiles - puts '', "Importing user profiles..." + puts "", "Importing user profiles..." user_profiles = mysql_stream <<-SQL SELECT userid, homepage, profilevisits @@ -278,16 +268,23 @@ class BulkImport::VBulletin < BulkImport::Base create_user_profiles(user_profiles) do |row| { user_id: user_id_from_imported_id(row[0]), - website: (URI.parse(row[1]).to_s rescue nil), + website: + ( + begin + URI.parse(row[1]).to_s + rescue StandardError + nil + end + ), views: row[2], } end end def import_categories - puts '', "Importing categories..." + puts "", "Importing categories..." - categories = mysql_query(<<-SQL + categories = mysql_query(<<-SQL).to_a select forumid, parentid, @@ -311,23 +308,20 @@ class BulkImport::VBulletin < BulkImport::Base from forum order by forumid SQL - ).to_a return if categories.empty? - parent_categories = categories.select { |c| c[1] == -1 } + parent_categories = categories.select { |c| c[1] == -1 } children_categories = categories.select { |c| c[1] != -1 } parent_category_ids = Set.new parent_categories.map { |c| c[0] } # cut down the tree to only 2 levels of categories children_categories.each do |cc| - until parent_category_ids.include?(cc[1]) - cc[1] = categories.find { |c| c[0] == cc[1] }[1] - end + cc[1] = categories.find { |c| c[0] == cc[1] }[1] until parent_category_ids.include?(cc[1]) end - puts '', "Importing parent categories..." + puts "", "Importing parent categories..." create_categories(parent_categories) do |row| { imported_id: row[0], @@ -337,7 +331,7 @@ class BulkImport::VBulletin < BulkImport::Base } end - puts '', "Importing children categories..." + puts "", "Importing children categories..." create_categories(children_categories) do |row| { imported_id: row[0], @@ -350,7 +344,7 @@ class BulkImport::VBulletin < BulkImport::Base end def import_topics - puts '', "Importing topics..." + puts "", "Importing topics..." topics = mysql_stream <<-SQL SELECT threadid, title, forumid, postuserid, open, dateline, views, visible, sticky @@ -381,7 +375,7 @@ class BulkImport::VBulletin < BulkImport::Base end def import_posts - puts '', "Importing posts..." + puts "", "Importing posts..." posts = mysql_stream <<-SQL SELECT postid, p.threadid, parentid, userid, p.dateline, p.visible, pagetext @@ -396,7 +390,8 @@ class BulkImport::VBulletin < BulkImport::Base create_posts(posts) do |row| topic_id = topic_id_from_imported_id(row[1]) replied_post_topic_id = topic_id_from_imported_post_id(row[2]) - reply_to_post_number = topic_id == replied_post_topic_id ? post_number_from_imported_id(row[2]) : nil + reply_to_post_number = + topic_id == replied_post_topic_id ? post_number_from_imported_id(row[2]) : nil post = { imported_id: row[0], @@ -415,7 +410,7 @@ class BulkImport::VBulletin < BulkImport::Base def import_likes return unless @has_post_thanks - puts '', "Importing likes..." + puts "", "Importing likes..." @imported_likes = Set.new @last_imported_post_id = 0 @@ -438,13 +433,13 @@ class BulkImport::VBulletin < BulkImport::Base post_id: post_id_from_imported_id(row[0]), user_id: user_id_from_imported_id(row[1]), post_action_type_id: 2, - created_at: Time.zone.at(row[2]) + created_at: Time.zone.at(row[2]), } end end def import_private_topics - puts '', "Importing private topics..." + puts "", "Importing private topics..." @imported_topics = {} @@ -473,34 +468,31 @@ class BulkImport::VBulletin < BulkImport::Base end def import_topic_allowed_users - puts '', "Importing topic allowed users..." + puts "", "Importing topic allowed users..." allowed_users = Set.new - mysql_stream(<<-SQL + mysql_stream(<<-SQL).each do |row| SELECT pmtextid, touserarray FROM #{TABLE_PREFIX}pmtext WHERE pmtextid > (#{@last_imported_private_topic_id - PRIVATE_OFFSET}) ORDER BY pmtextid SQL - ).each do |row| next unless topic_id = topic_id_from_imported_id(row[0] + PRIVATE_OFFSET) - row[1].scan(/i:(\d+)/).flatten.each do |id| - next unless user_id = user_id_from_imported_id(id) - allowed_users << [topic_id, user_id] - end + row[1] + .scan(/i:(\d+)/) + .flatten + .each do |id| + next unless user_id = user_id_from_imported_id(id) + allowed_users << [topic_id, user_id] + end end - create_topic_allowed_users(allowed_users) do |row| - { - topic_id: row[0], - user_id: row[1], - } - end + create_topic_allowed_users(allowed_users) { |row| { topic_id: row[0], user_id: row[1] } } end def import_private_posts - puts '', "Importing private posts..." + puts "", "Importing private posts..." posts = mysql_stream <<-SQL SELECT pmtextid, title, fromuserid, touserarray, dateline, message @@ -527,7 +519,7 @@ class BulkImport::VBulletin < BulkImport::Base end def create_permalink_file - puts '', 'Creating Permalink File...', '' + puts "", "Creating Permalink File...", "" total = Topic.listable_topics.count start = Time.now @@ -538,9 +530,9 @@ class BulkImport::VBulletin < BulkImport::Base i += 1 pcf = topic.posts.includes(:_custom_fields).where(post_number: 1).first.custom_fields if pcf && pcf["import_id"] - id = pcf["import_id"].split('-').last + id = pcf["import_id"].split("-").last - f.print [ "XXX#{id} YYY#{topic.id}" ].to_csv + f.print ["XXX#{id} YYY#{topic.id}"].to_csv print "\r%7d/%7d - %6d/sec" % [i, total, i.to_f / (Time.now - start)] if i % 5000 == 0 end end @@ -549,7 +541,8 @@ class BulkImport::VBulletin < BulkImport::Base # find the uploaded file information from the db def find_upload(post, attachment_id) - sql = "SELECT a.attachmentid attachment_id, a.userid user_id, a.filename filename + sql = + "SELECT a.attachmentid attachment_id, a.userid user_id, a.filename filename FROM #{TABLE_PREFIX}attachment a WHERE a.attachmentid = #{attachment_id}" results = mysql_query(sql) @@ -563,9 +556,10 @@ class BulkImport::VBulletin < BulkImport::Base user_id = row[1] db_filename = row[2] - filename = File.join(ATTACHMENT_DIR, user_id.to_s.split('').join('/'), "#{attachment_id}.attach") + filename = + File.join(ATTACHMENT_DIR, user_id.to_s.split("").join("/"), "#{attachment_id}.attach") real_filename = db_filename - real_filename.prepend SecureRandom.hex if real_filename[0] == '.' + real_filename.prepend SecureRandom.hex if real_filename[0] == "." unless File.exist?(filename) puts "Attachment file #{row.inspect} doesn't exist" @@ -588,7 +582,7 @@ class BulkImport::VBulletin < BulkImport::Base end def import_attachments - puts '', 'importing attachments...' + puts "", "importing attachments..." RateLimiter.disable current_count = 0 @@ -596,7 +590,7 @@ class BulkImport::VBulletin < BulkImport::Base success_count = 0 fail_count = 0 - attachment_regex = /\[attach[^\]]*\](\d+)\[\/attach\]/i + attachment_regex = %r{\[attach[^\]]*\](\d+)\[/attach\]}i Post.find_each do |post| current_count += 1 @@ -618,7 +612,12 @@ class BulkImport::VBulletin < BulkImport::Base end if new_raw != post.raw - PostRevisor.new(post).revise!(post.user, { raw: new_raw }, bypass_bump: true, edit_reason: 'Import attachments from vBulletin') + PostRevisor.new(post).revise!( + post.user, + { raw: new_raw }, + bypass_bump: true, + edit_reason: "Import attachments from vBulletin", + ) end success_count += 1 @@ -639,7 +638,7 @@ class BulkImport::VBulletin < BulkImport::Base Dir.foreach(AVATAR_DIR) do |item| print "\r%7d - %6d/sec" % [count, count.to_f / (Time.now - start)] - next if item == ('.') || item == ('..') || item == ('.DS_Store') + next if item == (".") || item == ("..") || item == (".DS_Store") next unless item =~ /avatar(\d+)_(\d).gif/ scan = item.scan(/avatar(\d+)_(\d).gif/) next unless scan[0][0].present? @@ -671,11 +670,10 @@ class BulkImport::VBulletin < BulkImport::Base def import_signatures puts "Importing user signatures..." - total_count = mysql_query(<<-SQL + total_count = mysql_query(<<-SQL).first[0].to_i SELECT COUNT(userid) count FROM #{TABLE_PREFIX}sigparsed SQL - ).first[0].to_i current_count = 0 user_signatures = mysql_stream <<-SQL @@ -695,13 +693,20 @@ class BulkImport::VBulletin < BulkImport::Base next unless u.present? # can not hold dupes - UserCustomField.where(user_id: u.id, name: ["see_signatures", "signature_raw", "signature_cooked"]).destroy_all + UserCustomField.where( + user_id: u.id, + name: %w[see_signatures signature_raw signature_cooked], + ).destroy_all - user_sig.gsub!(/\[\/?sigpic\]/i, "") + user_sig.gsub!(%r{\[/?sigpic\]}i, "") UserCustomField.create!(user_id: u.id, name: "see_signatures", value: true) UserCustomField.create!(user_id: u.id, name: "signature_raw", value: user_sig) - UserCustomField.create!(user_id: u.id, name: "signature_cooked", value: PrettyText.cook(user_sig, omit_nofollow: false)) + UserCustomField.create!( + user_id: u.id, + name: "signature_cooked", + value: PrettyText.cook(user_sig, omit_nofollow: false), + ) end end @@ -710,15 +715,15 @@ class BulkImport::VBulletin < BulkImport::Base total_count = 0 duplicated = {} - @user_ids_by_email. - select { |e, ids| ids.count > 1 }. - each_with_index do |(email, ids), i| - duplicated[email] = [ ids, i ] + @user_ids_by_email + .select { |e, ids| ids.count > 1 } + .each_with_index do |(email, ids), i| + duplicated[email] = [ids, i] count += 1 total_count += ids.count end - puts '', "Merging #{total_count} duplicated users across #{count} distinct emails..." + puts "", "Merging #{total_count} duplicated users across #{count} distinct emails..." start = Time.now @@ -727,14 +732,15 @@ class BulkImport::VBulletin < BulkImport::Base next unless email.presence # queried one by one to ensure ordering - first, *rest = user_ids.map do |id| - UserCustomField.includes(:user).find_by!(name: 'import_id', value: id).user - end + first, *rest = + user_ids.map do |id| + UserCustomField.includes(:user).find_by!(name: "import_id", value: id).user + end rest.each do |dup| UserMerger.new(dup, first).merge! first.reload - printf '.' + printf "." end print "\n%6d/%6d - %6d/sec" % [i, count, i.to_f / (Time.now - start)] if i % 10 == 0 @@ -744,13 +750,11 @@ class BulkImport::VBulletin < BulkImport::Base end def save_duplicated_users - File.open('duplicated_users.json', 'w+') do |f| - f.puts @user_ids_by_email.to_json - end + File.open("duplicated_users.json", "w+") { |f| f.puts @user_ids_by_email.to_json } end def read_duplicated_users - @user_ids_by_email = JSON.parse File.read('duplicated_users.json') + @user_ids_by_email = JSON.parse File.read("duplicated_users.json") end def extract_pm_title(title) @@ -759,17 +763,26 @@ class BulkImport::VBulletin < BulkImport::Base def parse_birthday(birthday) return if birthday.blank? - date_of_birth = Date.strptime(birthday.gsub(/[^\d-]+/, ""), "%m-%d-%Y") rescue nil + date_of_birth = + begin + Date.strptime(birthday.gsub(/[^\d-]+/, ""), "%m-%d-%Y") + rescue StandardError + nil + end return if date_of_birth.nil? - date_of_birth.year < 1904 ? Date.new(1904, date_of_birth.month, date_of_birth.day) : date_of_birth + if date_of_birth.year < 1904 + Date.new(1904, date_of_birth.month, date_of_birth.day) + else + date_of_birth + end end def print_status(current, max, start_time = nil) if start_time.present? elapsed_seconds = Time.now - start_time - elements_per_minute = '[%.0f items/min] ' % [current / elapsed_seconds.to_f * 60] + elements_per_minute = "[%.0f items/min] " % [current / elapsed_seconds.to_f * 60] else - elements_per_minute = '' + elements_per_minute = "" end print "\r%9d / %d (%5.1f%%) %s" % [current, max, current / max.to_f * 100, elements_per_minute] @@ -782,7 +795,6 @@ class BulkImport::VBulletin < BulkImport::Base def mysql_query(sql) @client.query(sql) end - end BulkImport::VBulletin.new.run diff --git a/script/bulk_import/vbulletin5.rb b/script/bulk_import/vbulletin5.rb index 9be967c25c..e952ab5d76 100644 --- a/script/bulk_import/vbulletin5.rb +++ b/script/bulk_import/vbulletin5.rb @@ -5,47 +5,56 @@ require "cgi" require "set" require "mysql2" require "htmlentities" -require 'ruby-bbcode-to-md' -require 'find' +require "ruby-bbcode-to-md" +require "find" class BulkImport::VBulletin5 < BulkImport::Base - DB_PREFIX = "" SUSPENDED_TILL ||= Date.new(3000, 1, 1) - ATTACH_DIR ||= ENV['ATTACH_DIR'] || '/shared/import/data/attachments' - AVATAR_DIR ||= ENV['AVATAR_DIR'] || '/shared/import/data/customavatars' + ATTACH_DIR ||= ENV["ATTACH_DIR"] || "/shared/import/data/attachments" + AVATAR_DIR ||= ENV["AVATAR_DIR"] || "/shared/import/data/customavatars" ROOT_NODE = 2 def initialize super - host = ENV["DB_HOST"] || "localhost" + host = ENV["DB_HOST"] || "localhost" username = ENV["DB_USERNAME"] || "root" password = ENV["DB_PASSWORD"] database = ENV["DB_NAME"] || "vbulletin" - charset = ENV["DB_CHARSET"] || "utf8" + charset = ENV["DB_CHARSET"] || "utf8" @html_entities = HTMLEntities.new @encoding = CHARSET_MAP[charset] @bbcode_to_md = true - @client = Mysql2::Client.new( - host: host, - username: username, - password: password, - database: database, - encoding: charset, - reconnect: true - ) + @client = + Mysql2::Client.new( + host: host, + username: username, + password: password, + database: database, + encoding: charset, + reconnect: true, + ) @client.query_options.merge!(as: :array, cache_rows: false) # TODO: Add `LIMIT 1` to the below queries # ------ # be aware there may be other contenttypeid's in use, such as poll, link, video, etc. - @forum_typeid = mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Forum'").to_a[0][0] - @channel_typeid = mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Channel'").to_a[0][0] - @text_typeid = mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Text'").to_a[0][0] + @forum_typeid = + mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Forum'").to_a[0][ + 0 + ] + @channel_typeid = + mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Channel'").to_a[ + 0 + ][ + 0 + ] + @text_typeid = + mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Text'").to_a[0][0] end def execute @@ -127,7 +136,7 @@ class BulkImport::VBulletin5 < BulkImport::Base date_of_birth: parse_birthday(row[3]), primary_group_id: group_id_from_imported_id(row[5]), admin: row[5] == 6, - moderator: row[5] == 7 + moderator: row[5] == 7, } u[:ip_address] = row[4][/\b(?:\d{1,3}\.){3}\d{1,3}\b/] if row[4].present? if row[7] @@ -153,7 +162,7 @@ class BulkImport::VBulletin5 < BulkImport::Base imported_id: row[0], imported_user_id: row[0], email: random_email, - created_at: Time.zone.at(row[2]) + created_at: Time.zone.at(row[2]), } end end @@ -203,10 +212,7 @@ class BulkImport::VBulletin5 < BulkImport::Base SQL create_group_users(group_users) do |row| - { - group_id: group_id_from_imported_id(row[0]), - user_id: user_id_from_imported_id(row[1]), - } + { group_id: group_id_from_imported_id(row[0]), user_id: user_id_from_imported_id(row[1]) } end # import secondary group memberships @@ -228,12 +234,7 @@ class BulkImport::VBulletin5 < BulkImport::Base end end - create_group_users(group_mapping) do |row| - { - group_id: row[0], - user_id: row[1] - } - end + create_group_users(group_mapping) { |row| { group_id: row[0], user_id: row[1] } } end def import_user_profiles @@ -249,7 +250,14 @@ class BulkImport::VBulletin5 < BulkImport::Base create_user_profiles(user_profiles) do |row| { user_id: user_id_from_imported_id(row[0]), - website: (URI.parse(row[1]).to_s rescue nil), + website: + ( + begin + URI.parse(row[1]).to_s + rescue StandardError + nil + end + ), views: row[2], } end @@ -258,7 +266,7 @@ class BulkImport::VBulletin5 < BulkImport::Base def import_categories puts "Importing categories..." - categories = mysql_query(<<-SQL + categories = mysql_query(<<-SQL).to_a SELECT nodeid AS forumid, title, description, displayorder, parentid, urlident FROM #{DB_PREFIX}node WHERE parentid = #{ROOT_NODE} @@ -269,11 +277,10 @@ class BulkImport::VBulletin5 < BulkImport::Base WHERE contenttypeid = #{@channel_typeid} AND nodeid > #{@last_imported_category_id} SQL - ).to_a return if categories.empty? - parent_categories = categories.select { |c| c[4] == ROOT_NODE } + parent_categories = categories.select { |c| c[4] == ROOT_NODE } children_categories = categories.select { |c| c[4] != ROOT_NODE } parent_category_ids = Set.new parent_categories.map { |c| c[0] } @@ -285,7 +292,7 @@ class BulkImport::VBulletin5 < BulkImport::Base name: normalize_text(row[1]), description: normalize_text(row[2]), position: row[3], - slug: row[5] + slug: row[5], } end @@ -297,7 +304,7 @@ class BulkImport::VBulletin5 < BulkImport::Base description: normalize_text(row[2]), position: row[3], parent_category_id: category_id_from_imported_id(row[4]), - slug: row[5] + slug: row[5], } end end @@ -428,7 +435,7 @@ class BulkImport::VBulletin5 < BulkImport::Base post_id: post_id, user_id: user_id, post_action_type_id: 2, - created_at: Time.zone.at(row[2]) + created_at: Time.zone.at(row[2]), } end end @@ -455,7 +462,6 @@ class BulkImport::VBulletin5 < BulkImport::Base user_id: user_id_from_imported_id(row[2]), created_at: Time.zone.at(row[3]), } - end end @@ -475,17 +481,18 @@ class BulkImport::VBulletin5 < BulkImport::Base users_added = Set.new create_topic_allowed_users(mysql_stream(allowed_users_sql)) do |row| - next unless topic_id = topic_id_from_imported_id(row[0] + PRIVATE_OFFSET) || topic_id_from_imported_id(row[2] + PRIVATE_OFFSET) + unless topic_id = + topic_id_from_imported_id(row[0] + PRIVATE_OFFSET) || + topic_id_from_imported_id(row[2] + PRIVATE_OFFSET) + next + end next unless user_id = user_id_from_imported_id(row[1]) next if users_added.add?([topic_id, user_id]).nil? added += 1 - { - topic_id: topic_id, - user_id: user_id, - } + { topic_id: topic_id, user_id: user_id } end - puts '', "Added #{added} topic allowed users records." + puts "", "Added #{added} topic allowed users records." end def import_private_first_posts @@ -543,7 +550,7 @@ class BulkImport::VBulletin5 < BulkImport::Base end def create_permalinks - puts '', 'creating permalinks...', '' + puts "", "creating permalinks...", "" # add permalink normalizations to site settings # EVERYTHING: /.*\/([\w-]+)$/\1 -- selects the last segment of the URL @@ -580,21 +587,23 @@ class BulkImport::VBulletin5 < BulkImport::Base return nil end - tmpfile = 'attach_' + row[6].to_s - filename = File.join('/tmp/', tmpfile) - File.open(filename, 'wb') { |f| f.write(row[5]) } + tmpfile = "attach_" + row[6].to_s + filename = File.join("/tmp/", tmpfile) + File.open(filename, "wb") { |f| f.write(row[5]) } filename end def find_upload(post, opts = {}) if opts[:node_id].present? - sql = "SELECT a.nodeid, n.parentid, a.filename, fd.userid, LENGTH(fd.filedata), filedata, fd.filedataid + sql = + "SELECT a.nodeid, n.parentid, a.filename, fd.userid, LENGTH(fd.filedata), filedata, fd.filedataid FROM #{DB_PREFIX}attach a LEFT JOIN #{DB_PREFIX}filedata fd ON fd.filedataid = a.filedataid LEFT JOIN #{DB_PREFIX}node n ON n.nodeid = a.nodeid WHERE a.nodeid = #{opts[:node_id]}" elsif opts[:attachment_id].present? - sql = "SELECT a.nodeid, n.parentid, a.filename, fd.userid, LENGTH(fd.filedata), filedata, fd.filedataid + sql = + "SELECT a.nodeid, n.parentid, a.filename, fd.userid, LENGTH(fd.filedata), filedata, fd.filedataid FROM #{DB_PREFIX}attachment a LEFT JOIN #{DB_PREFIX}filedata fd ON fd.filedataid = a.filedataid LEFT JOIN #{DB_PREFIX}node n ON n.nodeid = a.nodeid @@ -612,9 +621,9 @@ class BulkImport::VBulletin5 < BulkImport::Base user_id = row[3] db_filename = row[2] - filename = File.join(ATTACH_DIR, user_id.to_s.split('').join('/'), "#{attachment_id}.attach") + filename = File.join(ATTACH_DIR, user_id.to_s.split("").join("/"), "#{attachment_id}.attach") real_filename = db_filename - real_filename.prepend SecureRandom.hex if real_filename[0] == '.' + real_filename.prepend SecureRandom.hex if real_filename[0] == "." unless File.exist?(filename) filename = check_database_for_attachment(row) if filename.blank? @@ -637,7 +646,7 @@ class BulkImport::VBulletin5 < BulkImport::Base end def import_attachments - puts '', 'importing attachments...' + puts "", "importing attachments..." # add extensions to authorized setting #ext = mysql_query("SELECT GROUP_CONCAT(DISTINCT(extension)) exts FROM #{DB_PREFIX}filedata").first[0].split(',') @@ -655,8 +664,8 @@ class BulkImport::VBulletin5 < BulkImport::Base # new style matches the nodeid in the attach table # old style matches the filedataid in attach/filedata tables # if the site is very old, there may be multiple different attachment syntaxes used in posts - attachment_regex = /\[attach[^\]]*\].*\"data-attachmentid\":"?(\d+)"?,?.*\[\/attach\]/i - attachment_regex_oldstyle = /\[attach[^\]]*\](\d+)\[\/attach\]/i + attachment_regex = %r{\[attach[^\]]*\].*\"data-attachmentid\":"?(\d+)"?,?.*\[/attach\]}i + attachment_regex_oldstyle = %r{\[attach[^\]]*\](\d+)\[/attach\]}i Post.find_each do |post| current_count += 1 @@ -715,9 +724,18 @@ class BulkImport::VBulletin5 < BulkImport::Base def parse_birthday(birthday) return if birthday.blank? - date_of_birth = Date.strptime(birthday.gsub(/[^\d-]+/, ""), "%m-%d-%Y") rescue nil + date_of_birth = + begin + Date.strptime(birthday.gsub(/[^\d-]+/, ""), "%m-%d-%Y") + rescue StandardError + nil + end return if date_of_birth.nil? - date_of_birth.year < 1904 ? Date.new(1904, date_of_birth.month, date_of_birth.day) : date_of_birth + if date_of_birth.year < 1904 + Date.new(1904, date_of_birth.month, date_of_birth.day) + else + date_of_birth + end end def preprocess_raw(raw) @@ -726,33 +744,37 @@ class BulkImport::VBulletin5 < BulkImport::Base raw = raw.dup # [PLAINTEXT]...[/PLAINTEXT] - raw.gsub!(/\[\/?PLAINTEXT\]/i, "\n\n```\n\n") + raw.gsub!(%r{\[/?PLAINTEXT\]}i, "\n\n```\n\n") # [FONT=font]...[/FONT] raw.gsub!(/\[FONT=\w*\]/im, "") - raw.gsub!(/\[\/FONT\]/im, "") + raw.gsub!(%r{\[/FONT\]}im, "") # @[URL=][/URL] # [USER=id]username[/USER] # [MENTION=id]username[/MENTION] - raw.gsub!(/@\[URL=\"\S+\"\]([\w\s]+)\[\/URL\]/i) { "@#{$1.gsub(" ", "_")}" } - raw.gsub!(/\[USER=\"\d+\"\]([\S]+)\[\/USER\]/i) { "@#{$1.gsub(" ", "_")}" } - raw.gsub!(/\[MENTION=\d+\]([\S]+)\[\/MENTION\]/i) { "@#{$1.gsub(" ", "_")}" } + raw.gsub!(%r{@\[URL=\"\S+\"\]([\w\s]+)\[/URL\]}i) { "@#{$1.gsub(" ", "_")}" } + raw.gsub!(%r{\[USER=\"\d+\"\]([\S]+)\[/USER\]}i) { "@#{$1.gsub(" ", "_")}" } + raw.gsub!(%r{\[MENTION=\d+\]([\S]+)\[/MENTION\]}i) { "@#{$1.gsub(" ", "_")}" } # [IMG2=JSON]{..."src":""}[/IMG2] - raw.gsub!(/\[img2[^\]]*\].*\"src\":\"?([\w\\\/:\.\-;%]*)\"?}.*\[\/img2\]/i) { "\n#{CGI::unescape($1)}\n" } + raw.gsub!(/\[img2[^\]]*\].*\"src\":\"?([\w\\\/:\.\-;%]*)\"?}.*\[\/img2\]/i) do + "\n#{CGI.unescape($1)}\n" + end # [TABLE]...[/TABLE] raw.gsub!(/\[TABLE=\\"[\w:\-\s,]+\\"\]/i, "") - raw.gsub!(/\[\/TABLE\]/i, "") + raw.gsub!(%r{\[/TABLE\]}i, "") # [HR]...[/HR] - raw.gsub(/\[HR\]\s*\[\/HR\]/im, "---") + raw.gsub(%r{\[HR\]\s*\[/HR\]}im, "---") # [VIDEO=youtube_share;]...[/VIDEO] # [VIDEO=vimeo;]...[/VIDEO] - raw.gsub!(/\[VIDEO=YOUTUBE_SHARE;([^\]]+)\].*?\[\/VIDEO\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } - raw.gsub!(/\[VIDEO=VIMEO;([^\]]+)\].*?\[\/VIDEO\]/i) { "\nhttps://vimeo.com/#{$1}\n" } + raw.gsub!(%r{\[VIDEO=YOUTUBE_SHARE;([^\]]+)\].*?\[/VIDEO\]}i) do + "\nhttps://www.youtube.com/watch?v=#{$1}\n" + end + raw.gsub!(%r{\[VIDEO=VIMEO;([^\]]+)\].*?\[/VIDEO\]}i) { "\nhttps://vimeo.com/#{$1}\n" } raw end @@ -760,9 +782,9 @@ class BulkImport::VBulletin5 < BulkImport::Base def print_status(current, max, start_time = nil) if start_time.present? elapsed_seconds = Time.now - start_time - elements_per_minute = '[%.0f items/min] ' % [current / elapsed_seconds.to_f * 60] + elements_per_minute = "[%.0f items/min] " % [current / elapsed_seconds.to_f * 60] else - elements_per_minute = '' + elements_per_minute = "" end print "\r%9d / %d (%5.1f%%) %s" % [current, max, current / max.to_f * 100, elements_per_minute] @@ -775,7 +797,6 @@ class BulkImport::VBulletin5 < BulkImport::Base def mysql_query(sql) @client.query(sql) end - end BulkImport::VBulletin5.new.run diff --git a/script/check_forking.rb b/script/check_forking.rb index ae8196af94..8679e6ec25 100644 --- a/script/check_forking.rb +++ b/script/check_forking.rb @@ -13,20 +13,22 @@ end Discourse.after_fork pretty -child = fork do - Discourse.after_fork - pretty - grand_child = fork do +child = + fork do Discourse.after_fork pretty - puts "try to exit" + grand_child = + fork do + Discourse.after_fork + pretty + puts "try to exit" + Process.kill "KILL", Process.pid + end + puts "before wait 2" + Process.wait grand_child + puts "after wait 2" Process.kill "KILL", Process.pid end - puts "before wait 2" - Process.wait grand_child - puts "after wait 2" - Process.kill "KILL", Process.pid -end puts "before wait 1" Process.wait child diff --git a/script/db_timestamps_mover.rb b/script/db_timestamps_mover.rb index 30701dd1f6..248f189387 100644 --- a/script/db_timestamps_mover.rb +++ b/script/db_timestamps_mover.rb @@ -12,20 +12,18 @@ class TimestampsUpdater def initialize(schema, ignore_tables) @schema = schema @ignore_tables = ignore_tables - @raw_connection = PG.connect( - host: ENV['DISCOURSE_DB_HOST'] || 'localhost', - port: ENV['DISCOURSE_DB_PORT'] || 5432, - dbname: ENV['DISCOURSE_DB_NAME'] || 'discourse_development', - user: ENV['DISCOURSE_DB_USERNAME'] || 'postgres', - password: ENV['DISCOURSE_DB_PASSWORD'] || '') + @raw_connection = + PG.connect( + host: ENV["DISCOURSE_DB_HOST"] || "localhost", + port: ENV["DISCOURSE_DB_PORT"] || 5432, + dbname: ENV["DISCOURSE_DB_NAME"] || "discourse_development", + user: ENV["DISCOURSE_DB_USERNAME"] || "postgres", + password: ENV["DISCOURSE_DB_PASSWORD"] || "", + ) end def move_by(days) - postgresql_date_types = [ - "timestamp without time zone", - "timestamp with time zone", - "date" - ] + postgresql_date_types = ["timestamp without time zone", "timestamp with time zone", "date"] postgresql_date_types.each do |data_type| columns = all_columns_of_type(data_type) @@ -118,11 +116,19 @@ class TimestampsUpdater end def is_i?(string) - true if Integer(string) rescue false + begin + true if Integer(string) + rescue StandardError + false + end end def is_date?(string) - true if Date.parse(string) rescue false + begin + true if Date.parse(string) + rescue StandardError + false + end end def create_updater diff --git a/script/diff_heaps.rb b/script/diff_heaps.rb index 5a0e91efd6..8433773d7f 100644 --- a/script/diff_heaps.rb +++ b/script/diff_heaps.rb @@ -6,8 +6,8 @@ # rbtrace -p 15193 -e 'Thread.new{require "objspace"; ObjectSpace.trace_object_allocations_start; GC.start(full_mark: true); ObjectSpace.dump_all(output: File.open("heap.json","w"))}.join' # # -require 'set' -require 'json' +require "set" +require "json" if ARGV.length != 2 puts "Usage: diff_heaps [ORIG.json] [AFTER.json]" @@ -16,26 +16,26 @@ end origs = Set.new -File.open(ARGV[0], "r").each_line do |line| - parsed = JSON.parse(line) - origs << parsed["address"] if parsed && parsed["address"] -end +File + .open(ARGV[0], "r") + .each_line do |line| + parsed = JSON.parse(line) + origs << parsed["address"] if parsed && parsed["address"] + end diff = [] -File.open(ARGV[1], "r").each_line do |line| - parsed = JSON.parse(line) - if parsed && parsed["address"] - diff << parsed unless origs.include? parsed["address"] +File + .open(ARGV[1], "r") + .each_line do |line| + parsed = JSON.parse(line) + if parsed && parsed["address"] + diff << parsed unless origs.include? parsed["address"] + end end -end -diff.group_by do |x| - [x["type"], x["file"], x["line"]] -end.map { |x, y| - [x, y.count] -}.sort { |a, b| - b[1] <=> a[1] -}.each { |x, y| - puts "Leaked #{y} #{x[0]} objects at: #{x[1]}:#{x[2]}" -} +diff + .group_by { |x| [x["type"], x["file"], x["line"]] } + .map { |x, y| [x, y.count] } + .sort { |a, b| b[1] <=> a[1] } + .each { |x, y| puts "Leaked #{y} #{x[0]} objects at: #{x[1]}:#{x[2]}" } diff --git a/script/docker_test.rb b/script/docker_test.rb index db73d531be..d51b63b5c5 100644 --- a/script/docker_test.rb +++ b/script/docker_test.rb @@ -19,11 +19,11 @@ def run_or_fail(command) exit 1 unless $?.exitstatus == 0 end -unless ENV['NO_UPDATE'] +unless ENV["NO_UPDATE"] run_or_fail("git reset --hard") run_or_fail("git fetch") - checkout = ENV['COMMIT_HASH'] || "FETCH_HEAD" + checkout = ENV["COMMIT_HASH"] || "FETCH_HEAD" run_or_fail("LEFTHOOK=0 git checkout #{checkout}") run_or_fail("bundle") @@ -31,7 +31,7 @@ end log("Running tests") -if ENV['RUN_SMOKE_TESTS'] +if ENV["RUN_SMOKE_TESTS"] run_or_fail("bundle exec rake smoke:test") else run_or_fail("bundle exec rake docker:test") diff --git a/script/i18n_lint.rb b/script/i18n_lint.rb index b00de75191..f3f96abf87 100755 --- a/script/i18n_lint.rb +++ b/script/i18n_lint.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'colored2' -require 'psych' +require "colored2" +require "psych" class I18nLinter def initialize(filenames_or_patterns) @@ -27,16 +27,22 @@ end class LocaleFileValidator ERROR_MESSAGES = { - invalid_relative_links: "The following keys have relative links, but do not start with %{base_url} or %{base_path}:", - invalid_relative_image_sources: "The following keys have relative image sources, but do not start with %{base_url} or %{base_path}:", - invalid_interpolation_key_format: "The following keys use {{key}} instead of %{key} for interpolation keys:", - wrong_pluralization_keys: "Pluralized strings must have only the sub-keys 'one' and 'other'.\nThe following keys have missing or additional keys:", - invalid_one_keys: "The following keys contain the number 1 instead of the interpolation key %{count}:", - invalid_message_format_one_key: "The following keys use 'one {1 foo}' instead of the generic 'one {# foo}':", + invalid_relative_links: + "The following keys have relative links, but do not start with %{base_url} or %{base_path}:", + invalid_relative_image_sources: + "The following keys have relative image sources, but do not start with %{base_url} or %{base_path}:", + invalid_interpolation_key_format: + "The following keys use {{key}} instead of %{key} for interpolation keys:", + wrong_pluralization_keys: + "Pluralized strings must have only the sub-keys 'one' and 'other'.\nThe following keys have missing or additional keys:", + invalid_one_keys: + "The following keys contain the number 1 instead of the interpolation key %{count}:", + invalid_message_format_one_key: + "The following keys use 'one {1 foo}' instead of the generic 'one {# foo}':", } - PLURALIZATION_KEYS = ['zero', 'one', 'two', 'few', 'many', 'other'] - ENGLISH_KEYS = ['one', 'other'] + PLURALIZATION_KEYS = %w[zero one two few many other] + ENGLISH_KEYS = %w[one other] def initialize(filename) @filename = filename @@ -66,7 +72,7 @@ class LocaleFileValidator private - def each_translation(hash, parent_key = '', &block) + def each_translation(hash, parent_key = "", &block) hash.each do |key, value| current_key = parent_key.empty? ? key : "#{parent_key}.#{key}" @@ -85,13 +91,9 @@ class LocaleFileValidator @errors[:invalid_message_format_one_key] = [] each_translation(yaml) do |key, value| - if value.match?(/href\s*=\s*["']\/[^\/]|\]\(\/[^\/]/i) - @errors[:invalid_relative_links] << key - end + @errors[:invalid_relative_links] << key if value.match?(%r{href\s*=\s*["']/[^/]|\]\(/[^/]}i) - if value.match?(/src\s*=\s*["']\/[^\/]/i) - @errors[:invalid_relative_image_sources] << key - end + @errors[:invalid_relative_image_sources] << key if value.match?(%r{src\s*=\s*["']/[^/]}i) if value.match?(/{{.+?}}/) && !key.end_with?("_MF") @errors[:invalid_interpolation_key_format] << key @@ -103,7 +105,7 @@ class LocaleFileValidator end end - def each_pluralization(hash, parent_key = '', &block) + def each_pluralization(hash, parent_key = "", &block) hash.each do |key, value| if Hash === value current_key = parent_key.empty? ? key : "#{parent_key}.#{key}" @@ -124,8 +126,8 @@ class LocaleFileValidator @errors[:wrong_pluralization_keys] << key if hash.keys.sort != ENGLISH_KEYS - one_value = hash['one'] - if one_value && one_value.include?('1') && !one_value.match?(/%{count}|{{count}}/) + one_value = hash["one"] + if one_value && one_value.include?("1") && !one_value.match?(/%{count}|{{count}}/) @errors[:invalid_one_keys] << key end end diff --git a/script/import_scripts/answerbase.rb b/script/import_scripts/answerbase.rb index 1f436ac0ba..63de1d4a93 100644 --- a/script/import_scripts/answerbase.rb +++ b/script/import_scripts/answerbase.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require 'csv' -require 'reverse_markdown' -require_relative 'base' -require_relative 'base/generic_database' +require "csv" +require "reverse_markdown" +require_relative "base" +require_relative "base/generic_database" # Call it like this: # RAILS_ENV=production bundle exec ruby script/import_scripts/answerbase.rb DIRNAME @@ -15,8 +15,10 @@ class ImportScripts::Answerbase < ImportScripts::Base ANSWER_IMAGE_DIRECTORY = "Answer Images" QUESTION_ATTACHMENT_DIRECTORY = "Question Attachments" QUESTION_IMAGE_DIRECTORY = "Question Images" - EMBEDDED_IMAGE_REGEX = /]*href="[^"]*relativeUrl=(?[^"\&]*)[^"]*"[^>]*>\s*]*>\s*<\/a>/i - QUESTION_LINK_REGEX = /]*?href="#{Regexp.escape(OLD_DOMAIN)}\/[^"]*?(?:q|questionid=)(?\d+)[^"]*?"[^>]*>(?.*?)<\/a>/i + EMBEDDED_IMAGE_REGEX = + %r{]*href="[^"]*relativeUrl=(?[^"\&]*)[^"]*"[^>]*>\s*]*>\s*}i + QUESTION_LINK_REGEX = + %r{]*?href="#{Regexp.escape(OLD_DOMAIN)}/[^"]*?(?:q|questionid=)(?\d+)[^"]*?"[^>]*>(?.*?)}i TOPIC_LINK_NORMALIZATION = '/.*?-(q\d+).*/\1' BATCH_SIZE = 1000 @@ -24,12 +26,13 @@ class ImportScripts::Answerbase < ImportScripts::Base super() @path = path - @db = ImportScripts::GenericDatabase.new( - @path, - batch_size: BATCH_SIZE, - recreate: true, - numeric_keys: true - ) + @db = + ImportScripts::GenericDatabase.new( + @path, + batch_size: BATCH_SIZE, + recreate: true, + numeric_keys: true, + ) end def execute @@ -47,11 +50,7 @@ class ImportScripts::Answerbase < ImportScripts::Base category_position = 0 csv_parse("categories") do |row| - @db.insert_category( - id: row[:id], - name: row[:name], - position: category_position += 1 - ) + @db.insert_category(id: row[:id], name: row[:name], position: category_position += 1) end csv_parse("users") do |row| @@ -62,7 +61,7 @@ class ImportScripts::Answerbase < ImportScripts::Base bio: row[:description], avatar_path: row[:profile_image], created_at: parse_date(row[:createtime]), - active: true + active: true, ) end @@ -74,8 +73,9 @@ class ImportScripts::Answerbase < ImportScripts::Base begin if row[:type] == "Question" - attachments = parse_filenames(row[:attachments], QUESTION_ATTACHMENT_DIRECTORY) + - parse_filenames(row[:images], QUESTION_IMAGE_DIRECTORY) + attachments = + parse_filenames(row[:attachments], QUESTION_ATTACHMENT_DIRECTORY) + + parse_filenames(row[:images], QUESTION_IMAGE_DIRECTORY) @db.insert_topic( id: row[:id], @@ -84,12 +84,13 @@ class ImportScripts::Answerbase < ImportScripts::Base category_id: row[:categorylist], user_id: user_id, created_at: created_at, - attachments: attachments + attachments: attachments, ) last_topic_id = row[:id] else - attachments = parse_filenames(row[:attachments], ANSWER_ATTACHMENT_DIRECTORY) + - parse_filenames(row[:images], ANSWER_IMAGE_DIRECTORY) + attachments = + parse_filenames(row[:attachments], ANSWER_ATTACHMENT_DIRECTORY) + + parse_filenames(row[:images], ANSWER_IMAGE_DIRECTORY) @db.insert_post( id: row[:id], @@ -97,10 +98,10 @@ class ImportScripts::Answerbase < ImportScripts::Base topic_id: last_topic_id, user_id: user_id, created_at: created_at, - attachments: attachments + attachments: attachments, ) end - rescue + rescue StandardError p row raise end @@ -110,9 +111,7 @@ class ImportScripts::Answerbase < ImportScripts::Base def parse_filenames(text, directory) return [] if text.blank? - text - .split(';') - .map { |filename| File.join(@path, directory, filename.strip) } + text.split(";").map { |filename| File.join(@path, directory, filename.strip) } end def parse_date(text) @@ -132,10 +131,10 @@ class ImportScripts::Answerbase < ImportScripts::Base create_categories(rows) do |row| { - id: row['id'], - name: row['name'], - description: row['description'], - position: row['position'] + id: row["id"], + name: row["name"], + description: row["description"], + position: row["position"], } end end @@ -153,19 +152,17 @@ class ImportScripts::Answerbase < ImportScripts::Base rows, last_id = @db.fetch_users(last_id) break if rows.empty? - next if all_records_exist?(:users, rows.map { |row| row['id'] }) + next if all_records_exist?(:users, rows.map { |row| row["id"] }) create_users(rows, total: total_count, offset: offset) do |row| { - id: row['id'], - email: row['email'], - username: row['username'], - bio_raw: row['bio'], - created_at: row['created_at'], - active: row['active'] == 1, - post_create_action: proc do |user| - create_avatar(user, row['avatar_path']) - end + id: row["id"], + email: row["email"], + username: row["username"], + bio_raw: row["bio"], + created_at: row["created_at"], + active: row["active"] == 1, + post_create_action: proc { |user| create_avatar(user, row["avatar_path"]) }, } end end @@ -191,24 +188,25 @@ class ImportScripts::Answerbase < ImportScripts::Base rows, last_id = @db.fetch_topics(last_id) break if rows.empty? - next if all_records_exist?(:posts, rows.map { |row| row['id'] }) + next if all_records_exist?(:posts, rows.map { |row| row["id"] }) create_posts(rows, total: total_count, offset: offset) do |row| - attachments = @db.fetch_topic_attachments(row['id']) if row['upload_count'] > 0 - user_id = user_id_from_imported_user_id(row['user_id']) || Discourse.system_user.id + attachments = @db.fetch_topic_attachments(row["id"]) if row["upload_count"] > 0 + user_id = user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id { - id: row['id'], - title: row['title'], - raw: raw_with_attachments(row['raw'].presence || row['title'], attachments, user_id), - category: category_id_from_imported_category_id(row['category_id']), + id: row["id"], + title: row["title"], + raw: raw_with_attachments(row["raw"].presence || row["title"], attachments, user_id), + category: category_id_from_imported_category_id(row["category_id"]), user_id: user_id, - created_at: row['created_at'], - closed: row['closed'] == 1, - post_create_action: proc do |post| - url = "q#{row['id']}" - Permalink.create(url: url, topic_id: post.topic.id) unless permalink_exists?(url) - end + created_at: row["created_at"], + closed: row["closed"] == 1, + post_create_action: + proc do |post| + url = "q#{row["id"]}" + Permalink.create(url: url, topic_id: post.topic.id) unless permalink_exists?(url) + end, } end end @@ -223,19 +221,19 @@ class ImportScripts::Answerbase < ImportScripts::Base rows, last_row_id = @db.fetch_posts(last_row_id) break if rows.empty? - next if all_records_exist?(:posts, rows.map { |row| row['id'] }) + next if all_records_exist?(:posts, rows.map { |row| row["id"] }) create_posts(rows, total: total_count, offset: offset) do |row| - topic = topic_lookup_from_imported_post_id(row['topic_id']) - attachments = @db.fetch_post_attachments(row['id']) if row['upload_count'] > 0 - user_id = user_id_from_imported_user_id(row['user_id']) || Discourse.system_user.id + topic = topic_lookup_from_imported_post_id(row["topic_id"]) + attachments = @db.fetch_post_attachments(row["id"]) if row["upload_count"] > 0 + user_id = user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id { - id: row['id'], - raw: raw_with_attachments(row['raw'], attachments, user_id), + id: row["id"], + raw: raw_with_attachments(row["raw"], attachments, user_id), user_id: user_id, topic_id: topic[:topic_id], - created_at: row['created_at'] + created_at: row["created_at"], } end end @@ -247,7 +245,7 @@ class ImportScripts::Answerbase < ImportScripts::Base raw = ReverseMarkdown.convert(raw) || "" attachments&.each do |attachment| - path = attachment['path'] + path = attachment["path"] next if embedded_paths.include?(path) if File.exist?(path) @@ -269,23 +267,24 @@ class ImportScripts::Answerbase < ImportScripts::Base paths = [] upload_ids = [] - raw = raw.gsub(EMBEDDED_IMAGE_REGEX) do - path = File.join(@path, Regexp.last_match['path']) - filename = File.basename(path) - path = find_image_path(filename) + raw = + raw.gsub(EMBEDDED_IMAGE_REGEX) do + path = File.join(@path, Regexp.last_match["path"]) + filename = File.basename(path) + path = find_image_path(filename) - if path - upload = @uploader.create_upload(user_id, path, filename) + if path + upload = @uploader.create_upload(user_id, path, filename) - if upload.present? && upload.persisted? - paths << path - upload_ids << upload.id - @uploader.html_for_upload(upload, filename) + if upload.present? && upload.persisted? + paths << path + upload_ids << upload.id + @uploader.html_for_upload(upload, filename) + end + else + STDERR.puts "Could not find file: #{path}" end - else - STDERR.puts "Could not find file: #{path}" end - end [raw, paths, upload_ids] end @@ -311,11 +310,11 @@ class ImportScripts::Answerbase < ImportScripts::Base def add_permalink_normalizations normalizations = SiteSetting.permalink_normalizations - normalizations = normalizations.blank? ? [] : normalizations.split('|') + normalizations = normalizations.blank? ? [] : normalizations.split("|") add_normalization(normalizations, TOPIC_LINK_NORMALIZATION) - SiteSetting.permalink_normalizations = normalizations.join('|') + SiteSetting.permalink_normalizations = normalizations.join("|") end def add_normalization(normalizations, normalization) @@ -327,11 +326,13 @@ class ImportScripts::Answerbase < ImportScripts::Base end def csv_parse(table_name) - CSV.foreach(File.join(@path, "#{table_name}.csv"), - headers: true, - header_converters: :symbol, - skip_blanks: true, - encoding: 'bom|utf-8') { |row| yield row } + CSV.foreach( + File.join(@path, "#{table_name}.csv"), + headers: true, + header_converters: :symbol, + skip_blanks: true, + encoding: "bom|utf-8", + ) { |row| yield row } end end diff --git a/script/import_scripts/answerhub.rb b/script/import_scripts/answerhub.rb index f2e05811ba..b4954dcccb 100644 --- a/script/import_scripts/answerhub.rb +++ b/script/import_scripts/answerhub.rb @@ -5,34 +5,29 @@ # Based on having access to a mysql dump. # Pass in the ENV variables listed below before running the script. -require_relative 'base' -require 'mysql2' -require 'open-uri' +require_relative "base" +require "mysql2" +require "open-uri" class ImportScripts::AnswerHub < ImportScripts::Base - - DB_NAME ||= ENV['DB_NAME'] || "answerhub" - DB_PASS ||= ENV['DB_PASS'] || "answerhub" - DB_USER ||= ENV['DB_USER'] || "answerhub" - TABLE_PREFIX ||= ENV['TABLE_PREFIX'] || "network1" - BATCH_SIZE ||= ENV['BATCH_SIZE'].to_i || 1000 - ATTACHMENT_DIR = ENV['ATTACHMENT_DIR'] || '' - PROCESS_UPLOADS = ENV['PROCESS_UPLOADS'].to_i || 0 - ANSWERHUB_DOMAIN = ENV['ANSWERHUB_DOMAIN'] - AVATAR_DIR = ENV['AVATAR_DIR'] || "" - SITE_ID = ENV['SITE_ID'].to_i || 0 - CATEGORY_MAP_FROM = ENV['CATEGORY_MAP_FROM'].to_i || 0 - CATEGORY_MAP_TO = ENV['CATEGORY_MAP_TO'].to_i || 0 - SCRAPE_AVATARS = ENV['SCRAPE_AVATARS'].to_i || 0 + DB_NAME ||= ENV["DB_NAME"] || "answerhub" + DB_PASS ||= ENV["DB_PASS"] || "answerhub" + DB_USER ||= ENV["DB_USER"] || "answerhub" + TABLE_PREFIX ||= ENV["TABLE_PREFIX"] || "network1" + BATCH_SIZE ||= ENV["BATCH_SIZE"].to_i || 1000 + ATTACHMENT_DIR = ENV["ATTACHMENT_DIR"] || "" + PROCESS_UPLOADS = ENV["PROCESS_UPLOADS"].to_i || 0 + ANSWERHUB_DOMAIN = ENV["ANSWERHUB_DOMAIN"] + AVATAR_DIR = ENV["AVATAR_DIR"] || "" + SITE_ID = ENV["SITE_ID"].to_i || 0 + CATEGORY_MAP_FROM = ENV["CATEGORY_MAP_FROM"].to_i || 0 + CATEGORY_MAP_TO = ENV["CATEGORY_MAP_TO"].to_i || 0 + SCRAPE_AVATARS = ENV["SCRAPE_AVATARS"].to_i || 0 def initialize super - @client = Mysql2::Client.new( - host: "localhost", - username: DB_USER, - password: DB_PASS, - database: DB_NAME - ) + @client = + Mysql2::Client.new(host: "localhost", username: DB_USER, password: DB_PASS, database: DB_NAME) @skip_updates = true SiteSetting.tagging_enabled = true SiteSetting.max_tags_per_topic = 10 @@ -56,7 +51,7 @@ class ImportScripts::AnswerHub < ImportScripts::Base end def import_users - puts '', "creating users" + puts "", "creating users" query = "SELECT count(*) count @@ -64,12 +59,13 @@ class ImportScripts::AnswerHub < ImportScripts::Base WHERE c_type = 'user' AND c_active = 1 AND c_system <> 1;" - total_count = @client.query(query).first['count'] + total_count = @client.query(query).first["count"] puts "Total count: #{total_count}" @last_user_id = -1 batches(BATCH_SIZE) do |offset| - query = "SELECT c_id, c_creation_date, c_name, c_primaryEmail, c_last_seen, c_description + query = + "SELECT c_id, c_creation_date, c_name, c_primaryEmail, c_last_seen, c_description FROM #{TABLE_PREFIX}_authoritables WHERE c_type = 'user' AND c_active = 1 @@ -79,17 +75,18 @@ class ImportScripts::AnswerHub < ImportScripts::Base results = @client.query(query) break if results.size < 1 - @last_user_id = results.to_a.last['c_id'] + @last_user_id = results.to_a.last["c_id"] create_users(results, total: total_count, offset: offset) do |user| # puts user['c_id'].to_s + ' ' + user['c_name'] - next if @lookup.user_id_from_imported_user_id(user['c_id']) - { id: user['c_id'], + next if @lookup.user_id_from_imported_user_id(user["c_id"]) + { + id: user["c_id"], email: "#{SecureRandom.hex}@invalid.invalid", - username: user['c_name'], - created_at: user['c_creation_date'], - bio_raw: user['c_description'], - last_seen_at: user['c_last_seen'], + username: user["c_name"], + created_at: user["c_creation_date"], + bio_raw: user["c_description"], + last_seen_at: user["c_last_seen"], } end end @@ -99,7 +96,8 @@ class ImportScripts::AnswerHub < ImportScripts::Base puts "", "importing categories..." # Import parent categories first - query = "SELECT c_id, c_name, c_plug, c_parent + query = + "SELECT c_id, c_name, c_plug, c_parent FROM containers WHERE c_type = 'space' AND c_active = 1 @@ -107,15 +105,12 @@ class ImportScripts::AnswerHub < ImportScripts::Base results = @client.query(query) create_categories(results) do |c| - { - id: c['c_id'], - name: c['c_name'], - parent_category_id: check_parent_id(c['c_parent']), - } + { id: c["c_id"], name: c["c_name"], parent_category_id: check_parent_id(c["c_parent"]) } end # Import sub-categories - query = "SELECT c_id, c_name, c_plug, c_parent + query = + "SELECT c_id, c_name, c_plug, c_parent FROM containers WHERE c_type = 'space' AND c_active = 1 @@ -125,9 +120,9 @@ class ImportScripts::AnswerHub < ImportScripts::Base create_categories(results) do |c| # puts c.inspect { - id: c['c_id'], - name: c['c_name'], - parent_category_id: category_id_from_imported_category_id(check_parent_id(c['c_parent'])), + id: c["c_id"], + name: c["c_name"], + parent_category_id: category_id_from_imported_category_id(check_parent_id(c["c_parent"])), } end end @@ -141,7 +136,7 @@ class ImportScripts::AnswerHub < ImportScripts::Base WHERE c_visibility <> 'deleted' AND (c_type = 'question' OR c_type = 'kbentry');" - total_count = @client.query(count_query).first['count'] + total_count = @client.query(count_query).first["count"] @last_topic_id = -1 @@ -159,26 +154,25 @@ class ImportScripts::AnswerHub < ImportScripts::Base topics = @client.query(query) break if topics.size < 1 - @last_topic_id = topics.to_a.last['c_id'] + @last_topic_id = topics.to_a.last["c_id"] create_posts(topics, total: total_count, offset: offset) do |t| - user_id = user_id_from_imported_user_id(t['c_author']) || Discourse::SYSTEM_USER_ID - body = process_mentions(t['c_body']) - if PROCESS_UPLOADS == 1 - body = process_uploads(body, user_id) - end + user_id = user_id_from_imported_user_id(t["c_author"]) || Discourse::SYSTEM_USER_ID + body = process_mentions(t["c_body"]) + body = process_uploads(body, user_id) if PROCESS_UPLOADS == 1 markdown_body = HtmlToMarkdown.new(body).to_markdown { - id: t['c_id'], + id: t["c_id"], user_id: user_id, - title: t['c_title'], - category: category_id_from_imported_category_id(t['c_primaryContainer']), + title: t["c_title"], + category: category_id_from_imported_category_id(t["c_primaryContainer"]), raw: markdown_body, - created_at: t['c_creation_date'], - post_create_action: proc do |post| - tag_names = t['c_topic_names'].split(',') - DiscourseTagging.tag_topic_by_names(post.topic, staff_guardian, tag_names) - end + created_at: t["c_creation_date"], + post_create_action: + proc do |post| + tag_names = t["c_topic_names"].split(",") + DiscourseTagging.tag_topic_by_names(post.topic, staff_guardian, tag_names) + end, } end end @@ -194,7 +188,7 @@ class ImportScripts::AnswerHub < ImportScripts::Base AND (c_type = 'answer' OR c_type = 'comment' OR c_type = 'kbentry');" - total_count = @client.query(count_query).first['count'] + total_count = @client.query(count_query).first["count"] @last_post_id = -1 @@ -210,49 +204,49 @@ class ImportScripts::AnswerHub < ImportScripts::Base ORDER BY c_id ASC LIMIT #{BATCH_SIZE};" posts = @client.query(query) - next if all_records_exist? :posts, posts.map { |p| p['c_id'] } + next if all_records_exist? :posts, posts.map { |p| p["c_id"] } break if posts.size < 1 - @last_post_id = posts.to_a.last['c_id'] + @last_post_id = posts.to_a.last["c_id"] create_posts(posts, total: total_count, offset: offset) do |p| - t = topic_lookup_from_imported_post_id(p['c_originalParent']) + t = topic_lookup_from_imported_post_id(p["c_originalParent"]) next unless t - reply_to_post_id = post_id_from_imported_post_id(p['c_parent']) + reply_to_post_id = post_id_from_imported_post_id(p["c_parent"]) reply_to_post = reply_to_post_id.present? ? Post.find(reply_to_post_id) : nil reply_to_post_number = reply_to_post.present? ? reply_to_post.post_number : nil - user_id = user_id_from_imported_user_id(p['c_author']) || Discourse::SYSTEM_USER_ID + user_id = user_id_from_imported_user_id(p["c_author"]) || Discourse::SYSTEM_USER_ID - body = process_mentions(p['c_body']) - if PROCESS_UPLOADS == 1 - body = process_uploads(body, user_id) - end + body = process_mentions(p["c_body"]) + body = process_uploads(body, user_id) if PROCESS_UPLOADS == 1 markdown_body = HtmlToMarkdown.new(body).to_markdown { - id: p['c_id'], + id: p["c_id"], user_id: user_id, topic_id: t[:topic_id], reply_to_post_number: reply_to_post_number, raw: markdown_body, - created_at: p['c_creation_date'], - post_create_action: proc do |post_info| - begin - if p['c_type'] == 'answer' && p['c_marked'] == 1 - post = Post.find(post_info[:id]) - if post - user_id = user_id_from_imported_user_id(p['c_author']) || Discourse::SYSTEM_USER_ID - current_user = User.find(user_id) - solved = DiscourseSolved.accept_answer!(post, current_user) - # puts "SOLVED: #{solved}" + created_at: p["c_creation_date"], + post_create_action: + proc do |post_info| + begin + if p["c_type"] == "answer" && p["c_marked"] == 1 + post = Post.find(post_info[:id]) + if post + user_id = + user_id_from_imported_user_id(p["c_author"]) || Discourse::SYSTEM_USER_ID + current_user = User.find(user_id) + solved = DiscourseSolved.accept_answer!(post, current_user) + # puts "SOLVED: #{solved}" + end end + rescue ActiveRecord::RecordInvalid + puts "SOLVED: Skipped post_id: #{post.id} because invalid" end - rescue ActiveRecord::RecordInvalid - puts "SOLVED: Skipped post_id: #{post.id} because invalid" - end - end + end, } end end @@ -269,11 +263,7 @@ class ImportScripts::AnswerHub < ImportScripts::Base groups = @client.query(query) create_groups(groups) do |group| - { - id: group["c_id"], - name: group["c_name"], - visibility_level: 1 - } + { id: group["c_id"], name: group["c_name"], visibility_level: 1 } end end @@ -298,11 +288,16 @@ class ImportScripts::AnswerHub < ImportScripts::Base group_members.map groups.each do |group| - dgroup = find_group_by_import_id(group['c_id']) + dgroup = find_group_by_import_id(group["c_id"]) - next if dgroup.custom_fields['import_users_added'] + next if dgroup.custom_fields["import_users_added"] - group_member_ids = group_members.map { |m| user_id_from_imported_user_id(m["c_members"]) if m["c_groups"] == group['c_id'] }.compact + group_member_ids = + group_members + .map do |m| + user_id_from_imported_user_id(m["c_members"]) if m["c_groups"] == group["c_id"] + end + .compact # add members dgroup.bulk_add(group_member_ids) @@ -310,7 +305,7 @@ class ImportScripts::AnswerHub < ImportScripts::Base # reload group dgroup.reload - dgroup.custom_fields['import_users_added'] = true + dgroup.custom_fields["import_users_added"] = true dgroup.save progress_count += 1 @@ -362,7 +357,7 @@ class ImportScripts::AnswerHub < ImportScripts::Base avatars.each do |a| begin - user_id = user_id_from_imported_user_id(a['c_user']) + user_id = user_id_from_imported_user_id(a["c_user"]) user = User.find(user_id) if user filename = "avatar-#{user_id}.png" @@ -371,9 +366,11 @@ class ImportScripts::AnswerHub < ImportScripts::Base # Scrape Avatars - Avatars are saved in the db, but it might be easier to just scrape them if SCRAPE_AVATARS == 1 - File.open(path, 'wb') { |f| - f << open("https://#{ANSWERHUB_DOMAIN}/forums/users/#{a['c_user']}/photo/view.html?s=240").read - } + File.open(path, "wb") do |f| + f << open( + "https://#{ANSWERHUB_DOMAIN}/forums/users/#{a["c_user"]}/photo/view.html?s=240", + ).read + end end upload = @uploader.create_upload(user.id, path, filename) @@ -389,7 +386,7 @@ class ImportScripts::AnswerHub < ImportScripts::Base end end rescue ActiveRecord::RecordNotFound - puts "Could not find User for user_id: #{a['c_user']}" + puts "Could not find User for user_id: #{a["c_user"]}" end end end @@ -438,9 +435,10 @@ class ImportScripts::AnswerHub < ImportScripts::Base raw = body.dup # https://example.forum.com/forums/users/1469/XYZ_Rob.html - raw.gsub!(/(https:\/\/example.forum.com\/forums\/users\/\d+\/[\w_%-.]*.html)/) do + raw.gsub!(%r{(https://example.forum.com/forums/users/\d+/[\w_%-.]*.html)}) do legacy_url = $1 - import_user_id = legacy_url.match(/https:\/\/example.forum.com\/forums\/users\/(\d+)\/[\w_%-.]*.html/).captures + import_user_id = + legacy_url.match(%r{https://example.forum.com/forums/users/(\d+)/[\w_%-.]*.html}).captures user = @lookup.find_user_by_import_id(import_user_id[0]) if user.present? @@ -453,9 +451,9 @@ class ImportScripts::AnswerHub < ImportScripts::Base end # /forums/users/395/petrocket.html - raw.gsub!(/(\/forums\/users\/\d+\/[\w_%-.]*.html)/) do + raw.gsub!(%r{(/forums/users/\d+/[\w_%-.]*.html)}) do legacy_url = $1 - import_user_id = legacy_url.match(/\/forums\/users\/(\d+)\/[\w_%-.]*.html/).captures + import_user_id = legacy_url.match(%r{/forums/users/(\d+)/[\w_%-.]*.html}).captures # puts raw user = @lookup.find_user_by_import_id(import_user_id[0]) @@ -472,7 +470,7 @@ class ImportScripts::AnswerHub < ImportScripts::Base end def create_permalinks - puts '', 'Creating redirects...', '' + puts "", "Creating redirects...", "" # https://example.forum.com/forums/questions/2005/missing-file.html Topic.find_each do |topic| @@ -480,8 +478,12 @@ class ImportScripts::AnswerHub < ImportScripts::Base if pcf && pcf["import_id"] id = pcf["import_id"] slug = Slug.for(topic.title) - Permalink.create(url: "questions/#{id}/#{slug}.html", topic_id: topic.id) rescue nil - print '.' + begin + Permalink.create(url: "questions/#{id}/#{slug}.html", topic_id: topic.id) + rescue StandardError + nil + end + print "." end end end @@ -496,7 +498,6 @@ class ImportScripts::AnswerHub < ImportScripts::Base return CATEGORY_MAP_TO if CATEGORY_MAP_FROM > 0 && id == CATEGORY_MAP_FROM id end - end ImportScripts::AnswerHub.new.perform diff --git a/script/import_scripts/askbot.rb b/script/import_scripts/askbot.rb index 7bc7866a0e..537c5ba71f 100644 --- a/script/import_scripts/askbot.rb +++ b/script/import_scripts/askbot.rb @@ -1,23 +1,23 @@ # frozen_string_literal: true require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require 'pg' +require "pg" class ImportScripts::MyAskBot < ImportScripts::Base # CHANGE THESE BEFORE RUNNING THE IMPORTER BATCH_SIZE = 1000 - OLD_SITE = "ask.cvxr.com" - DB_NAME = "cvxforum" - DB_USER = "cvxforum" - DB_PORT = 5432 - DB_HOST = "ask.cvxr.com" - DB_PASS = 'yeah, right' + OLD_SITE = "ask.cvxr.com" + DB_NAME = "cvxforum" + DB_USER = "cvxforum" + DB_PORT = 5432 + DB_HOST = "ask.cvxr.com" + DB_PASS = "yeah, right" # A list of categories to create. Any post with one of these tags will be # assigned to that category. Ties are broken by list order. - CATEGORIES = [ 'Nonconvex', 'TFOCS', 'MIDCP', 'FAQ' ] + CATEGORIES = %w[Nonconvex TFOCS MIDCP FAQ] def initialize super @@ -25,13 +25,8 @@ class ImportScripts::MyAskBot < ImportScripts::Base @thread_parents = {} @tagmap = [] @td = PG::TextDecoder::TimestampWithTimeZone.new - @client = PG.connect( - dbname: DB_NAME, - host: DB_HOST, - port: DB_PORT, - user: DB_USER, - password: DB_PASS - ) + @client = + PG.connect(dbname: DB_NAME, host: DB_HOST, port: DB_PORT, user: DB_USER, password: DB_PASS) end def execute @@ -55,18 +50,17 @@ class ImportScripts::MyAskBot < ImportScripts::Base def read_tags puts "", "reading thread tags..." - tag_count = @client.exec(<<-SQL + tag_count = @client.exec(<<-SQL)[0]["count"] SELECT COUNT(A.id) FROM askbot_thread_tags A JOIN tag B ON A.tag_id = B.id WHERE A.tag_id > 0 SQL - )[0]["count"] tags_done = 0 batches(BATCH_SIZE) do |offset| - tags = @client.exec(<<-SQL + tags = @client.exec(<<-SQL) SELECT A.thread_id, B.name FROM askbot_thread_tags A JOIN tag B @@ -75,7 +69,6 @@ class ImportScripts::MyAskBot < ImportScripts::Base LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ) break if tags.ntuples() < 1 tags.each do |tag| tid = tag["thread_id"].to_i @@ -83,7 +76,7 @@ class ImportScripts::MyAskBot < ImportScripts::Base if @tagmap[tid] @tagmap[tid].push(tnm) else - @tagmap[tid] = [ tnm ] + @tagmap[tid] = [tnm] end tags_done += 1 print_status tags_done, tag_count @@ -94,21 +87,19 @@ class ImportScripts::MyAskBot < ImportScripts::Base def import_users puts "", "importing users" - total_count = @client.exec(<<-SQL + total_count = @client.exec(<<-SQL)[0]["count"] SELECT COUNT(id) FROM auth_user SQL - )[0]["count"] batches(BATCH_SIZE) do |offset| - users = @client.query(<<-SQL + users = @client.query(<<-SQL) SELECT id, username, email, is_staff, date_joined, last_seen, real_name, website, location, about FROM auth_user ORDER BY date_joined LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ) break if users.ntuples() < 1 @@ -133,17 +124,16 @@ class ImportScripts::MyAskBot < ImportScripts::Base def import_posts puts "", "importing questions..." - post_count = @client.exec(<<-SQL + post_count = @client.exec(<<-SQL)[0]["count"] SELECT COUNT(A.id) FROM askbot_post A JOIN askbot_thread B ON A.thread_id = B.id WHERE NOT B.closed AND A.post_type='question' SQL - )[0]["count"] batches(BATCH_SIZE) do |offset| - posts = @client.exec(<<-SQL + posts = @client.exec(<<-SQL) SELECT A.id, A.author_id, A.added_at, A.text, A.thread_id, B.title FROM askbot_post A JOIN askbot_thread B @@ -153,7 +143,6 @@ class ImportScripts::MyAskBot < ImportScripts::Base LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ) break if posts.ntuples() < 1 @@ -176,7 +165,11 @@ class ImportScripts::MyAskBot < ImportScripts::Base id: pid, title: post["title"], category: cat, - custom_fields: { import_id: pid, import_thread_id: tid, import_tags: tags }, + custom_fields: { + import_id: pid, + import_thread_id: tid, + import_tags: tags, + }, user_id: user_id_from_imported_user_id(post["author_id"]) || Discourse::SYSTEM_USER_ID, created_at: Time.zone.at(@td.decode(post["added_at"])), raw: post["text"], @@ -188,17 +181,16 @@ class ImportScripts::MyAskBot < ImportScripts::Base def import_replies puts "", "importing answers and comments..." - post_count = @client.exec(<<-SQL + post_count = @client.exec(<<-SQL)[0]["count"] SELECT COUNT(A.id) FROM askbot_post A JOIN askbot_thread B ON A.thread_id = B.id WHERE NOT B.closed AND A.post_type<>'question' SQL - )[0]["count"] batches(BATCH_SIZE) do |offset| - posts = @client.exec(<<-SQL + posts = @client.exec(<<-SQL) SELECT A.id, A.author_id, A.added_at, A.text, A.thread_id, B.title FROM askbot_post A JOIN askbot_thread B @@ -208,7 +200,6 @@ class ImportScripts::MyAskBot < ImportScripts::Base LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ) break if posts.ntuples() < 1 @@ -222,10 +213,12 @@ class ImportScripts::MyAskBot < ImportScripts::Base { id: pid, topic_id: parent[:topic_id], - custom_fields: { import_id: pid }, + custom_fields: { + import_id: pid, + }, user_id: user_id_from_imported_user_id(post["author_id"]) || Discourse::SYSTEM_USER_ID, created_at: Time.zone.at(@td.decode(post["added_at"])), - raw: post["text"] + raw: post["text"], } end end @@ -240,32 +233,37 @@ class ImportScripts::MyAskBot < ImportScripts::Base # I am sure this is incomplete, but we didn't make heavy use of internal # links on our site. tmp = Regexp.quote("http://#{OLD_SITE}") - r1 = /"(#{tmp})?\/question\/(\d+)\/[a-zA-Z-]*\/?"/ - r2 = /\((#{tmp})?\/question\/(\d+)\/[a-zA-Z-]*\/?\)/ - r3 = /?/ + r1 = %r{"(#{tmp})?/question/(\d+)/[a-zA-Z-]*/?"} + r2 = %r{\((#{tmp})?/question/(\d+)/[a-zA-Z-]*/?\)} + r3 = %r{?} Post.find_each do |post| - raw = post.raw.gsub(r1) do - if topic = topic_lookup_from_imported_post_id($2) - "\"#{topic[:url]}\"" - else - $& + raw = + post + .raw + .gsub(r1) do + if topic = topic_lookup_from_imported_post_id($2) + "\"#{topic[:url]}\"" + else + $& + end + end + raw = + raw.gsub(r2) do + if topic = topic_lookup_from_imported_post_id($2) + "(#{topic[:url]})" + else + $& + end end - end - raw = raw.gsub(r2) do - if topic = topic_lookup_from_imported_post_id($2) - "(#{topic[:url]})" - else - $& + raw = + raw.gsub(r3) do + if topic = topic_lookup_from_imported_post_id($1) + trec = Topic.find_by(id: topic[:topic_id]) + "[#{trec.title}](#{topic[:url]})" + else + $& + end end - end - raw = raw.gsub(r3) do - if topic = topic_lookup_from_imported_post_id($1) - trec = Topic.find_by(id: topic[:topic_id]) - "[#{trec.title}](#{topic[:url]})" - else - $& - end - end if raw != post.raw post.raw = raw diff --git a/script/import_scripts/base.rb b/script/import_scripts/base.rb index 1345f206d7..7dbca7d353 100644 --- a/script/import_scripts/base.rb +++ b/script/import_scripts/base.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -if ARGV.include?('bbcode-to-md') +if ARGV.include?("bbcode-to-md") # Replace (most) bbcode with markdown before creating posts. # This will dramatically clean up the final posts in Discourse. # @@ -10,17 +10,17 @@ if ARGV.include?('bbcode-to-md') # cd ruby-bbcode-to-md # gem build ruby-bbcode-to-md.gemspec # gem install ruby-bbcode-to-md-*.gem - require 'ruby-bbcode-to-md' + require "ruby-bbcode-to-md" end -require_relative '../../config/environment' -require_relative 'base/lookup_container' -require_relative 'base/uploader' +require_relative "../../config/environment" +require_relative "base/lookup_container" +require_relative "base/uploader" -module ImportScripts; end +module ImportScripts +end class ImportScripts::Base - def initialize preload_i18n @@ -62,15 +62,14 @@ class ImportScripts::Base end elapsed = Time.now - @start_times[:import] - puts '', '', 'Done (%02dh %02dmin %02dsec)' % [elapsed / 3600, elapsed / 60 % 60, elapsed % 60] - + puts "", "", "Done (%02dh %02dmin %02dsec)" % [elapsed / 3600, elapsed / 60 % 60, elapsed % 60] ensure reset_site_settings end def get_site_settings_for_import { - blocked_email_domains: '', + blocked_email_domains: "", min_topic_title_length: 1, min_post_length: 1, min_first_post_length: 1, @@ -78,21 +77,23 @@ class ImportScripts::Base min_personal_message_title_length: 1, allow_duplicate_topic_titles: true, allow_duplicate_topic_titles_category: false, - disable_emails: 'yes', - max_attachment_size_kb: 102400, - max_image_size_kb: 102400, - authorized_extensions: '*', + disable_emails: "yes", + max_attachment_size_kb: 102_400, + max_image_size_kb: 102_400, + authorized_extensions: "*", clean_up_inactive_users_after_days: 0, clean_up_unused_staged_users_after_days: 0, clean_up_uploads: false, - clean_orphan_uploads_grace_period_hours: 1800 + clean_orphan_uploads_grace_period_hours: 1800, } end def change_site_settings if SiteSetting.bootstrap_mode_enabled - SiteSetting.default_trust_level = TrustLevel[0] if SiteSetting.default_trust_level == TrustLevel[1] - SiteSetting.default_email_digest_frequency = 10080 if SiteSetting.default_email_digest_frequency == 1440 + SiteSetting.default_trust_level = TrustLevel[0] if SiteSetting.default_trust_level == + TrustLevel[1] + SiteSetting.default_email_digest_frequency = + 10_080 if SiteSetting.default_email_digest_frequency == 1440 SiteSetting.bootstrap_mode_enabled = false end @@ -131,7 +132,7 @@ class ImportScripts::Base raise NotImplementedError end - %i{ + %i[ add_category add_group add_post @@ -146,9 +147,7 @@ class ImportScripts::Base topic_lookup_from_imported_post_id user_already_imported? user_id_from_imported_user_id - }.each do |method_name| - delegate method_name, to: :@lookup - end + ].each { |method_name| delegate method_name, to: :@lookup } def create_admin(opts = {}) admin = User.new @@ -196,7 +195,11 @@ class ImportScripts::Base end end - print_status(created + skipped + failed + (opts[:offset] || 0), total, get_start_time("groups")) + print_status( + created + skipped + failed + (opts[:offset] || 0), + total, + get_start_time("groups"), + ) end [created, skipped] @@ -224,23 +227,22 @@ class ImportScripts::Base ActiveRecord::Base.transaction do begin connection = ActiveRecord::Base.connection.raw_connection - connection.exec('CREATE TEMP TABLE import_ids(val text PRIMARY KEY)') + connection.exec("CREATE TEMP TABLE import_ids(val text PRIMARY KEY)") - import_id_clause = import_ids.map { |id| "('#{PG::Connection.escape_string(id.to_s)}')" }.join(",") + import_id_clause = + import_ids.map { |id| "('#{PG::Connection.escape_string(id.to_s)}')" }.join(",") connection.exec("INSERT INTO import_ids VALUES #{import_id_clause}") existing = "#{type.to_s.classify}CustomField".constantize - existing = existing.where(name: 'import_id') - .joins('JOIN import_ids ON val = value') - .count + existing = existing.where(name: "import_id").joins("JOIN import_ids ON val = value").count if existing == import_ids.length puts "Skipping #{import_ids.length} already imported #{type}" true end ensure - connection.exec('DROP TABLE import_ids') unless connection.nil? + connection.exec("DROP TABLE import_ids") unless connection.nil? end end end @@ -292,7 +294,11 @@ class ImportScripts::Base end end - print_status(created + skipped + failed + (opts[:offset] || 0), total, get_start_time("users")) + print_status( + created + skipped + failed + (opts[:offset] || 0), + total, + get_start_time("users"), + ) end [created, skipped] @@ -305,7 +311,9 @@ class ImportScripts::Base post_create_action = opts.delete(:post_create_action) existing = find_existing_user(opts[:email], opts[:username]) - return existing if existing && (merge || existing.custom_fields["import_id"].to_s == import_id.to_s) + if existing && (merge || existing.custom_fields["import_id"].to_s == import_id.to_s) + return existing + end bio_raw = opts.delete(:bio_raw) website = opts.delete(:website) @@ -316,8 +324,11 @@ class ImportScripts::Base original_name = opts[:name] original_email = opts[:email] = opts[:email].downcase - if !UsernameValidator.new(opts[:username]).valid_format? || !User.username_available?(opts[:username]) - opts[:username] = UserNameSuggester.suggest(opts[:username].presence || opts[:name].presence || opts[:email]) + if !UsernameValidator.new(opts[:username]).valid_format? || + !User.username_available?(opts[:username]) + opts[:username] = UserNameSuggester.suggest( + opts[:username].presence || opts[:name].presence || opts[:email], + ) end if !EmailAddressValidator.valid_value?(opts[:email]) @@ -339,7 +350,8 @@ class ImportScripts::Base u = User.new(opts) (opts[:custom_fields] || {}).each { |k, v| u.custom_fields[k] = v } u.custom_fields["import_id"] = import_id - u.custom_fields["import_username"] = original_username if original_username.present? && original_username != opts[:username] + u.custom_fields["import_username"] = original_username if original_username.present? && + original_username != opts[:username] u.custom_fields["import_avatar_url"] = avatar_url if avatar_url.present? u.custom_fields["import_pass"] = opts[:password] if opts[:password].present? u.custom_fields["import_email"] = original_email if original_email != opts[:email] @@ -359,9 +371,7 @@ class ImportScripts::Base end end - if opts[:active] && opts[:password].present? - u.activate - end + u.activate if opts[:active] && opts[:password].present? rescue => e # try based on email if e.try(:record).try(:errors).try(:messages).try(:[], :primary_email).present? @@ -377,7 +387,7 @@ class ImportScripts::Base end end - if u.custom_fields['import_email'] + if u.custom_fields["import_email"] u.suspended_at = Time.zone.at(Time.now) u.suspended_till = 200.years.from_now u.save! @@ -388,11 +398,15 @@ class ImportScripts::Base user_option.email_messages_level = UserOption.email_level_types[:never] user_option.save! if u.save - StaffActionLogger.new(Discourse.system_user).log_user_suspend(u, 'Invalid email address on import') + StaffActionLogger.new(Discourse.system_user).log_user_suspend( + u, + "Invalid email address on import", + ) else - Rails.logger.error("Failed to suspend user #{u.username}. #{u.errors.try(:full_messages).try(:inspect)}") + Rails.logger.error( + "Failed to suspend user #{u.username}. #{u.errors.try(:full_messages).try(:inspect)}", + ) end - end post_create_action.try(:call, u) if u.persisted? @@ -402,7 +416,8 @@ class ImportScripts::Base def find_existing_user(email, username) # Force the use of the index on the 'user_emails' table - UserEmail.where("lower(email) = ?", email.downcase).first&.user || User.where(username: username).first + UserEmail.where("lower(email) = ?", email.downcase).first&.user || + User.where(username: username).first end def created_category(category) @@ -435,7 +450,8 @@ class ImportScripts::Base # make sure categories don't go more than 2 levels deep if params[:parent_category_id] top = Category.find_by_id(params[:parent_category_id]) - top = top.parent_category while (top&.height_of_ancestors || -1) + 1 >= SiteSetting.max_category_nesting + top = top.parent_category while (top&.height_of_ancestors || -1) + 1 >= + SiteSetting.max_category_nesting params[:parent_category_id] = top.id if top end @@ -471,15 +487,16 @@ class ImportScripts::Base post_create_action = opts.delete(:post_create_action) - new_category = Category.new( - name: opts[:name], - user_id: opts[:user_id] || opts[:user].try(:id) || Discourse::SYSTEM_USER_ID, - position: opts[:position], - parent_category_id: opts[:parent_category_id], - color: opts[:color] || category_color(opts[:parent_category_id]), - text_color: opts[:text_color] || "FFF", - read_restricted: opts[:read_restricted] || false, - ) + new_category = + Category.new( + name: opts[:name], + user_id: opts[:user_id] || opts[:user].try(:id) || Discourse::SYSTEM_USER_ID, + position: opts[:position], + parent_category_id: opts[:parent_category_id], + color: opts[:color] || category_color(opts[:parent_category_id]), + text_color: opts[:text_color] || "FFF", + read_restricted: opts[:read_restricted] || false, + ) new_category.custom_fields["import_id"] = import_id if import_id new_category.save! @@ -498,10 +515,16 @@ class ImportScripts::Base end def category_color(parent_category_id) - @category_colors ||= SiteSetting.category_colors.split('|') + @category_colors ||= SiteSetting.category_colors.split("|") index = @next_category_color_index[parent_category_id].presence || 0 - @next_category_color_index[parent_category_id] = index + 1 >= @category_colors.count ? 0 : index + 1 + @next_category_color_index[parent_category_id] = ( + if index + 1 >= @category_colors.count + 0 + else + index + 1 + end + ) @category_colors[index] end @@ -571,7 +594,7 @@ class ImportScripts::Base opts = opts.merge(skip_validations: true) opts[:import_mode] = true opts[:custom_fields] ||= {} - opts[:custom_fields]['import_id'] = import_id + opts[:custom_fields]["import_id"] = import_id unless opts[:topic_id] opts[:meta_data] = meta_data = {} @@ -582,7 +605,11 @@ class ImportScripts::Base opts[:guardian] = STAFF_GUARDIAN if @bbcode_to_md - opts[:raw] = opts[:raw].bbcode_to_md(false, {}, :disable, :quote) rescue opts[:raw] + opts[:raw] = begin + opts[:raw].bbcode_to_md(false, {}, :disable, :quote) + rescue StandardError + opts[:raw] + end end post_creator = PostCreator.new(user, opts) @@ -628,7 +655,7 @@ class ImportScripts::Base created += 1 if manager.errors.none? skipped += 1 if manager.errors.any? - rescue + rescue StandardError skipped += 1 end end @@ -671,14 +698,14 @@ class ImportScripts::Base def close_inactive_topics(opts = {}) num_days = opts[:days] || 30 - puts '', "Closing topics that have been inactive for more than #{num_days} days." + puts "", "Closing topics that have been inactive for more than #{num_days} days." - query = Topic.where('last_posted_at < ?', num_days.days.ago).where(closed: false) + query = Topic.where("last_posted_at < ?", num_days.days.ago).where(closed: false) total_count = query.count closed_count = 0 query.find_each do |topic| - topic.update_status('closed', true, Discourse.system_user) + topic.update_status("closed", true, Discourse.system_user) closed_count += 1 print_status(closed_count, total_count, get_start_time("close_inactive_topics")) end @@ -790,7 +817,9 @@ class ImportScripts::Base puts "", "Updating user digest_attempted_at..." - DB.exec("UPDATE user_stats SET digest_attempted_at = now() - random() * interval '1 week' WHERE digest_attempted_at IS NULL") + DB.exec( + "UPDATE user_stats SET digest_attempted_at = now() - random() * interval '1 week' WHERE digest_attempted_at IS NULL", + ) end # scripts that are able to import last_seen_at from the source data should override this method @@ -854,13 +883,15 @@ class ImportScripts::Base count = 0 total = User.count - User.includes(:user_stat).find_each do |user| - begin - user.update_columns(trust_level: 0) if user.trust_level > 0 && user.post_count == 0 - rescue Discourse::InvalidAccess + User + .includes(:user_stat) + .find_each do |user| + begin + user.update_columns(trust_level: 0) if user.trust_level > 0 && user.post_count == 0 + rescue Discourse::InvalidAccess + end + print_status(count += 1, total, get_start_time("update_tl0")) end - print_status(count += 1, total, get_start_time("update_tl0")) - end end def update_user_signup_date_based_on_first_post @@ -870,7 +901,7 @@ class ImportScripts::Base total = User.count User.find_each do |user| - if first = user.posts.order('created_at ASC').first + if first = user.posts.order("created_at ASC").first user.created_at = first.created_at user.save! end @@ -893,16 +924,16 @@ class ImportScripts::Base def print_status(current, max, start_time = nil) if start_time.present? elapsed_seconds = Time.now - start_time - elements_per_minute = '[%.0f items/min] ' % [current / elapsed_seconds.to_f * 60] + elements_per_minute = "[%.0f items/min] " % [current / elapsed_seconds.to_f * 60] else - elements_per_minute = '' + elements_per_minute = "" end print "\r%9d / %d (%5.1f%%) %s" % [current, max, current / max.to_f * 100, elements_per_minute] end def print_spinner - @spinner_chars ||= %w{ | / - \\ } + @spinner_chars ||= %w[| / - \\] @spinner_chars.push @spinner_chars.shift print "\b#{@spinner_chars[0]}" end diff --git a/script/import_scripts/base/csv_helper.rb b/script/import_scripts/base/csv_helper.rb index 7f7becbd3d..3f211ea366 100644 --- a/script/import_scripts/base/csv_helper.rb +++ b/script/import_scripts/base/csv_helper.rb @@ -13,65 +13,69 @@ module ImportScripts def initialize(cols) cols.each_with_index do |col, idx| - self.class.public_send(:define_method, col.downcase.gsub(/[\W]/, '_').squeeze('_')) do - @row[idx] - end + self + .class + .public_send(:define_method, col.downcase.gsub(/[\W]/, "_").squeeze("_")) { @row[idx] } end end end - def csv_parse(filename, col_sep = ',') + def csv_parse(filename, col_sep = ",") first = true row = nil current_row = +"" double_quote_count = 0 - File.open(filename).each_line do |line| + File + .open(filename) + .each_line do |line| + line.strip! - line.strip! + current_row << "\n" unless current_row.empty? + current_row << line - current_row << "\n" unless current_row.empty? - current_row << line + double_quote_count += line.scan('"').count - double_quote_count += line.scan('"').count + next if double_quote_count % 2 == 1 # this row continues on a new line. don't parse until we have the whole row. - next if double_quote_count % 2 == 1 # this row continues on a new line. don't parse until we have the whole row. + raw = + begin + CSV.parse(current_row, col_sep: col_sep) + rescue CSV::MalformedCSVError => e + puts e.message + puts "*" * 100 + puts "Bad row skipped, line is: #{line}" + puts + puts current_row + puts + puts "double quote count is : #{double_quote_count}" + puts "*" * 100 - raw = begin - CSV.parse(current_row, col_sep: col_sep) - rescue CSV::MalformedCSVError => e - puts e.message - puts "*" * 100 - puts "Bad row skipped, line is: #{line}" - puts - puts current_row - puts - puts "double quote count is : #{double_quote_count}" - puts "*" * 100 + current_row = "" + double_quote_count = 0 - current_row = "" - double_quote_count = 0 + next + end[ + 0 + ] - next - end[0] + if first + row = RowResolver.create(raw) - if first - row = RowResolver.create(raw) + current_row = "" + double_quote_count = 0 + first = false + next + end + + row.load(raw) + + yield row current_row = "" double_quote_count = 0 - first = false - next end - - row.load(raw) - - yield row - - current_row = "" - double_quote_count = 0 - end end end end diff --git a/script/import_scripts/base/generic_database.rb b/script/import_scripts/base/generic_database.rb index 28bfd8ee3b..dafea94199 100644 --- a/script/import_scripts/base/generic_database.rb +++ b/script/import_scripts/base/generic_database.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'sqlite3' +require "sqlite3" module ImportScripts class GenericDatabase @@ -80,24 +80,20 @@ module ImportScripts VALUES (:id, :raw, :topic_id, :user_id, :created_at, :reply_to_post_id, :url, :upload_count) SQL - attachments&.each do |attachment| - @db.execute(<<-SQL, post_id: post[:id], path: attachment) + attachments&.each { |attachment| @db.execute(<<-SQL, post_id: post[:id], path: attachment) } INSERT OR REPLACE INTO post_upload (post_id, path) VALUES (:post_id, :path) SQL - end - like_user_ids&.each do |user_id| - @db.execute(<<-SQL, post_id: post[:id], user_id: user_id) + like_user_ids&.each { |user_id| @db.execute(<<-SQL, post_id: post[:id], user_id: user_id) } INSERT OR REPLACE INTO like (post_id, user_id) VALUES (:post_id, :user_id) SQL - end end end def sort_posts_by_created_at - @db.execute 'DELETE FROM post_order' + @db.execute "DELETE FROM post_order" @db.execute <<-SQL INSERT INTO post_order (post_id) @@ -146,7 +142,7 @@ module ImportScripts LIMIT #{@batch_size} SQL - add_last_column_value(rows, 'id') + add_last_column_value(rows, "id") end def get_user_id(username) @@ -173,7 +169,7 @@ module ImportScripts LIMIT #{@batch_size} SQL - add_last_column_value(rows, 'id') + add_last_column_value(rows, "id") end def fetch_topic_attachments(topic_id) @@ -200,7 +196,7 @@ module ImportScripts LIMIT #{@batch_size} SQL - add_last_column_value(rows, 'rowid') + add_last_column_value(rows, "rowid") end def fetch_sorted_posts(last_row_id) @@ -213,7 +209,7 @@ module ImportScripts LIMIT #{@batch_size} SQL - add_last_column_value(rows, 'rowid') + add_last_column_value(rows, "rowid") end def fetch_post_attachments(post_id) @@ -240,7 +236,7 @@ module ImportScripts LIMIT #{@batch_size} SQL - add_last_column_value(rows, 'rowid') + add_last_column_value(rows, "rowid") end def execute_sql(sql) @@ -254,12 +250,12 @@ module ImportScripts private def configure_database - @db.execute 'PRAGMA journal_mode = OFF' - @db.execute 'PRAGMA locking_mode = EXCLUSIVE' + @db.execute "PRAGMA journal_mode = OFF" + @db.execute "PRAGMA locking_mode = EXCLUSIVE" end def key_data_type - @numeric_keys ? 'INTEGER' : 'TEXT' + @numeric_keys ? "INTEGER" : "TEXT" end def create_category_table @@ -299,7 +295,7 @@ module ImportScripts ) SQL - @db.execute 'CREATE INDEX IF NOT EXISTS user_by_username ON user (username)' + @db.execute "CREATE INDEX IF NOT EXISTS user_by_username ON user (username)" end def create_topic_table @@ -317,7 +313,7 @@ module ImportScripts ) SQL - @db.execute 'CREATE INDEX IF NOT EXISTS topic_by_user_id ON topic (user_id)' + @db.execute "CREATE INDEX IF NOT EXISTS topic_by_user_id ON topic (user_id)" @db.execute <<-SQL CREATE TABLE IF NOT EXISTS topic_upload ( @@ -326,7 +322,7 @@ module ImportScripts ) SQL - @db.execute 'CREATE UNIQUE INDEX IF NOT EXISTS topic_upload_unique ON topic_upload(topic_id, path)' + @db.execute "CREATE UNIQUE INDEX IF NOT EXISTS topic_upload_unique ON topic_upload(topic_id, path)" end def create_post_table @@ -343,7 +339,7 @@ module ImportScripts ) SQL - @db.execute 'CREATE INDEX IF NOT EXISTS post_by_user_id ON post (user_id)' + @db.execute "CREATE INDEX IF NOT EXISTS post_by_user_id ON post (user_id)" @db.execute <<-SQL CREATE TABLE IF NOT EXISTS post_order ( @@ -358,7 +354,7 @@ module ImportScripts ) SQL - @db.execute 'CREATE UNIQUE INDEX IF NOT EXISTS post_upload_unique ON post_upload(post_id, path)' + @db.execute "CREATE UNIQUE INDEX IF NOT EXISTS post_upload_unique ON post_upload(post_id, path)" end def prepare(hash) diff --git a/script/import_scripts/base/lookup_container.rb b/script/import_scripts/base/lookup_container.rb index 30ac96203d..4147daf43f 100644 --- a/script/import_scripts/base/lookup_container.rb +++ b/script/import_scripts/base/lookup_container.rb @@ -3,27 +3,26 @@ module ImportScripts class LookupContainer def initialize - puts 'Loading existing groups...' - @groups = GroupCustomField.where(name: 'import_id').pluck(:value, :group_id).to_h + puts "Loading existing groups..." + @groups = GroupCustomField.where(name: "import_id").pluck(:value, :group_id).to_h - puts 'Loading existing users...' - @users = UserCustomField.where(name: 'import_id').pluck(:value, :user_id).to_h + puts "Loading existing users..." + @users = UserCustomField.where(name: "import_id").pluck(:value, :user_id).to_h - puts 'Loading existing categories...' - @categories = CategoryCustomField.where(name: 'import_id').pluck(:value, :category_id).to_h + puts "Loading existing categories..." + @categories = CategoryCustomField.where(name: "import_id").pluck(:value, :category_id).to_h - puts 'Loading existing posts...' - @posts = PostCustomField.where(name: 'import_id').pluck(:value, :post_id).to_h + puts "Loading existing posts..." + @posts = PostCustomField.where(name: "import_id").pluck(:value, :post_id).to_h - puts 'Loading existing topics...' + puts "Loading existing topics..." @topics = {} - Post.joins(:topic).pluck('posts.id, posts.topic_id, posts.post_number, topics.slug').each do |p| - @topics[p[0]] = { - topic_id: p[1], - post_number: p[2], - url: Post.url(p[3], p[1], p[2]) - } - end + Post + .joins(:topic) + .pluck("posts.id, posts.topic_id, posts.post_number, topics.slug") + .each do |p| + @topics[p[0]] = { topic_id: p[1], post_number: p[2], url: Post.url(p[3], p[1], p[2]) } + end end # Get the Discourse Post id based on the id of the source record @@ -44,7 +43,7 @@ module ImportScripts # Get the Discourse Group based on the id of the source group def find_group_by_import_id(import_id) - GroupCustomField.where(name: 'import_id', value: import_id.to_s).first.try(:group) + GroupCustomField.where(name: "import_id", value: import_id.to_s).first.try(:group) end # Get the Discourse User id based on the id of the source user @@ -54,7 +53,7 @@ module ImportScripts # Get the Discourse User based on the id of the source user def find_user_by_import_id(import_id) - UserCustomField.where(name: 'import_id', value: import_id.to_s).first.try(:user) + UserCustomField.where(name: "import_id", value: import_id.to_s).first.try(:user) end def find_username_by_import_id(import_id) @@ -84,11 +83,7 @@ module ImportScripts end def add_topic(post) - @topics[post.id] = { - post_number: post.post_number, - topic_id: post.topic_id, - url: post.url, - } + @topics[post.id] = { post_number: post.post_number, topic_id: post.topic_id, url: post.url } end def user_already_imported?(import_id) @@ -98,6 +93,5 @@ module ImportScripts def post_already_imported?(import_id) @posts.has_key?(import_id) || @posts.has_key?(import_id.to_s) end - end end diff --git a/script/import_scripts/base/uploader.rb b/script/import_scripts/base/uploader.rb index 45404bba21..4342d32887 100644 --- a/script/import_scripts/base/uploader.rb +++ b/script/import_scripts/base/uploader.rb @@ -13,8 +13,16 @@ module ImportScripts STDERR.puts "Failed to create upload: #{e}" nil ensure - tmp.close rescue nil - tmp.unlink rescue nil + begin + tmp.close + rescue StandardError + nil + end + begin + tmp.unlink + rescue StandardError + nil + end end def create_avatar(user, avatar_path) @@ -30,7 +38,7 @@ module ImportScripts STDERR.puts "Failed to upload avatar for user #{user.username}: #{avatar_path}" STDERR.puts upload.errors.inspect if upload end - rescue + rescue StandardError STDERR.puts "Failed to create avatar for user #{user.username}: #{avatar_path}" ensure tempfile.close! if tempfile @@ -52,11 +60,9 @@ module ImportScripts def copy_to_tempfile(source_path) extension = File.extname(source_path) - tmp = Tempfile.new(['discourse-upload', extension]) + tmp = Tempfile.new(["discourse-upload", extension]) - File.open(source_path) do |source_stream| - IO.copy_stream(source_stream, tmp) - end + File.open(source_path) { |source_stream| IO.copy_stream(source_stream, tmp) } tmp.rewind tmp diff --git a/script/import_scripts/bbpress.rb b/script/import_scripts/bbpress.rb index 2cd0698d12..4864ba6b94 100644 --- a/script/import_scripts/bbpress.rb +++ b/script/import_scripts/bbpress.rb @@ -1,29 +1,29 @@ # frozen_string_literal: true -require 'mysql2' +require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::Bbpress < ImportScripts::Base - - BB_PRESS_HOST ||= ENV['BBPRESS_HOST'] || "localhost" - BB_PRESS_DB ||= ENV['BBPRESS_DB'] || "bbpress" - BATCH_SIZE ||= 1000 - BB_PRESS_PW ||= ENV['BBPRESS_PW'] || "" - BB_PRESS_USER ||= ENV['BBPRESS_USER'] || "root" - BB_PRESS_PREFIX ||= ENV['BBPRESS_PREFIX'] || "wp_" - BB_PRESS_ATTACHMENTS_DIR ||= ENV['BBPRESS_ATTACHMENTS_DIR'] || "/path/to/attachments" + BB_PRESS_HOST ||= ENV["BBPRESS_HOST"] || "localhost" + BB_PRESS_DB ||= ENV["BBPRESS_DB"] || "bbpress" + BATCH_SIZE ||= 1000 + BB_PRESS_PW ||= ENV["BBPRESS_PW"] || "" + BB_PRESS_USER ||= ENV["BBPRESS_USER"] || "root" + BB_PRESS_PREFIX ||= ENV["BBPRESS_PREFIX"] || "wp_" + BB_PRESS_ATTACHMENTS_DIR ||= ENV["BBPRESS_ATTACHMENTS_DIR"] || "/path/to/attachments" def initialize super @he = HTMLEntities.new - @client = Mysql2::Client.new( - host: BB_PRESS_HOST, - username: BB_PRESS_USER, - database: BB_PRESS_DB, - password: BB_PRESS_PW, - ) + @client = + Mysql2::Client.new( + host: BB_PRESS_HOST, + username: BB_PRESS_USER, + database: BB_PRESS_DB, + password: BB_PRESS_PW, + ) end def execute @@ -40,17 +40,16 @@ class ImportScripts::Bbpress < ImportScripts::Base puts "", "importing users..." last_user_id = -1 - total_users = bbpress_query(<<-SQL + total_users = bbpress_query(<<-SQL).first["cnt"] SELECT COUNT(DISTINCT(u.id)) AS cnt FROM #{BB_PRESS_PREFIX}users u LEFT JOIN #{BB_PRESS_PREFIX}posts p ON p.post_author = u.id WHERE p.post_type IN ('forum', 'reply', 'topic') AND user_email LIKE '%@%' SQL - ).first["cnt"] batches(BATCH_SIZE) do |offset| - users = bbpress_query(<<-SQL + users = bbpress_query(<<-SQL).to_a SELECT u.id, user_nicename, display_name, user_email, user_registered, user_url, user_pass FROM #{BB_PRESS_PREFIX}users u LEFT JOIN #{BB_PRESS_PREFIX}posts p ON p.post_author = u.id @@ -61,7 +60,6 @@ class ImportScripts::Bbpress < ImportScripts::Base ORDER BY u.id LIMIT #{BATCH_SIZE} SQL - ).to_a break if users.empty? @@ -73,22 +71,20 @@ class ImportScripts::Bbpress < ImportScripts::Base user_ids_sql = user_ids.join(",") users_description = {} - bbpress_query(<<-SQL + bbpress_query(<<-SQL).each { |um| users_description[um["user_id"]] = um["description"] } SELECT user_id, meta_value description FROM #{BB_PRESS_PREFIX}usermeta WHERE user_id IN (#{user_ids_sql}) AND meta_key = 'description' SQL - ).each { |um| users_description[um["user_id"]] = um["description"] } users_last_activity = {} - bbpress_query(<<-SQL + bbpress_query(<<-SQL).each { |um| users_last_activity[um["user_id"]] = um["last_activity"] } SELECT user_id, meta_value last_activity FROM #{BB_PRESS_PREFIX}usermeta WHERE user_id IN (#{user_ids_sql}) AND meta_key = 'last_activity' SQL - ).each { |um| users_last_activity[um["user_id"]] = um["last_activity"] } create_users(users, total: total_users, offset: offset) do |u| { @@ -96,7 +92,7 @@ class ImportScripts::Bbpress < ImportScripts::Base username: u["user_nicename"], password: u["user_pass"], email: u["user_email"].downcase, - name: u["display_name"].presence || u['user_nicename'], + name: u["display_name"].presence || u["user_nicename"], created_at: u["user_registered"], website: u["user_url"], bio_raw: users_description[u["id"]], @@ -114,67 +110,60 @@ class ImportScripts::Bbpress < ImportScripts::Base emails = Array.new # gather anonymous users via postmeta table - bbpress_query(<<-SQL + bbpress_query(<<-SQL).each do |pm| SELECT post_id, meta_key, meta_value FROM #{BB_PRESS_PREFIX}postmeta WHERE meta_key LIKE '_bbp_anonymous%' SQL - ).each do |pm| - anon_posts[pm['post_id']] = Hash.new if not anon_posts[pm['post_id']] + anon_posts[pm["post_id"]] = Hash.new if not anon_posts[pm["post_id"]] - if pm['meta_key'] == '_bbp_anonymous_email' - anon_posts[pm['post_id']]['email'] = pm['meta_value'] + if pm["meta_key"] == "_bbp_anonymous_email" + anon_posts[pm["post_id"]]["email"] = pm["meta_value"] end - if pm['meta_key'] == '_bbp_anonymous_name' - anon_posts[pm['post_id']]['name'] = pm['meta_value'] + if pm["meta_key"] == "_bbp_anonymous_name" + anon_posts[pm["post_id"]]["name"] = pm["meta_value"] end - if pm['meta_key'] == '_bbp_anonymous_website' - anon_posts[pm['post_id']]['website'] = pm['meta_value'] + if pm["meta_key"] == "_bbp_anonymous_website" + anon_posts[pm["post_id"]]["website"] = pm["meta_value"] end end # gather every existent username anon_posts.each do |id, post| - anon_names[post['name']] = Hash.new if not anon_names[post['name']] + anon_names[post["name"]] = Hash.new if not anon_names[post["name"]] # overwriting email address, one user can only use one email address - anon_names[post['name']]['email'] = post['email'] - anon_names[post['name']]['website'] = post['website'] if post['website'] != '' + anon_names[post["name"]]["email"] = post["email"] + anon_names[post["name"]]["website"] = post["website"] if post["website"] != "" end # make sure every user name has a unique email address anon_names.each do |k, name| - if not emails.include? name['email'] - emails.push ( name['email']) + if not emails.include? name["email"] + emails.push (name["email"]) else - name['email'] = "anonymous_#{SecureRandom.hex}@no-email.invalid" + name["email"] = "anonymous_#{SecureRandom.hex}@no-email.invalid" end end create_users(anon_names) do |k, n| - { - id: k, - email: n["email"].downcase, - name: k, - website: n["website"] - } + { id: k, email: n["email"].downcase, name: k, website: n["website"] } end end def import_categories puts "", "importing categories..." - categories = bbpress_query(<<-SQL + categories = bbpress_query(<<-SQL) SELECT id, post_name, post_parent FROM #{BB_PRESS_PREFIX}posts WHERE post_type = 'forum' AND LENGTH(COALESCE(post_name, '')) > 0 ORDER BY post_parent, id SQL - ) create_categories(categories) do |c| - category = { id: c['id'], name: c['post_name'] } - if (parent_id = c['post_parent'].to_i) > 0 + category = { id: c["id"], name: c["post_name"] } + if (parent_id = c["post_parent"].to_i) > 0 category[:parent_category_id] = category_id_from_imported_category_id(parent_id) end category @@ -185,16 +174,15 @@ class ImportScripts::Bbpress < ImportScripts::Base puts "", "importing topics and posts..." last_post_id = -1 - total_posts = bbpress_query(<<-SQL + total_posts = bbpress_query(<<-SQL).first["count"] SELECT COUNT(*) count FROM #{BB_PRESS_PREFIX}posts WHERE post_status <> 'spam' AND post_type IN ('topic', 'reply') SQL - ).first["count"] batches(BATCH_SIZE) do |offset| - posts = bbpress_query(<<-SQL + posts = bbpress_query(<<-SQL).to_a SELECT id, post_author, post_date, @@ -209,7 +197,6 @@ class ImportScripts::Bbpress < ImportScripts::Base ORDER BY id LIMIT #{BATCH_SIZE} SQL - ).to_a break if posts.empty? @@ -221,31 +208,29 @@ class ImportScripts::Bbpress < ImportScripts::Base post_ids_sql = post_ids.join(",") posts_likes = {} - bbpress_query(<<-SQL + bbpress_query(<<-SQL).each { |pm| posts_likes[pm["post_id"]] = pm["likes"].to_i } SELECT post_id, meta_value likes FROM #{BB_PRESS_PREFIX}postmeta WHERE post_id IN (#{post_ids_sql}) AND meta_key = 'Likes' SQL - ).each { |pm| posts_likes[pm["post_id"]] = pm["likes"].to_i } anon_names = {} - bbpress_query(<<-SQL + bbpress_query(<<-SQL).each { |pm| anon_names[pm["post_id"]] = pm["meta_value"] } SELECT post_id, meta_value FROM #{BB_PRESS_PREFIX}postmeta WHERE post_id IN (#{post_ids_sql}) AND meta_key = '_bbp_anonymous_name' SQL - ).each { |pm| anon_names[pm["post_id"]] = pm["meta_value"] } create_posts(posts, total: total_posts, offset: offset) do |p| skip = false - user_id = user_id_from_imported_user_id(p["post_author"]) || - find_user_by_import_id(p["post_author"]).try(:id) || - user_id_from_imported_user_id(anon_names[p['id']]) || - find_user_by_import_id(anon_names[p['id']]).try(:id) || - -1 + user_id = + user_id_from_imported_user_id(p["post_author"]) || + find_user_by_import_id(p["post_author"]).try(:id) || + user_id_from_imported_user_id(anon_names[p["id"]]) || + find_user_by_import_id(anon_names[p["id"]]).try(:id) || -1 post = { id: p["id"], @@ -256,7 +241,9 @@ class ImportScripts::Bbpress < ImportScripts::Base } if post[:raw].present? - post[:raw].gsub!(/\\(.*?)\<\/code\>\<\/pre\>/im) { "```\n#{@he.decode($2)}\n```" } + post[:raw].gsub!(%r{\\(.*?)\\}im) do + "```\n#{@he.decode($2)}\n```" + end end if p["post_type"] == "topic" @@ -288,17 +275,16 @@ class ImportScripts::Bbpress < ImportScripts::Base count = 0 last_attachment_id = -1 - total_attachments = bbpress_query(<<-SQL + total_attachments = bbpress_query(<<-SQL).first["count"] SELECT COUNT(*) count FROM #{BB_PRESS_PREFIX}postmeta pm JOIN #{BB_PRESS_PREFIX}posts p ON p.id = pm.post_id WHERE pm.meta_key = '_wp_attached_file' AND p.post_parent > 0 SQL - ).first["count"] batches(BATCH_SIZE) do |offset| - attachments = bbpress_query(<<-SQL + attachments = bbpress_query(<<-SQL).to_a SELECT pm.meta_id id, pm.meta_value, p.post_parent post_id FROM #{BB_PRESS_PREFIX}postmeta pm JOIN #{BB_PRESS_PREFIX}posts p ON p.id = pm.post_id @@ -308,7 +294,6 @@ class ImportScripts::Bbpress < ImportScripts::Base ORDER BY pm.meta_id LIMIT #{BATCH_SIZE} SQL - ).to_a break if attachments.empty? last_attachment_id = attachments[-1]["id"].to_i @@ -325,7 +310,9 @@ class ImportScripts::Bbpress < ImportScripts::Base if !post.raw[html] post.raw << "\n\n" << html post.save! - PostUpload.create!(post: post, upload: upload) unless PostUpload.where(post: post, upload: upload).exists? + unless PostUpload.where(post: post, upload: upload).exists? + PostUpload.create!(post: post, upload: upload) + end end end end @@ -340,15 +327,14 @@ class ImportScripts::Bbpress < ImportScripts::Base count = 0 last_attachment_id = -1 - total_attachments = bbpress_query(<<-SQL + total_attachments = bbpress_query(<<-SQL).first["count"] SELECT COUNT(*) count FROM #{BB_PRESS_PREFIX}bb_attachments WHERE post_id IN (SELECT id FROM #{BB_PRESS_PREFIX}posts WHERE post_status <> 'spam' AND post_type IN ('topic', 'reply')) SQL - ).first["count"] batches(BATCH_SIZE) do |offset| - attachments = bbpress_query(<<-SQL + attachments = bbpress_query(<<-SQL).to_a SELECT id, filename, post_id FROM #{BB_PRESS_PREFIX}bb_attachments WHERE post_id IN (SELECT id FROM #{BB_PRESS_PREFIX}posts WHERE post_status <> 'spam' AND post_type IN ('topic', 'reply')) @@ -356,13 +342,16 @@ class ImportScripts::Bbpress < ImportScripts::Base ORDER BY id LIMIT #{BATCH_SIZE} SQL - ).to_a break if attachments.empty? last_attachment_id = attachments[-1]["id"].to_i attachments.each do |a| - print_status(count += 1, total_attachments, get_start_time("attachments_from_bb_attachments")) + print_status( + count += 1, + total_attachments, + get_start_time("attachments_from_bb_attachments"), + ) if path = find_attachment(a["filename"], a["id"]) if post = Post.find_by(id: post_id_from_imported_post_id(a["post_id"])) upload = create_upload(post.user.id, path, a["filename"]) @@ -371,7 +360,9 @@ class ImportScripts::Bbpress < ImportScripts::Base if !post.raw[html] post.raw << "\n\n" << html post.save! - PostUpload.create!(post: post, upload: upload) unless PostUpload.where(post: post, upload: upload).exists? + unless PostUpload.where(post: post, upload: upload).exists? + PostUpload.create!(post: post, upload: upload) + end end end end @@ -391,7 +382,7 @@ class ImportScripts::Bbpress < ImportScripts::Base last_topic_id = -1 batches(BATCH_SIZE) do |offset| - topics = bbpress_query(<<-SQL + topics = bbpress_query(<<-SQL).to_a SELECT id, guid FROM #{BB_PRESS_PREFIX}posts @@ -401,14 +392,17 @@ class ImportScripts::Bbpress < ImportScripts::Base ORDER BY id LIMIT #{BATCH_SIZE} SQL - ).to_a break if topics.empty? last_topic_id = topics[-1]["id"].to_i topics.each do |t| - topic = topic_lookup_from_imported_post_id(t['id']) - Permalink.create(url: URI.parse(t['guid']).path.chomp('/'), topic_id: topic[:topic_id]) rescue nil + topic = topic_lookup_from_imported_post_id(t["id"]) + begin + Permalink.create(url: URI.parse(t["guid"]).path.chomp("/"), topic_id: topic[:topic_id]) + rescue StandardError + nil + end end end end @@ -417,42 +411,44 @@ class ImportScripts::Bbpress < ImportScripts::Base puts "", "importing private messages..." last_post_id = -1 - total_posts = bbpress_query("SELECT COUNT(*) count FROM #{BB_PRESS_PREFIX}bp_messages_messages").first["count"] + total_posts = + bbpress_query("SELECT COUNT(*) count FROM #{BB_PRESS_PREFIX}bp_messages_messages").first[ + "count" + ] threads = {} - total_count = bbpress_query("SELECT COUNT(*) count FROM #{BB_PRESS_PREFIX}bp_messages_recipients").first["count"] + total_count = + bbpress_query("SELECT COUNT(*) count FROM #{BB_PRESS_PREFIX}bp_messages_recipients").first[ + "count" + ] current_count = 0 batches(BATCH_SIZE) do |offset| - rows = bbpress_query(<<-SQL + rows = bbpress_query(<<-SQL).to_a SELECT thread_id, user_id FROM #{BB_PRESS_PREFIX}bp_messages_recipients ORDER BY id LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ).to_a break if rows.empty? rows.each do |row| current_count += 1 - print_status(current_count, total_count, get_start_time('private_messages')) + print_status(current_count, total_count, get_start_time("private_messages")) - threads[row['thread_id']] ||= { - target_user_ids: [], - imported_topic_id: nil - } - user_id = user_id_from_imported_user_id(row['user_id']) - if user_id && !threads[row['thread_id']][:target_user_ids].include?(user_id) - threads[row['thread_id']][:target_user_ids] << user_id + threads[row["thread_id"]] ||= { target_user_ids: [], imported_topic_id: nil } + user_id = user_id_from_imported_user_id(row["user_id"]) + if user_id && !threads[row["thread_id"]][:target_user_ids].include?(user_id) + threads[row["thread_id"]][:target_user_ids] << user_id end end end batches(BATCH_SIZE) do |offset| - posts = bbpress_query(<<-SQL + posts = bbpress_query(<<-SQL).to_a SELECT id, thread_id, date_sent, @@ -464,39 +460,48 @@ class ImportScripts::Bbpress < ImportScripts::Base ORDER BY thread_id, date_sent LIMIT #{BATCH_SIZE} SQL - ).to_a break if posts.empty? last_post_id = posts[-1]["id"].to_i create_posts(posts, total: total_posts, offset: offset) do |post| - if tcf = TopicCustomField.where(name: 'bb_thread_id', value: post['thread_id']).first + if tcf = TopicCustomField.where(name: "bb_thread_id", value: post["thread_id"]).first { - id: "pm#{post['id']}", - topic_id: threads[post['thread_id']][:imported_topic_id], - user_id: user_id_from_imported_user_id(post['sender_id']) || find_user_by_import_id(post['sender_id'])&.id || -1, - raw: post['message'], - created_at: post['date_sent'], + id: "pm#{post["id"]}", + topic_id: threads[post["thread_id"]][:imported_topic_id], + user_id: + user_id_from_imported_user_id(post["sender_id"]) || + find_user_by_import_id(post["sender_id"])&.id || -1, + raw: post["message"], + created_at: post["date_sent"], } else # First post of the thread { - id: "pm#{post['id']}", + id: "pm#{post["id"]}", archetype: Archetype.private_message, - user_id: user_id_from_imported_user_id(post['sender_id']) || find_user_by_import_id(post['sender_id'])&.id || -1, - title: post['subject'], - raw: post['message'], - created_at: post['date_sent'], - target_usernames: User.where(id: threads[post['thread_id']][:target_user_ids]).pluck(:username), - post_create_action: proc do |new_post| - if topic = new_post.topic - threads[post['thread_id']][:imported_topic_id] = topic.id - TopicCustomField.create(topic_id: topic.id, name: 'bb_thread_id', value: post['thread_id']) - else - puts "Error in post_create_action! Can't find topic!" - end - end + user_id: + user_id_from_imported_user_id(post["sender_id"]) || + find_user_by_import_id(post["sender_id"])&.id || -1, + title: post["subject"], + raw: post["message"], + created_at: post["date_sent"], + target_usernames: + User.where(id: threads[post["thread_id"]][:target_user_ids]).pluck(:username), + post_create_action: + proc do |new_post| + if topic = new_post.topic + threads[post["thread_id"]][:imported_topic_id] = topic.id + TopicCustomField.create( + topic_id: topic.id, + name: "bb_thread_id", + value: post["thread_id"], + ) + else + puts "Error in post_create_action! Can't find topic!" + end + end, } end end @@ -506,7 +511,6 @@ class ImportScripts::Bbpress < ImportScripts::Base def bbpress_query(sql) @client.query(sql, cache_rows: false) end - end ImportScripts::Bbpress.new.perform diff --git a/script/import_scripts/bespoke_1.rb b/script/import_scripts/bespoke_1.rb index 9ca420f319..833f772350 100644 --- a/script/import_scripts/bespoke_1.rb +++ b/script/import_scripts/bespoke_1.rb @@ -2,13 +2,12 @@ # bespoke importer for a customer, feel free to borrow ideas -require 'csv' +require "csv" require File.expand_path(File.dirname(__FILE__) + "/base.rb") # Call it like this: # RAILS_ENV=production bundle exec ruby script/import_scripts/bespoke_1.rb class ImportScripts::Bespoke < ImportScripts::Base - BATCH_SIZE = 1000 def initialize(path) @@ -18,9 +17,9 @@ class ImportScripts::Bespoke < ImportScripts::Base puts "loading post mappings..." @post_number_map = {} - Post.pluck(:id, :post_number).each do |post_id, post_number| - @post_number_map[post_id] = post_number - end + Post + .pluck(:id, :post_number) + .each { |post_id, post_number| @post_number_map[post_id] = post_number } end def created_post(post) @@ -32,7 +31,6 @@ class ImportScripts::Bespoke < ImportScripts::Base import_users import_categories import_posts - end class RowResolver @@ -45,19 +43,13 @@ class ImportScripts::Bespoke < ImportScripts::Base end def initialize(cols) - cols.each_with_index do |col, idx| - self.class.public_send(:define_method, col) do - @row[idx] - end - end + cols.each_with_index { |col, idx| self.class.public_send(:define_method, col) { @row[idx] } } end end def load_user_batch!(users, offset, total) if users.length > 0 - create_users(users, offset: offset, total: total) do |user| - user - end + create_users(users, offset: offset, total: total) { |user| user } users.clear end end @@ -70,54 +62,56 @@ class ImportScripts::Bespoke < ImportScripts::Base current_row = +"" double_quote_count = 0 - File.open(filename).each_line do |line| + File + .open(filename) + .each_line do |line| + # escaping is mental here + line.gsub!(/\\(.{1})/) { |m| m[-1] == '"' ? '""' : m[-1] } + line.strip! - # escaping is mental here - line.gsub!(/\\(.{1})/) { |m| m[-1] == '"' ? '""' : m[-1] } - line.strip! + current_row << "\n" unless current_row.empty? + current_row << line - current_row << "\n" unless current_row.empty? - current_row << line + double_quote_count += line.scan('"').count - double_quote_count += line.scan('"').count + next if double_quote_count % 2 == 1 - if double_quote_count % 2 == 1 - next - end + raw = + begin + CSV.parse(current_row) + rescue CSV::MalformedCSVError => e + puts e.message + puts "*" * 100 + puts "Bad row skipped, line is: #{line}" + puts + puts current_row + puts + puts "double quote count is : #{double_quote_count}" + puts "*" * 100 - raw = begin - CSV.parse(current_row) - rescue CSV::MalformedCSVError => e - puts e.message - puts "*" * 100 - puts "Bad row skipped, line is: #{line}" - puts - puts current_row - puts - puts "double quote count is : #{double_quote_count}" - puts "*" * 100 + current_row = "" + double_quote_count = 0 + next + end[ + 0 + ] - current_row = "" - double_quote_count = 0 - next - end[0] + if first + row = RowResolver.create(raw) - if first - row = RowResolver.create(raw) + current_row = "" + double_quote_count = 0 + first = false + next + end + + row.load(raw) + + yield row current_row = "" double_quote_count = 0 - first = false - next end - - row.load(raw) - - yield row - - current_row = "" - double_quote_count = 0 - end end def total_rows(table) @@ -133,14 +127,11 @@ class ImportScripts::Bespoke < ImportScripts::Base total = total_rows("users") csv_parse("users") do |row| - id = row.id email = row.email # fake it - if row.email.blank? || row.email !~ /@/ - email = fake_email - end + email = fake_email if row.email.blank? || row.email !~ /@/ name = row.display_name username = row.key_custom @@ -150,19 +141,10 @@ class ImportScripts::Bespoke < ImportScripts::Base username = email.split("@")[0] if username.blank? name = email.split("@")[0] if name.blank? - users << { - id: id, - email: email, - name: name, - username: username, - created_at: created_at - } + users << { id: id, email: email, name: name, username: username, created_at: created_at } count += 1 - if count % BATCH_SIZE == 0 - load_user_batch! users, count - users.length, total - end - + load_user_batch! users, count - users.length, total if count % BATCH_SIZE == 0 end load_user_batch! users, count, total @@ -174,22 +156,19 @@ class ImportScripts::Bespoke < ImportScripts::Base rows << { id: row.id, name: row.name, description: row.description } end - create_categories(rows) do |row| - row - end + create_categories(rows) { |row| row } end def normalize_raw!(raw) # purple and #1223f3 raw.gsub!(/\[color=[#a-z0-9]+\]/i, "") - raw.gsub!(/\[\/color\]/i, "") - raw.gsub!(/\[signature\].+\[\/signature\]/im, "") + raw.gsub!(%r{\[/color\]}i, "") + raw.gsub!(%r{\[signature\].+\[/signature\]}im, "") raw end def import_post_batch!(posts, topics, offset, total) create_posts(posts, total: total, offset: offset) do |post| - mapped = {} mapped[:id] = post[:id] @@ -223,7 +202,7 @@ class ImportScripts::Bespoke < ImportScripts::Base mapped end - posts.clear + posts.clear end def import_posts @@ -237,7 +216,7 @@ class ImportScripts::Bespoke < ImportScripts::Base category_id: topic.forum_category_id, deleted: topic.is_deleted.to_i == 1, locked: topic.is_locked.to_i == 1, - pinned: topic.is_pinned.to_i == 1 + pinned: topic.is_pinned.to_i == 1, } end @@ -246,7 +225,6 @@ class ImportScripts::Bespoke < ImportScripts::Base posts = [] count = 0 csv_parse("posts") do |row| - unless row.dcreate puts "NO CREATION DATE FOR POST" p row @@ -261,7 +239,7 @@ class ImportScripts::Bespoke < ImportScripts::Base title: row.title, body: normalize_raw!(row.body), deleted: row.is_deleted.to_i == 1, - created_at: DateTime.parse(row.dcreate) + created_at: DateTime.parse(row.dcreate), } posts << row count += 1 @@ -275,7 +253,6 @@ class ImportScripts::Bespoke < ImportScripts::Base exit end - end unless ARGV[0] && Dir.exist?(ARGV[0]) diff --git a/script/import_scripts/csv_importer.rb b/script/import_scripts/csv_importer.rb index a414373dbb..626645f530 100644 --- a/script/import_scripts/csv_importer.rb +++ b/script/import_scripts/csv_importer.rb @@ -7,18 +7,18 @@ require File.expand_path(File.dirname(__FILE__) + "/base.rb") # Make sure to follow the right format in your CSV files. class ImportScripts::CsvImporter < ImportScripts::Base - - CSV_FILE_PATH = ENV['CSV_USER_FILE'] || '/var/www/discourse/tmp/users.csv' - CSV_CUSTOM_FIELDS = ENV['CSV_CUSTOM_FIELDS'] || '/var/www/discourse/tmp/custom_fields.csv' - CSV_EMAILS = ENV['CSV_EMAILS'] || '/var/www/discourse/tmp/emails.csv' - CSV_CATEGORIES = ENV['CSV_CATEGORIES'] || '/var/www/discourse/tmp/categories.csv' - CSV_TOPICS = ENV['CSV_TOPICS'] || '/var/www/discourse/tmp/topics_new_users.csv' - CSV_TOPICS_EXISTING_USERS = ENV['CSV_TOPICS'] || '/var/www/discourse/tmp/topics_existing_users.csv' - IMPORT_PREFIX = ENV['IMPORT_PREFIX'] || '2022-08-11' - IMPORT_USER_ID_PREFIX = 'csv-user-import-' + IMPORT_PREFIX + '-' - IMPORT_CATEGORY_ID_PREFIX = 'csv-category-import-' + IMPORT_PREFIX + '-' - IMPORT_TOPIC_ID_PREFIX = 'csv-topic-import-' + IMPORT_PREFIX + '-' - IMPORT_TOPIC_ID_EXISITNG_PREFIX = 'csv-topic_existing-import-' + IMPORT_PREFIX + '-' + CSV_FILE_PATH = ENV["CSV_USER_FILE"] || "/var/www/discourse/tmp/users.csv" + CSV_CUSTOM_FIELDS = ENV["CSV_CUSTOM_FIELDS"] || "/var/www/discourse/tmp/custom_fields.csv" + CSV_EMAILS = ENV["CSV_EMAILS"] || "/var/www/discourse/tmp/emails.csv" + CSV_CATEGORIES = ENV["CSV_CATEGORIES"] || "/var/www/discourse/tmp/categories.csv" + CSV_TOPICS = ENV["CSV_TOPICS"] || "/var/www/discourse/tmp/topics_new_users.csv" + CSV_TOPICS_EXISTING_USERS = + ENV["CSV_TOPICS"] || "/var/www/discourse/tmp/topics_existing_users.csv" + IMPORT_PREFIX = ENV["IMPORT_PREFIX"] || "2022-08-11" + IMPORT_USER_ID_PREFIX = "csv-user-import-" + IMPORT_PREFIX + "-" + IMPORT_CATEGORY_ID_PREFIX = "csv-category-import-" + IMPORT_PREFIX + "-" + IMPORT_TOPIC_ID_PREFIX = "csv-topic-import-" + IMPORT_PREFIX + "-" + IMPORT_TOPIC_ID_EXISITNG_PREFIX = "csv-topic_existing-import-" + IMPORT_PREFIX + "-" def initialize super @@ -49,25 +49,19 @@ class ImportScripts::CsvImporter < ImportScripts::Base return nil end - CSV.parse(File.read(path, encoding: 'bom|utf-8'), headers: true) + CSV.parse(File.read(path, encoding: "bom|utf-8"), headers: true) end def username_for(name) - result = name.downcase.gsub(/[^a-z0-9\-\_]/, '') - if result.blank? - result = Digest::SHA1.hexdigest(name)[0...10] - end + result = name.downcase.gsub(/[^a-z0-9\-\_]/, "") + result = Digest::SHA1.hexdigest(name)[0...10] if result.blank? result end def get_email(id) email = nil - @imported_emails.each do |e| - if e["user_id"] == id - email = e["email"] - end - end + @imported_emails.each { |e| email = e["email"] if e["user_id"] == id } email end @@ -76,9 +70,7 @@ class ImportScripts::CsvImporter < ImportScripts::Base custom_fields = {} @imported_custom_fields.each do |cf| if cf["user_id"] == id - @imported_custom_fields_names.each do |name| - custom_fields[name] = cf[name] - end + @imported_custom_fields_names.each { |name| custom_fields[name] = cf[name] } end end @@ -86,98 +78,95 @@ class ImportScripts::CsvImporter < ImportScripts::Base end def import_users - puts '', "Importing users" + puts "", "Importing users" users = [] @imported_users.each do |u| - email = get_email(u['id']) - custom_fields = get_custom_fields(u['id']) - u['email'] = email - u['custom_fields'] = custom_fields - u['id'] = IMPORT_USER_ID_PREFIX + u['id'] + email = get_email(u["id"]) + custom_fields = get_custom_fields(u["id"]) + u["email"] = email + u["custom_fields"] = custom_fields + u["id"] = IMPORT_USER_ID_PREFIX + u["id"] users << u end users.uniq! create_users(users) do |u| { - id: u['id'], - username: u['username'], - email: u['email'], - created_at: u['created_at'], - custom_fields: u['custom_fields'], + id: u["id"], + username: u["username"], + email: u["email"], + created_at: u["created_at"], + custom_fields: u["custom_fields"], } end end def import_categories - puts '', "Importing categories" + puts "", "Importing categories" categories = [] @imported_categories.each do |c| - c['user_id'] = user_id_from_imported_user_id(IMPORT_USER_ID_PREFIX + c['user_id']) || Discourse::SYSTEM_USER_ID - c['id'] = IMPORT_CATEGORY_ID_PREFIX + c['id'] + c["user_id"] = user_id_from_imported_user_id(IMPORT_USER_ID_PREFIX + c["user_id"]) || + Discourse::SYSTEM_USER_ID + c["id"] = IMPORT_CATEGORY_ID_PREFIX + c["id"] categories << c end categories.uniq! create_categories(categories) do |c| - { - id: c['id'], - user_id: c['user_id'], - name: c['name'], - description: c['description'] - } + { id: c["id"], user_id: c["user_id"], name: c["name"], description: c["description"] } end end def import_topics - puts '', "Importing topics" + puts "", "Importing topics" topics = [] @imported_topics.each do |t| - t['user_id'] = user_id_from_imported_user_id(IMPORT_USER_ID_PREFIX + t['user_id']) || Discourse::SYSTEM_USER_ID - t['category_id'] = category_id_from_imported_category_id(IMPORT_CATEGORY_ID_PREFIX + t['category_id']) - t['id'] = IMPORT_TOPIC_ID_PREFIX + t['id'] + t["user_id"] = user_id_from_imported_user_id(IMPORT_USER_ID_PREFIX + t["user_id"]) || + Discourse::SYSTEM_USER_ID + t["category_id"] = category_id_from_imported_category_id( + IMPORT_CATEGORY_ID_PREFIX + t["category_id"], + ) + t["id"] = IMPORT_TOPIC_ID_PREFIX + t["id"] topics << t end create_posts(topics) do |t| { - id: t['id'], - user_id: t['user_id'], - title: t['title'], - category: t['category_id'], - raw: t['raw'] + id: t["id"], + user_id: t["user_id"], + title: t["title"], + category: t["category_id"], + raw: t["raw"], } end end def import_topics_existing_users # Import topics for users that already existed in the DB, not imported during this migration - puts '', "Importing topics for existing users" + puts "", "Importing topics for existing users" topics = [] @imported_topics_existing_users.each do |t| - t['id'] = IMPORT_TOPIC_ID_EXISITNG_PREFIX + t['id'] + t["id"] = IMPORT_TOPIC_ID_EXISITNG_PREFIX + t["id"] topics << t end create_posts(topics) do |t| { - id: t['id'], - user_id: t['user_id'], # This is a Discourse user ID - title: t['title'], - category: t['category_id'], # This is a Discourse category ID - raw: t['raw'] + id: t["id"], + user_id: t["user_id"], # This is a Discourse user ID + title: t["title"], + category: t["category_id"], # This is a Discourse category ID + raw: t["raw"], } end end end -if __FILE__ == $0 - ImportScripts::CsvImporter.new.perform -end +ImportScripts::CsvImporter.new.perform if __FILE__ == $0 # == CSV files format # diff --git a/script/import_scripts/csv_restore_staged_users.rb b/script/import_scripts/csv_restore_staged_users.rb index 2145bcc351..314004b880 100755 --- a/script/import_scripts/csv_restore_staged_users.rb +++ b/script/import_scripts/csv_restore_staged_users.rb @@ -6,10 +6,9 @@ require File.expand_path(File.dirname(__FILE__) + "/base.rb") # Edit the constants and initialize method for your import data. class ImportScripts::CsvRestoreStagedUsers < ImportScripts::Base - - CSV_FILE_PATH = ENV['CSV_USER_FILE'] - CSV_CUSTOM_FIELDS = ENV['CSV_CUSTOM_FIELDS'] - CSV_EMAILS = ENV['CSV_EMAILS'] + CSV_FILE_PATH = ENV["CSV_USER_FILE"] + CSV_CUSTOM_FIELDS = ENV["CSV_CUSTOM_FIELDS"] + CSV_EMAILS = ENV["CSV_EMAILS"] BATCH_SIZE ||= 1000 @@ -35,62 +34,51 @@ class ImportScripts::CsvRestoreStagedUsers < ImportScripts::Base end def username_for(name) - result = name.downcase.gsub(/[^a-z0-9\-\_]/, '') + result = name.downcase.gsub(/[^a-z0-9\-\_]/, "") - if result.blank? - result = Digest::SHA1.hexdigest(name)[0...10] - end + result = Digest::SHA1.hexdigest(name)[0...10] if result.blank? result end def get_email(id) email = nil - @imported_emails.each do |e| - if e["user_id"] == id - email = e["email"] - end - end + @imported_emails.each { |e| email = e["email"] if e["user_id"] == id } email end def get_custom_fields(id) custom_fields = {} @imported_custom_fields.each do |cf| - if cf["user_id"] == id - custom_fields[cf["name"]] = cf["value"] - end + custom_fields[cf["name"]] = cf["value"] if cf["user_id"] == id end custom_fields end def import_users - puts '', "Importing users" + puts "", "Importing users" users = [] @imported_users.each do |u| - email = get_email(u['id']) - custom_fields = get_custom_fields(u['id']) - u['email'] = email - u['custom_fields'] = custom_fields + email = get_email(u["id"]) + custom_fields = get_custom_fields(u["id"]) + u["email"] = email + u["custom_fields"] = custom_fields users << u end users.uniq! create_users(users) do |u| { - id: u['id'], - username: u['username'], - email: u['email'], - created_at: u['created_at'], - staged: u['staged'], - custom_fields: u['custom_fields'], + id: u["id"], + username: u["username"], + email: u["email"], + created_at: u["created_at"], + staged: u["staged"], + custom_fields: u["custom_fields"], } end end - end -if __FILE__ == $0 - ImportScripts::CsvRestoreStagedUsers.new.perform -end +ImportScripts::CsvRestoreStagedUsers.new.perform if __FILE__ == $0 diff --git a/script/import_scripts/discuz_x.rb b/script/import_scripts/discuz_x.rb index 1b3cb5b8cb..df6a28c2af 100644 --- a/script/import_scripts/discuz_x.rb +++ b/script/import_scripts/discuz_x.rb @@ -9,48 +9,47 @@ # This script is tested only on Simplified Chinese Discuz! X instances # If you want to import data other than Simplified Chinese, email me. -require 'php_serialize' -require 'miro' -require 'mysql2' +require "php_serialize" +require "miro" +require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::DiscuzX < ImportScripts::Base - DISCUZX_DB = "ultrax" - DB_TABLE_PREFIX = 'pre_' + DB_TABLE_PREFIX = "pre_" BATCH_SIZE = 1000 ORIGINAL_SITE_PREFIX = "oldsite.example.com/forums" # without http(s):// - NEW_SITE_PREFIX = "http://discourse.example.com" # with http:// or https:// + NEW_SITE_PREFIX = "http://discourse.example.com" # with http:// or https:// # Set DISCUZX_BASE_DIR to the base directory of your discuz installation. - DISCUZX_BASE_DIR = '/var/www/discuz/upload' - AVATAR_DIR = '/uc_server/data/avatar' - ATTACHMENT_DIR = '/data/attachment/forum' - AUTHORIZED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'zip', 'rar', 'pdf'] + DISCUZX_BASE_DIR = "/var/www/discuz/upload" + AVATAR_DIR = "/uc_server/data/avatar" + ATTACHMENT_DIR = "/data/attachment/forum" + AUTHORIZED_EXTENSIONS = %w[jpg jpeg png gif zip rar pdf] def initialize super - @client = Mysql2::Client.new( - host: "localhost", - username: "root", - #password: "password", - database: DISCUZX_DB - ) + @client = + Mysql2::Client.new( + host: "localhost", + username: "root", + #password: "password", + database: DISCUZX_DB, + ) @first_post_id_by_topic_id = {} @internal_url_regexps = [ - /http(?:s)?:\/\/#{ORIGINAL_SITE_PREFIX.gsub('.', '\.')}\/forum\.php\?mod=viewthread(?:&|&)tid=(?\d+)(?:[^\[\]\s]*)(?:pid=?(?\d+))?(?:[^\[\]\s]*)/, - /http(?:s)?:\/\/#{ORIGINAL_SITE_PREFIX.gsub('.', '\.')}\/viewthread\.php\?tid=(?\d+)(?:[^\[\]\s]*)(?:pid=?(?\d+))?(?:[^\[\]\s]*)/, - /http(?:s)?:\/\/#{ORIGINAL_SITE_PREFIX.gsub('.', '\.')}\/forum\.php\?mod=redirect(?:&|&)goto=findpost(?:&|&)pid=(?\d+)(?:&|&)ptid=(?\d+)(?:[^\[\]\s]*)/, - /http(?:s)?:\/\/#{ORIGINAL_SITE_PREFIX.gsub('.', '\.')}\/redirect\.php\?goto=findpost(?:&|&)pid=(?\d+)(?:&|&)ptid=(?\d+)(?:[^\[\]\s]*)/, - /http(?:s)?:\/\/#{ORIGINAL_SITE_PREFIX.gsub('.', '\.')}\/forumdisplay\.php\?fid=(?\d+)(?:[^\[\]\s]*)/, - /http(?:s)?:\/\/#{ORIGINAL_SITE_PREFIX.gsub('.', '\.')}\/forum\.php\?mod=forumdisplay(?:&|&)fid=(?\d+)(?:[^\[\]\s]*)/, - /http(?:s)?:\/\/#{ORIGINAL_SITE_PREFIX.gsub('.', '\.')}\/(?index)\.php(?:[^\[\]\s]*)/, - /http(?:s)?:\/\/#{ORIGINAL_SITE_PREFIX.gsub('.', '\.')}\/(?stats)\.php(?:[^\[\]\s]*)/, - /http(?:s)?:\/\/#{ORIGINAL_SITE_PREFIX.gsub('.', '\.')}\/misc.php\?mod=(?stat|ranklist)(?:[^\[\]\s]*)/ + %r{http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub(".", '\.')}/forum\.php\?mod=viewthread(?:&|&)tid=(?\d+)(?:[^\[\]\s]*)(?:pid=?(?\d+))?(?:[^\[\]\s]*)}, + %r{http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub(".", '\.')}/viewthread\.php\?tid=(?\d+)(?:[^\[\]\s]*)(?:pid=?(?\d+))?(?:[^\[\]\s]*)}, + %r{http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub(".", '\.')}/forum\.php\?mod=redirect(?:&|&)goto=findpost(?:&|&)pid=(?\d+)(?:&|&)ptid=(?\d+)(?:[^\[\]\s]*)}, + %r{http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub(".", '\.')}/redirect\.php\?goto=findpost(?:&|&)pid=(?\d+)(?:&|&)ptid=(?\d+)(?:[^\[\]\s]*)}, + %r{http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub(".", '\.')}/forumdisplay\.php\?fid=(?\d+)(?:[^\[\]\s]*)}, + %r{http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub(".", '\.')}/forum\.php\?mod=forumdisplay(?:&|&)fid=(?\d+)(?:[^\[\]\s]*)}, + %r{http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub(".", '\.')}/(?index)\.php(?:[^\[\]\s]*)}, + %r{http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub(".", '\.')}/(?stats)\.php(?:[^\[\]\s]*)}, + %r{http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub(".", '\.')}/misc.php\?mod=(?stat|ranklist)(?:[^\[\]\s]*)}, ] - end def execute @@ -69,75 +68,84 @@ class ImportScripts::DiscuzX < ImportScripts::Base # find which group members can be granted as admin def get_knowledge_about_group - group_table = table_name 'common_usergroup' - result = mysql_query( - "SELECT groupid group_id, radminid role_id - FROM #{group_table};") + group_table = table_name "common_usergroup" + result = + mysql_query( + "SELECT groupid group_id, radminid role_id + FROM #{group_table};", + ) @moderator_group_id = [] @admin_group_id = [] #@banned_group_id = [4,5] # 禁止的用户及其帖子均不导入,如果你想导入这些用户和帖子,请把这个数组清空。 result.each do |group| - case group['role_id'] + case group["role_id"] when 1 # 管理员 - @admin_group_id << group['group_id'] - when 2, 3 # 超级版主、版主。如果你不希望原普通版主成为Discourse版主,把3去掉。 - @moderator_group_id << group['group_id'] + @admin_group_id << group["group_id"] + when 2, + 3 # 超级版主、版主。如果你不希望原普通版主成为Discourse版主,把3去掉。 + @moderator_group_id << group["group_id"] end end end def get_knowledge_about_category_slug @category_slug = {} - results = mysql_query("SELECT svalue value - FROM #{table_name 'common_setting'} - WHERE skey = 'forumkeys'") + results = + mysql_query( + "SELECT svalue value + FROM #{table_name "common_setting"} + WHERE skey = 'forumkeys'", + ) return if results.size < 1 - value = results.first['value'] + value = results.first["value"] return if value.blank? - PHP.unserialize(value).each do |category_import_id, slug| - next if slug.blank? - @category_slug[category_import_id] = slug - end + PHP + .unserialize(value) + .each do |category_import_id, slug| + next if slug.blank? + @category_slug[category_import_id] = slug + end end def get_knowledge_about_duplicated_email @duplicated_email = {} - results = mysql_query( - "select a.uid uid, b.uid import_id from pre_common_member a + results = + mysql_query( + "select a.uid uid, b.uid import_id from pre_common_member a join (select uid, email from pre_common_member group by email having count(email) > 1 order by uid asc) b USING(email) - where a.uid != b.uid") + where a.uid != b.uid", + ) users = @lookup.instance_variable_get :@users results.each do |row| - @duplicated_email[row['uid']] = row['import_id'] - user_id = users[row['import_id']] - if user_id - users[row['uid']] = user_id - end + @duplicated_email[row["uid"]] = row["import_id"] + user_id = users[row["import_id"]] + users[row["uid"]] = user_id if user_id end end def import_users - puts '', "creating users" + puts "", "creating users" get_knowledge_about_group - sensitive_user_table = table_name 'ucenter_members' - user_table = table_name 'common_member' - profile_table = table_name 'common_member_profile' - status_table = table_name 'common_member_status' - forum_table = table_name 'common_member_field_forum' - home_table = table_name 'common_member_field_home' - total_count = mysql_query("SELECT count(*) count FROM #{user_table};").first['count'] + sensitive_user_table = table_name "ucenter_members" + user_table = table_name "common_member" + profile_table = table_name "common_member_profile" + status_table = table_name "common_member_status" + forum_table = table_name "common_member_field_forum" + home_table = table_name "common_member_field_home" + total_count = mysql_query("SELECT count(*) count FROM #{user_table};").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query( - "SELECT u.uid id, u.username username, u.email email, u.groupid group_id, + results = + mysql_query( + "SELECT u.uid id, u.username username, u.email email, u.groupid group_id, su.regdate regdate, su.password password_hash, su.salt salt, s.regip regip, s.lastip last_visit_ip, s.lastvisit last_visit_time, s.lastpost last_posted_at, s.lastsendmail last_emailed_at, u.emailstatus email_confirmed, u.avatarstatus avatar_exists, @@ -154,7 +162,8 @@ class ImportScripts::DiscuzX < ImportScripts::Base LEFT JOIN #{home_table} h USING(uid) ORDER BY u.uid ASC LIMIT #{BATCH_SIZE} - OFFSET #{offset};") + OFFSET #{offset};", + ) break if results.size < 1 @@ -162,147 +171,233 @@ class ImportScripts::DiscuzX < ImportScripts::Base # next if all_records_exist? :users, users.map {|u| u["id"].to_i} create_users(results, total: total_count, offset: offset) do |user| - { id: user['id'], - email: user['email'], - username: user['username'], - name: first_exists(user['realname'], user['customstatus'], user['username']), - import_pass: user['password_hash'], + { + id: user["id"], + email: user["email"], + username: user["username"], + name: first_exists(user["realname"], user["customstatus"], user["username"]), + import_pass: user["password_hash"], active: true, - salt: user['salt'], + salt: user["salt"], # TODO: title: user['customstatus'], # move custom title to name since discourse can't let user custom title https://meta.discourse.org/t/let-users-custom-their-title/37626 - created_at: user['regdate'] ? Time.zone.at(user['regdate']) : nil, - registration_ip_address: user['regip'], - ip_address: user['last_visit_ip'], - last_seen_at: user['last_visit_time'], - last_emailed_at: user['last_emailed_at'], - last_posted_at: user['last_posted_at'], - moderator: @moderator_group_id.include?(user['group_id']), - admin: @admin_group_id.include?(user['group_id']), - website: (user['website'] && user['website'].include?('.')) ? user['website'].strip : (user['qq'] && user['qq'].strip == (user['qq'].strip.to_i) && user['qq'].strip.to_i > (10000)) ? 'http://user.qzone.qq.com/' + user['qq'].strip : nil, - bio_raw: first_exists((user['bio'] && CGI.unescapeHTML(user['bio'])), user['sightml'], user['spacenote']).strip[0, 3000], - location: first_exists(user['address'], (!user['resideprovince'].blank? ? [user['resideprovince'], user['residecity'], user['residedist'], user['residecommunity']] : [user['birthprovince'], user['birthcity'], user['birthdist'], user['birthcommunity']]).reject { |location|location.blank? }.join(' ')), - post_create_action: lambda do |newmember| - if user['avatar_exists'] == (1) && newmember.uploaded_avatar_id.blank? - path, filename = discuzx_avatar_fullpath(user['id']) - if path - begin - upload = create_upload(newmember.id, path, filename) - if !upload.nil? && upload.persisted? - newmember.import_mode = false - newmember.create_user_avatar - newmember.import_mode = true - newmember.user_avatar.update(custom_upload_id: upload.id) - newmember.update(uploaded_avatar_id: upload.id) - else - puts "Error: Upload did not persist!" + created_at: user["regdate"] ? Time.zone.at(user["regdate"]) : nil, + registration_ip_address: user["regip"], + ip_address: user["last_visit_ip"], + last_seen_at: user["last_visit_time"], + last_emailed_at: user["last_emailed_at"], + last_posted_at: user["last_posted_at"], + moderator: @moderator_group_id.include?(user["group_id"]), + admin: @admin_group_id.include?(user["group_id"]), + website: + (user["website"] && user["website"].include?(".")) ? + user["website"].strip : + if ( + user["qq"] && user["qq"].strip == (user["qq"].strip.to_i) && + user["qq"].strip.to_i > (10_000) + ) + "http://user.qzone.qq.com/" + user["qq"].strip + else + nil + end, + bio_raw: + first_exists( + (user["bio"] && CGI.unescapeHTML(user["bio"])), + user["sightml"], + user["spacenote"], + ).strip[ + 0, + 3000 + ], + location: + first_exists( + user["address"], + ( + if !user["resideprovince"].blank? + [ + user["resideprovince"], + user["residecity"], + user["residedist"], + user["residecommunity"], + ] + else + [ + user["birthprovince"], + user["birthcity"], + user["birthdist"], + user["birthcommunity"], + ] + end + ).reject { |location| location.blank? }.join(" "), + ), + post_create_action: + lambda do |newmember| + if user["avatar_exists"] == (1) && newmember.uploaded_avatar_id.blank? + path, filename = discuzx_avatar_fullpath(user["id"]) + if path + begin + upload = create_upload(newmember.id, path, filename) + if !upload.nil? && upload.persisted? + newmember.import_mode = false + newmember.create_user_avatar + newmember.import_mode = true + newmember.user_avatar.update(custom_upload_id: upload.id) + newmember.update(uploaded_avatar_id: upload.id) + else + puts "Error: Upload did not persist!" + end + rescue SystemCallError => err + puts "Could not import avatar: #{err.message}" end - rescue SystemCallError => err - puts "Could not import avatar: #{err.message}" end end - end - if !user['spacecss'].blank? && newmember.user_profile.profile_background_upload.blank? - # profile background - if matched = user['spacecss'].match(/body\s*{[^}]*url\('?(.+?)'?\)/i) - body_background = matched[1].split(ORIGINAL_SITE_PREFIX, 2).last - end - if matched = user['spacecss'].match(/#hd\s*{[^}]*url\('?(.+?)'?\)/i) - header_background = matched[1].split(ORIGINAL_SITE_PREFIX, 2).last - end - if matched = user['spacecss'].match(/.blocktitle\s*{[^}]*url\('?(.+?)'?\)/i) - blocktitle_background = matched[1].split(ORIGINAL_SITE_PREFIX, 2).last - end - if matched = user['spacecss'].match(/#ct\s*{[^}]*url\('?(.+?)'?\)/i) - content_background = matched[1].split(ORIGINAL_SITE_PREFIX, 2).last + if !user["spacecss"].blank? && newmember.user_profile.profile_background_upload.blank? + # profile background + if matched = user["spacecss"].match(/body\s*{[^}]*url\('?(.+?)'?\)/i) + body_background = matched[1].split(ORIGINAL_SITE_PREFIX, 2).last + end + if matched = user["spacecss"].match(/#hd\s*{[^}]*url\('?(.+?)'?\)/i) + header_background = matched[1].split(ORIGINAL_SITE_PREFIX, 2).last + end + if matched = user["spacecss"].match(/.blocktitle\s*{[^}]*url\('?(.+?)'?\)/i) + blocktitle_background = matched[1].split(ORIGINAL_SITE_PREFIX, 2).last + end + if matched = user["spacecss"].match(/#ct\s*{[^}]*url\('?(.+?)'?\)/i) + content_background = matched[1].split(ORIGINAL_SITE_PREFIX, 2).last + end + + if body_background || header_background || blocktitle_background || + content_background + profile_background = + first_exists( + header_background, + body_background, + content_background, + blocktitle_background, + ) + card_background = + first_exists( + content_background, + body_background, + header_background, + blocktitle_background, + ) + upload = + create_upload( + newmember.id, + File.join(DISCUZX_BASE_DIR, profile_background), + File.basename(profile_background), + ) + if upload + newmember.user_profile.upload_profile_background upload + else + puts "WARNING: #{user["username"]} (UID: #{user["id"]}) profile_background file did not persist!" + end + upload = + create_upload( + newmember.id, + File.join(DISCUZX_BASE_DIR, card_background), + File.basename(card_background), + ) + if upload + newmember.user_profile.upload_card_background upload + else + puts "WARNING: #{user["username"]} (UID: #{user["id"]}) card_background file did not persist!" + end + end end - if body_background || header_background || blocktitle_background || content_background - profile_background = first_exists(header_background, body_background, content_background, blocktitle_background) - card_background = first_exists(content_background, body_background, header_background, blocktitle_background) - upload = create_upload(newmember.id, File.join(DISCUZX_BASE_DIR, profile_background), File.basename(profile_background)) - if upload - newmember.user_profile.upload_profile_background upload - else - puts "WARNING: #{user['username']} (UID: #{user['id']}) profile_background file did not persist!" - end - upload = create_upload(newmember.id, File.join(DISCUZX_BASE_DIR, card_background), File.basename(card_background)) - if upload - newmember.user_profile.upload_card_background upload - else - puts "WARNING: #{user['username']} (UID: #{user['id']}) card_background file did not persist!" - end + # we don't send email to the unconfirmed user + if newmember.email_digests + newmember.update(email_digests: user["email_confirmed"] == 1) end - end - - # we don't send email to the unconfirmed user - newmember.update(email_digests: user['email_confirmed'] == 1) if newmember.email_digests - newmember.update(name: '') if !newmember.name.blank? && newmember.name == (newmember.username) - end + if !newmember.name.blank? && newmember.name == (newmember.username) + newmember.update(name: "") + end + end, } end end end def import_categories - puts '', "creating categories" + puts "", "creating categories" get_knowledge_about_category_slug - forums_table = table_name 'forum_forum' - forums_data_table = table_name 'forum_forumfield' + forums_table = table_name "forum_forum" + forums_data_table = table_name "forum_forumfield" - results = mysql_query(" + results = + mysql_query( + " SELECT f.fid id, f.fup parent_id, f.name, f.type type, f.status status, f.displayorder position, d.description description, d.rules rules, d.icon, d.extra extra FROM #{forums_table} f LEFT JOIN #{forums_data_table} d USING(fid) ORDER BY parent_id ASC, id ASC - ") + ", + ) max_position = Category.all.max_by(&:position).position create_categories(results) do |row| - next if row['type'] == ('group') || row['status'] == (2) # or row['status'].to_i == 3 # 如果不想导入群组,取消注释 - extra = PHP.unserialize(row['extra']) if !row['extra'].blank? - if extra && !extra["namecolor"].blank? - color = extra["namecolor"][1, 6] - end + next if row["type"] == ("group") || row["status"] == (2) # or row['status'].to_i == 3 # 如果不想导入群组,取消注释 + extra = PHP.unserialize(row["extra"]) if !row["extra"].blank? + color = extra["namecolor"][1, 6] if extra && !extra["namecolor"].blank? Category.all.max_by(&:position).position h = { - id: row['id'], - name: row['name'], - description: row['description'], - position: row['position'].to_i + max_position, + id: row["id"], + name: row["name"], + description: row["description"], + position: row["position"].to_i + max_position, color: color, - post_create_action: lambda do |category| - if slug = @category_slug[row['id']] - category.update(slug: slug) - end - - raw = process_discuzx_post(row['rules'], nil) - if @bbcode_to_md - raw = raw.bbcode_to_md(false) rescue raw - end - category.topic.posts.first.update_attribute(:raw, raw) - if !row['icon'].empty? - upload = create_upload(Discourse::SYSTEM_USER_ID, File.join(DISCUZX_BASE_DIR, ATTACHMENT_DIR, '../common', row['icon']), File.basename(row['icon'])) - if upload - category.uploaded_logo_id = upload.id - # FIXME: I don't know how to get '/shared' by script. May change to Rails.root - category.color = Miro::DominantColors.new(File.join('/shared', upload.url)).to_hex.first[1, 6] if !color - category.save! + post_create_action: + lambda do |category| + if slug = @category_slug[row["id"]] + category.update(slug: slug) end - end - if row['status'] == (0) || row['status'] == (3) - SiteSetting.default_categories_muted = [SiteSetting.default_categories_muted, category.id].reject(&:blank?).join("|") - end - category - end + raw = process_discuzx_post(row["rules"], nil) + if @bbcode_to_md + raw = + begin + raw.bbcode_to_md(false) + rescue StandardError + raw + end + end + category.topic.posts.first.update_attribute(:raw, raw) + if !row["icon"].empty? + upload = + create_upload( + Discourse::SYSTEM_USER_ID, + File.join(DISCUZX_BASE_DIR, ATTACHMENT_DIR, "../common", row["icon"]), + File.basename(row["icon"]), + ) + if upload + category.uploaded_logo_id = upload.id + # FIXME: I don't know how to get '/shared' by script. May change to Rails.root + category.color = + Miro::DominantColors.new(File.join("/shared", upload.url)).to_hex.first[ + 1, + 6 + ] if !color + category.save! + end + end + + if row["status"] == (0) || row["status"] == (3) + SiteSetting.default_categories_muted = [ + SiteSetting.default_categories_muted, + category.id, + ].reject(&:blank?).join("|") + end + category + end, } - if row['parent_id'].to_i > 0 - h[:parent_category_id] = category_id_from_imported_category_id(row['parent_id']) + if row["parent_id"].to_i > 0 + h[:parent_category_id] = category_id_from_imported_category_id(row["parent_id"]) end h end @@ -311,14 +406,16 @@ class ImportScripts::DiscuzX < ImportScripts::Base def import_posts puts "", "creating topics and posts" - users_table = table_name 'common_member' - posts_table = table_name 'forum_post' - topics_table = table_name 'forum_thread' + users_table = table_name "common_member" + posts_table = table_name "forum_post" + topics_table = table_name "forum_thread" - total_count = mysql_query("SELECT count(*) count FROM #{posts_table}").first['count'] + total_count = mysql_query("SELECT count(*) count FROM #{posts_table}").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query(" + results = + mysql_query( + " SELECT p.pid id, p.tid topic_id, t.fid category_id, @@ -336,7 +433,8 @@ class ImportScripts::DiscuzX < ImportScripts::Base ORDER BY id ASC, topic_id ASC LIMIT #{BATCH_SIZE} OFFSET #{offset}; - ") + ", + ) # u.status != -1 AND u.groupid != 4 AND u.groupid != 5 用户未被锁定、禁访或禁言。在现实中的 Discuz 论坛,禁止的用户通常是广告机或驱逐的用户,这些不需要导入。 break if results.size < 1 @@ -346,63 +444,70 @@ class ImportScripts::DiscuzX < ImportScripts::Base skip = false mapped = {} - mapped[:id] = m['id'] - mapped[:user_id] = user_id_from_imported_user_id(m['user_id']) || -1 - mapped[:raw] = process_discuzx_post(m['raw'], m['id']) - mapped[:created_at] = Time.zone.at(m['post_time']) - mapped[:tags] = m['tags'] + mapped[:id] = m["id"] + mapped[:user_id] = user_id_from_imported_user_id(m["user_id"]) || -1 + mapped[:raw] = process_discuzx_post(m["raw"], m["id"]) + mapped[:created_at] = Time.zone.at(m["post_time"]) + mapped[:tags] = m["tags"] - if m['id'] == m['first_id'] - mapped[:category] = category_id_from_imported_category_id(m['category_id']) - mapped[:title] = CGI.unescapeHTML(m['title']) + if m["id"] == m["first_id"] + mapped[:category] = category_id_from_imported_category_id(m["category_id"]) + mapped[:title] = CGI.unescapeHTML(m["title"]) - if m['special'] == 1 - results = mysql_query(" + if m["special"] == 1 + results = + mysql_query( + " SELECT multiple, maxchoices - FROM #{table_name 'forum_poll'} - WHERE tid = #{m['topic_id']}") + FROM #{table_name "forum_poll"} + WHERE tid = #{m["topic_id"]}", + ) poll = results.first || {} - results = mysql_query(" + results = + mysql_query( + " SELECT polloption - FROM #{table_name 'forum_polloption'} - WHERE tid = #{m['topic_id']} - ORDER BY displayorder") + FROM #{table_name "forum_polloption"} + WHERE tid = #{m["topic_id"]} + ORDER BY displayorder", + ) if results.empty? - puts "WARNING: can't find poll options for topic #{m['topic_id']}, skip poll" + puts "WARNING: can't find poll options for topic #{m["topic_id"]}, skip poll" else - mapped[:raw].prepend "[poll#{poll['multiple'] ? ' type=multiple' : ''}#{poll['maxchoices'] > 0 ? " max=#{poll['maxchoices']}" : ''}]\n#{results.map { |option|'- ' + option['polloption'] }.join("\n")}\n[/poll]\n" + mapped[ + :raw + ].prepend "[poll#{poll["multiple"] ? " type=multiple" : ""}#{poll["maxchoices"] > 0 ? " max=#{poll["maxchoices"]}" : ""}]\n#{results.map { |option| "- " + option["polloption"] }.join("\n")}\n[/poll]\n" end end else - parent = topic_lookup_from_imported_post_id(m['first_id']) + parent = topic_lookup_from_imported_post_id(m["first_id"]) if parent mapped[:topic_id] = parent[:topic_id] - reply_post_import_id = find_post_id_by_quote_number(m['raw']) + reply_post_import_id = find_post_id_by_quote_number(m["raw"]) if reply_post_import_id post_id = post_id_from_imported_post_id(reply_post_import_id.to_i) if (post = Post.find_by(id: post_id)) if post.topic_id == mapped[:topic_id] mapped[:reply_to_post_number] = post.post_number else - puts "post #{m['id']} reply to another topic, skip reply" + puts "post #{m["id"]} reply to another topic, skip reply" end else - puts "post #{m['id']} reply to not exists post #{reply_post_import_id}, skip reply" + puts "post #{m["id"]} reply to not exists post #{reply_post_import_id}, skip reply" end end else - puts "Parent topic #{m['topic_id']} doesn't exist. Skipping #{m['id']}: #{m['title'][0..40]}" + puts "Parent topic #{m["topic_id"]} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" skip = true end - end - if m['status'] & 1 == 1 || mapped[:raw].blank? + if m["status"] & 1 == 1 || mapped[:raw].blank? mapped[:post_create_action] = lambda do |action_post| PostDestroyer.new(Discourse.system_user, action_post).perform_delete end - elsif (m['status'] & 2) >> 1 == 1 # waiting for approve + elsif (m["status"] & 2) >> 1 == 1 # waiting for approve mapped[:post_create_action] = lambda do |action_post| PostActionCreator.notify_user(Discourse.system_user, action_post) end @@ -413,42 +518,47 @@ class ImportScripts::DiscuzX < ImportScripts::Base end def import_bookmarks - puts '', 'creating bookmarks' - favorites_table = table_name 'home_favorite' - posts_table = table_name 'forum_post' + puts "", "creating bookmarks" + favorites_table = table_name "home_favorite" + posts_table = table_name "forum_post" - total_count = mysql_query("SELECT count(*) count FROM #{favorites_table} WHERE idtype = 'tid'").first['count'] + total_count = + mysql_query("SELECT count(*) count FROM #{favorites_table} WHERE idtype = 'tid'").first[ + "count" + ] batches(BATCH_SIZE) do |offset| - results = mysql_query(" + results = + mysql_query( + " SELECT p.pid post_id, f.uid user_id FROM #{favorites_table} f JOIN #{posts_table} p ON f.id = p.tid WHERE f.idtype = 'tid' AND p.first LIMIT #{BATCH_SIZE} - OFFSET #{offset};") + OFFSET #{offset};", + ) break if results.size < 1 # next if all_records_exist? create_bookmarks(results, total: total_count, offset: offset) do |row| - { - user_id: row['user_id'], - post_id: row['post_id'] - } + { user_id: row["user_id"], post_id: row["post_id"] } end end end def import_private_messages - puts '', 'creating private messages' + puts "", "creating private messages" - pm_indexes = table_name 'ucenter_pm_indexes' - pm_messages = table_name 'ucenter_pm_messages' - total_count = mysql_query("SELECT count(*) count FROM #{pm_indexes}").first['count'] + pm_indexes = table_name "ucenter_pm_indexes" + pm_messages = table_name "ucenter_pm_messages" + total_count = mysql_query("SELECT count(*) count FROM #{pm_indexes}").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query(" + results = + mysql_query( + " SELECT pmid id, plid thread_id, authorid user_id, message, dateline created_at FROM #{pm_messages}_1 UNION SELECT pmid id, plid thread_id, authorid user_id, message, dateline created_at @@ -469,7 +579,8 @@ class ImportScripts::DiscuzX < ImportScripts::Base FROM #{pm_messages}_9 ORDER BY thread_id ASC, id ASC LIMIT #{BATCH_SIZE} - OFFSET #{offset};") + OFFSET #{offset};", + ) break if results.size < 1 @@ -479,35 +590,47 @@ class ImportScripts::DiscuzX < ImportScripts::Base skip = false mapped = {} - mapped[:id] = "pm:#{m['id']}" - mapped[:user_id] = user_id_from_imported_user_id(m['user_id']) || -1 - mapped[:raw] = process_discuzx_post(m['message'], m['id']) - mapped[:created_at] = Time.zone.at(m['created_at']) - thread_id = "pm_#{m['thread_id']}" + mapped[:id] = "pm:#{m["id"]}" + mapped[:user_id] = user_id_from_imported_user_id(m["user_id"]) || -1 + mapped[:raw] = process_discuzx_post(m["message"], m["id"]) + mapped[:created_at] = Time.zone.at(m["created_at"]) + thread_id = "pm_#{m["thread_id"]}" - if is_first_pm(m['id'], m['thread_id']) + if is_first_pm(m["id"], m["thread_id"]) # find the title from list table - pm_thread = mysql_query(" + pm_thread = + mysql_query( + " SELECT plid thread_id, subject - FROM #{table_name 'ucenter_pm_lists'} - WHERE plid = #{m['thread_id']};").first - mapped[:title] = pm_thread['subject'] + FROM #{table_name "ucenter_pm_lists"} + WHERE plid = #{m["thread_id"]};", + ).first + mapped[:title] = pm_thread["subject"] mapped[:archetype] = Archetype.private_message # Find the users who are part of this private message. - import_user_ids = mysql_query(" + import_user_ids = + mysql_query( + " SELECT plid thread_id, uid user_id - FROM #{table_name 'ucenter_pm_members'} - WHERE plid = #{m['thread_id']}; - ").map { |r| r['user_id'] }.uniq + FROM #{table_name "ucenter_pm_members"} + WHERE plid = #{m["thread_id"]}; + ", + ).map { |r| r["user_id"] }.uniq - mapped[:target_usernames] = import_user_ids.map! do |import_user_id| - import_user_id.to_s == m['user_id'].to_s ? nil : User.find_by(id: user_id_from_imported_user_id(import_user_id)).try(:username) - end.compact + mapped[:target_usernames] = import_user_ids + .map! do |import_user_id| + if import_user_id.to_s == m["user_id"].to_s + nil + else + User.find_by(id: user_id_from_imported_user_id(import_user_id)).try(:username) + end + end + .compact if mapped[:target_usernames].empty? # pm with yourself? skip = true - puts "Skipping pm:#{m['id']} due to no target" + puts "Skipping pm:#{m["id"]} due to no target" else @first_post_id_by_topic_id[thread_id] = mapped[:id] end @@ -523,22 +646,24 @@ class ImportScripts::DiscuzX < ImportScripts::Base skip ? nil : mapped end - end end # search for first pm id for the series of pm def is_first_pm(pm_id, thread_id) - result = mysql_query(" + result = + mysql_query( + " SELECT pmid id - FROM #{table_name 'ucenter_pm_indexes'} + FROM #{table_name "ucenter_pm_indexes"} WHERE plid = #{thread_id} - ORDER BY id") - result.first['id'].to_s == pm_id.to_s + ORDER BY id", + ) + result.first["id"].to_s == pm_id.to_s end def process_and_upload_inline_images(raw) - inline_image_regex = /\[img\]([\s\S]*?)\[\/img\]/ + inline_image_regex = %r{\[img\]([\s\S]*?)\[/img\]} s = raw.dup @@ -549,7 +674,6 @@ class ImportScripts::DiscuzX < ImportScripts::Base upload, filename = upload_inline_image data upload ? html_for_upload(upload, filename) : nil end - end def process_discuzx_post(raw, import_id) @@ -559,10 +683,18 @@ class ImportScripts::DiscuzX < ImportScripts::Base # Strip the quote # [quote] quotation includes the topic which is the same as reply to in Discourse # We get the pid to find the post number the post reply to. So it can be stripped - s = s.gsub(/\[b\]回复 \[url=forum.php\?mod=redirect&goto=findpost&pid=\d+&ptid=\d+\].* 的帖子\[\/url\]\[\/b\]/i, '').strip - s = s.gsub(/\[b\]回复 \[url=https?:\/\/#{ORIGINAL_SITE_PREFIX}\/redirect.php\?goto=findpost&pid=\d+&ptid=\d+\].*?\[\/url\].*?\[\/b\]/i, '').strip + s = + s.gsub( + %r{\[b\]回复 \[url=forum.php\?mod=redirect&goto=findpost&pid=\d+&ptid=\d+\].* 的帖子\[/url\]\[/b\]}i, + "", + ).strip + s = + s.gsub( + %r{\[b\]回复 \[url=https?://#{ORIGINAL_SITE_PREFIX}/redirect.php\?goto=findpost&pid=\d+&ptid=\d+\].*?\[/url\].*?\[/b\]}i, + "", + ).strip - s.gsub!(/\[quote\](.*)?\[\/quote\]/im) do |matched| + s.gsub!(%r{\[quote\](.*)?\[/quote\]}im) do |matched| content = $1 post_import_id = find_post_id_by_quote_number(content) if post_import_id @@ -578,73 +710,93 @@ class ImportScripts::DiscuzX < ImportScripts::Base end end - s.gsub!(/\[size=2\]\[color=#999999\].*? 发表于 [\d\-\: ]*\[\/color\] \[url=forum.php\?mod=redirect&goto=findpost&pid=\d+&ptid=\d+\].*?\[\/url\]\[\/size\]/i, '') - s.gsub!(/\[size=2\]\[color=#999999\].*? 发表于 [\d\-\: ]*\[\/color\] \[url=https?:\/\/#{ORIGINAL_SITE_PREFIX}\/redirect.php\?goto=findpost&pid=\d+&ptid=\d+\].*?\[\/url\]\[\/size\]/i, '') + s.gsub!( + %r{\[size=2\]\[color=#999999\].*? 发表于 [\d\-\: ]*\[/color\] \[url=forum.php\?mod=redirect&goto=findpost&pid=\d+&ptid=\d+\].*?\[/url\]\[/size\]}i, + "", + ) + s.gsub!( + %r{\[size=2\]\[color=#999999\].*? 发表于 [\d\-\: ]*\[/color\] \[url=https?://#{ORIGINAL_SITE_PREFIX}/redirect.php\?goto=findpost&pid=\d+&ptid=\d+\].*?\[/url\]\[/size\]}i, + "", + ) # convert quote - s.gsub!(/\[quote\](.*?)\[\/quote\]/m) { "\n" + ($1.strip).gsub(/^/, '> ') + "\n" } + s.gsub!(%r{\[quote\](.*?)\[/quote\]}m) { "\n" + ($1.strip).gsub(/^/, "> ") + "\n" } # truncate line space, preventing line starting with many blanks to be parsed as code blocks - s.gsub!(/^ {4,}/, ' ') + s.gsub!(/^ {4,}/, " ") # TODO: Much better to use bbcode-to-md gem # Convert image bbcode with width and height - s.gsub!(/\[img[^\]]*\]https?:\/\/#{ORIGINAL_SITE_PREFIX}\/(.*)\[\/img\]/i, '[x-attach]\1[/x-attach]') # dont convert attachment - s.gsub!(/]*src="https?:\/\/#{ORIGINAL_SITE_PREFIX}\/(.*)".*?>/i, '[x-attach]\1[/x-attach]') # dont convert attachment - s.gsub!(/\[img[^\]]*\]https?:\/\/www\.touhou\.cc\/blog\/(.*)\[\/img\]/i, '[x-attach]../blog/\1[/x-attach]') # 私货 - s.gsub!(/\[img[^\]]*\]https?:\/\/www\.touhou\.cc\/ucenter\/avatar.php\?uid=(\d+)[^\]]*\[\/img\]/i) { "[x-attach]#{discuzx_avatar_fullpath($1, false)[0]}[/x-attach]" } # 私货 - s.gsub!(/\[img=(\d+),(\d+)\]([^\]]*)\[\/img\]/i, '') - s.gsub!(/\[img\]([^\]]*)\[\/img\]/i, '') + s.gsub!( + %r{\[img[^\]]*\]https?://#{ORIGINAL_SITE_PREFIX}/(.*)\[/img\]}i, + '[x-attach]\1[/x-attach]', + ) # dont convert attachment + s.gsub!( + %r{]*src="https?://#{ORIGINAL_SITE_PREFIX}/(.*)".*?>}i, + '[x-attach]\1[/x-attach]', + ) # dont convert attachment + s.gsub!( + %r{\[img[^\]]*\]https?://www\.touhou\.cc/blog/(.*)\[/img\]}i, + '[x-attach]../blog/\1[/x-attach]', + ) # 私货 + s.gsub!( + %r{\[img[^\]]*\]https?://www\.touhou\.cc/ucenter/avatar.php\?uid=(\d+)[^\]]*\[/img\]}i, + ) { "[x-attach]#{discuzx_avatar_fullpath($1, false)[0]}[/x-attach]" } # 私货 + s.gsub!(%r{\[img=(\d+),(\d+)\]([^\]]*)\[/img\]}i, '') + s.gsub!(%r{\[img\]([^\]]*)\[/img\]}i, '') - s.gsub!(/\[qq\]([^\]]*)\[\/qq\]/i, 'QQ 交谈') + s.gsub!( + %r{\[qq\]([^\]]*)\[/qq\]}i, + 'QQ 交谈', + ) - s.gsub!(/\[email\]([^\]]*)\[\/email\]/i, '[url=mailto:\1]\1[/url]') # bbcode-to-md can convert it - s.gsub!(/\[s\]([^\]]*)\[\/s\]/i, '\1') - s.gsub!(/\[sup\]([^\]]*)\[\/sup\]/i, '\1') - s.gsub!(/\[sub\]([^\]]*)\[\/sub\]/i, '\1') + s.gsub!(%r{\[email\]([^\]]*)\[/email\]}i, '[url=mailto:\1]\1[/url]') # bbcode-to-md can convert it + s.gsub!(%r{\[s\]([^\]]*)\[/s\]}i, '\1') + s.gsub!(%r{\[sup\]([^\]]*)\[/sup\]}i, '\1') + s.gsub!(%r{\[sub\]([^\]]*)\[/sub\]}i, '\1') s.gsub!(/\[hr\]/i, "\n---\n") # remove the media tag - s.gsub!(/\[\/?media[^\]]*\]/i, "\n") - s.gsub!(/\[\/?flash[^\]]*\]/i, "\n") - s.gsub!(/\[\/?audio[^\]]*\]/i, "\n") - s.gsub!(/\[\/?video[^\]]*\]/i, "\n") + s.gsub!(%r{\[/?media[^\]]*\]}i, "\n") + s.gsub!(%r{\[/?flash[^\]]*\]}i, "\n") + s.gsub!(%r{\[/?audio[^\]]*\]}i, "\n") + s.gsub!(%r{\[/?video[^\]]*\]}i, "\n") # Remove the font, p and backcolor tag # Discourse doesn't support the font tag - s.gsub!(/\[font=[^\]]*?\]/i, '') - s.gsub!(/\[\/font\]/i, '') - s.gsub!(/\[p=[^\]]*?\]/i, '') - s.gsub!(/\[\/p\]/i, '') - s.gsub!(/\[backcolor=[^\]]*?\]/i, '') - s.gsub!(/\[\/backcolor\]/i, '') + s.gsub!(/\[font=[^\]]*?\]/i, "") + s.gsub!(%r{\[/font\]}i, "") + s.gsub!(/\[p=[^\]]*?\]/i, "") + s.gsub!(%r{\[/p\]}i, "") + s.gsub!(/\[backcolor=[^\]]*?\]/i, "") + s.gsub!(%r{\[/backcolor\]}i, "") # Remove the size tag # I really have no idea what is this - s.gsub!(/\[size=[^\]]*?\]/i, '') - s.gsub!(/\[\/size\]/i, '') + s.gsub!(/\[size=[^\]]*?\]/i, "") + s.gsub!(%r{\[/size\]}i, "") # Remove the color tag - s.gsub!(/\[color=[^\]]*?\]/i, '') - s.gsub!(/\[\/color\]/i, '') + s.gsub!(/\[color=[^\]]*?\]/i, "") + s.gsub!(%r{\[/color\]}i, "") # Remove the hide tag - s.gsub!(/\[\/?hide\]/i, '') - s.gsub!(/\[\/?free[^\]]*\]/i, "\n") + s.gsub!(%r{\[/?hide\]}i, "") + s.gsub!(%r{\[/?free[^\]]*\]}i, "\n") # Remove the align tag # still don't know what it is s.gsub!(/\[align=[^\]]*?\]/i, "\n") - s.gsub!(/\[\/align\]/i, "\n") + s.gsub!(%r{\[/align\]}i, "\n") s.gsub!(/\[float=[^\]]*?\]/i, "\n") - s.gsub!(/\[\/float\]/i, "\n") + s.gsub!(%r{\[/float\]}i, "\n") # Convert code - s.gsub!(/\[\/?code\]/i, "\n```\n") + s.gsub!(%r{\[/?code\]}i, "\n```\n") # The edit notice should be removed # example: 本帖最后由 Helloworld 于 2015-1-28 22:05 编辑 - s.gsub!(/\[i=s\] 本帖最后由[\s\S]*?编辑 \[\/i\]/, '') + s.gsub!(%r{\[i=s\] 本帖最后由[\s\S]*?编辑 \[/i\]}, "") # Convert the custom smileys to emojis # `{:cry:}` to `:cry` @@ -653,35 +805,71 @@ class ImportScripts::DiscuzX < ImportScripts::Base # Replace internal forum links that aren't in the format # convert list tags to ul and list=1 tags to ol # (basically, we're only missing list=a here...) - s.gsub!(/\[list\](.*?)\[\/list:u\]/m, '[ul]\1[/ul]') - s.gsub!(/\[list=1\](.*?)\[\/list:o\]/m, '[ol]\1[/ol]') + s.gsub!(%r{\[list\](.*?)\[/list:u\]}m, '[ul]\1[/ul]') + s.gsub!(%r{\[list=1\](.*?)\[/list:o\]}m, '[ol]\1[/ol]') # convert *-tags to li-tags so bbcode-to-md can do its magic on phpBB's lists: - s.gsub!(/\[\*\](.*?)\[\/\*:m\]/, '[li]\1[/li]') + s.gsub!(%r{\[\*\](.*?)\[/\*:m\]}, '[li]\1[/li]') # Discuz can create PM out of a post, which will generates like # [url=http://example.com/forum.php?mod=redirect&goto=findpost&pid=111&ptid=11][b]关于您在“主题名称”的帖子[/b][/url] - s.gsub!(pm_url_regexp) do |discuzx_link| - replace_internal_link(discuzx_link, $1) - end + s.gsub!(pm_url_regexp) { |discuzx_link| replace_internal_link(discuzx_link, $1) } # [url][b]text[/b][/url] to **[url]text[/url]** - s.gsub!(/(\[url=[^\[\]]*?\])\[b\](\S*)\[\/b\](\[\/url\])/, '**\1\2\3**') + s.gsub!(%r{(\[url=[^\[\]]*?\])\[b\](\S*)\[/b\](\[/url\])}, '**\1\2\3**') @internal_url_regexps.each do |internal_url_regexp| s.gsub!(internal_url_regexp) do |discuzx_link| - replace_internal_link(discuzx_link, ($~[:tid].to_i rescue nil), ($~[:pid].to_i rescue nil), ($~[:fid].to_i rescue nil), ($~[:action] rescue nil)) + replace_internal_link( + discuzx_link, + ( + begin + $~[:tid].to_i + rescue StandardError + nil + end + ), + ( + begin + $~[:pid].to_i + rescue StandardError + nil + end + ), + ( + begin + $~[:fid].to_i + rescue StandardError + nil + end + ), + ( + begin + $~[:action] + rescue StandardError + nil + end + ), + ) end end # @someone without the url - s.gsub!(/@\[url=[^\[\]]*?\](\S*)\[\/url\]/i, '@\1') + s.gsub!(%r{@\[url=[^\[\]]*?\](\S*)\[/url\]}i, '@\1') - s.scan(/http(?:s)?:\/\/#{ORIGINAL_SITE_PREFIX.gsub('.', '\.')}\/[^\[\]\s]*/) { |link|puts "WARNING: post #{import_id} can't replace internal url #{link}" } + s.scan(%r{http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub(".", '\.')}/[^\[\]\s]*}) do |link| + puts "WARNING: post #{import_id} can't replace internal url #{link}" + end s.strip end - def replace_internal_link(discuzx_link, import_topic_id, import_post_id, import_category_id, action) + def replace_internal_link( + discuzx_link, + import_topic_id, + import_post_id, + import_category_id, + action + ) if import_post_id post_id = post_id_from_imported_post_id import_post_id if post_id @@ -691,15 +879,17 @@ class ImportScripts::DiscuzX < ImportScripts::Base end if import_topic_id - - results = mysql_query("SELECT pid - FROM #{table_name 'forum_post'} + results = + mysql_query( + "SELECT pid + FROM #{table_name "forum_post"} WHERE tid = #{import_topic_id} AND first - LIMIT 1") + LIMIT 1", + ) return discuzx_link unless results.size > 0 - linked_post_id = results.first['pid'] + linked_post_id = results.first["pid"] lookup = topic_lookup_from_imported_post_id(linked_post_id) if lookup @@ -707,7 +897,6 @@ class ImportScripts::DiscuzX < ImportScripts::Base else return discuzx_link end - end if import_category_id @@ -719,9 +908,9 @@ class ImportScripts::DiscuzX < ImportScripts::Base end case action - when 'index' + when "index" return "#{NEW_SITE_PREFIX}/" - when 'stat', 'stats', 'ranklist' + when "stat", "stats", "ranklist" return "#{NEW_SITE_PREFIX}/users" end @@ -729,28 +918,32 @@ class ImportScripts::DiscuzX < ImportScripts::Base end def pm_url_regexp - @pm_url_regexp ||= Regexp.new("http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub('.', '\.')}/forum\\.php\\?mod=redirect&goto=findpost&pid=\\d+&ptid=(\\d+)") + @pm_url_regexp ||= + Regexp.new( + "http(?:s)?://#{ORIGINAL_SITE_PREFIX.gsub(".", '\.')}/forum\\.php\\?mod=redirect&goto=findpost&pid=\\d+&ptid=(\\d+)", + ) end # This step is done separately because it can take multiple attempts to get right (because of # missing files, wrong paths, authorized extensions, etc.). def import_attachments - setting = AUTHORIZED_EXTENSIONS.join('|') + setting = AUTHORIZED_EXTENSIONS.join("|") SiteSetting.authorized_extensions = setting if setting != SiteSetting.authorized_extensions - attachment_regex = /\[attach\](\d+)\[\/attach\]/ - attachment_link_regex = /\[x-attach\](.+)\[\/x-attach\]/ + attachment_regex = %r{\[attach\](\d+)\[/attach\]} + attachment_link_regex = %r{\[x-attach\](.+)\[/x-attach\]} current_count = 0 - total_count = mysql_query("SELECT count(*) count FROM #{table_name 'forum_post'};").first['count'] + total_count = + mysql_query("SELECT count(*) count FROM #{table_name "forum_post"};").first["count"] success_count = 0 fail_count = 0 - puts '', "Importing attachments...", '' + puts "", "Importing attachments...", "" Post.find_each do |post| - next unless post.custom_fields['import_id'] == post.custom_fields['import_id'].to_i.to_s + next unless post.custom_fields["import_id"] == post.custom_fields["import_id"].to_i.to_s user = post.user @@ -786,17 +979,16 @@ class ImportScripts::DiscuzX < ImportScripts::Base html_for_upload(upload, filename) end - sql = "SELECT aid - FROM #{table_name 'forum_attachment'} - WHERE pid = #{post.custom_fields['import_id']}" - if !inline_attachments.empty? - sql = "#{sql} AND aid NOT IN (#{inline_attachments.join(',')})" - end + sql = + "SELECT aid + FROM #{table_name "forum_attachment"} + WHERE pid = #{post.custom_fields["import_id"]}" + sql = "#{sql} AND aid NOT IN (#{inline_attachments.join(",")})" if !inline_attachments.empty? results = mysql_query(sql) results.each do |attachment| - attachment_id = attachment['aid'] + attachment_id = attachment["aid"] upload, filename = find_upload(user, post, attachment_id) unless upload fail_count += 1 @@ -810,21 +1002,26 @@ class ImportScripts::DiscuzX < ImportScripts::Base end if new_raw != post.raw - PostRevisor.new(post).revise!(post.user, { raw: new_raw }, bypass_bump: true, edit_reason: '从 Discuz 中导入附件') + PostRevisor.new(post).revise!( + post.user, + { raw: new_raw }, + bypass_bump: true, + edit_reason: "从 Discuz 中导入附件", + ) end success_count += 1 end - puts '', '' + puts "", "" puts "succeeded: #{success_count}" puts " failed: #{fail_count}" if fail_count > 0 - puts '' + puts "" end # Create the full path to the discuz avatar specified from user id def discuzx_avatar_fullpath(user_id, absolute = true) - padded_id = user_id.to_s.rjust(9, '0') + padded_id = user_id.to_s.rjust(9, "0") part_1 = padded_id[0..2] part_2 = padded_id[3..4] @@ -844,9 +1041,9 @@ class ImportScripts::DiscuzX < ImportScripts::Base case raw when /\[url=forum.php\?mod=redirect&goto=findpost&pid=(\d+)&ptid=\d+\]/ #standard $1 - when /\[url=https?:\/\/#{ORIGINAL_SITE_PREFIX}\/redirect.php\?goto=findpost&pid=(\d+)&ptid=\d+\]/ # old discuz 7 format + when %r{\[url=https?://#{ORIGINAL_SITE_PREFIX}/redirect.php\?goto=findpost&pid=(\d+)&ptid=\d+\]} # old discuz 7 format $1 - when /\[quote\][\S\s]*pid=(\d+)[\S\s]*\[\/quote\]/ # quote + when %r{\[quote\][\S\s]*pid=(\d+)[\S\s]*\[/quote\]} # quote $1 end end @@ -856,18 +1053,18 @@ class ImportScripts::DiscuzX < ImportScripts::Base def upload_inline_image(data) return unless data - puts 'Creating inline image' + puts "Creating inline image" - encoded_photo = data['data:image/png;base64,'.length .. -1] + encoded_photo = data["data:image/png;base64,".length..-1] if encoded_photo raw_file = Base64.decode64(encoded_photo) else - puts 'Error parsed inline photo', data[0..20] + puts "Error parsed inline photo", data[0..20] return end real_filename = "#{SecureRandom.hex}.png" - filename = Tempfile.new(['inline', '.png']) + filename = Tempfile.new(%w[inline .png]) begin filename.binmode filename.write(raw_file) @@ -875,8 +1072,16 @@ class ImportScripts::DiscuzX < ImportScripts::Base upload = create_upload(Discourse::SYSTEM_USER_ID, filename, real_filename) ensure - filename.close rescue nil - filename.unlink rescue nil + begin + filename.close + rescue StandardError + nil + end + begin + filename.unlink + rescue StandardError + nil + end end if upload.nil? || !upload.valid? @@ -890,23 +1095,25 @@ class ImportScripts::DiscuzX < ImportScripts::Base # find the uploaded file and real name from the db def find_upload(user, post, upload_id) - attachment_table = table_name 'forum_attachment' + attachment_table = table_name "forum_attachment" # search for table id - sql = "SELECT a.pid post_id, + sql = + "SELECT a.pid post_id, a.aid upload_id, a.tableid table_id FROM #{attachment_table} a - WHERE a.pid = #{post.custom_fields['import_id']} + WHERE a.pid = #{post.custom_fields["import_id"]} AND a.aid = #{upload_id};" results = mysql_query(sql) unless (meta_data = results.first) - puts "Couldn't find forum_attachment record meta data for post.id = #{post.id}, import_id = #{post.custom_fields['import_id']}" + puts "Couldn't find forum_attachment record meta data for post.id = #{post.id}, import_id = #{post.custom_fields["import_id"]}" return nil end # search for uploaded file meta data - sql = "SELECT a.pid post_id, + sql = + "SELECT a.pid post_id, a.aid upload_id, a.tid topic_id, a.uid user_id, @@ -917,22 +1124,22 @@ class ImportScripts::DiscuzX < ImportScripts::Base a.description description, a.isimage is_image, a.thumb is_thumb - FROM #{attachment_table}_#{meta_data['table_id']} a + FROM #{attachment_table}_#{meta_data["table_id"]} a WHERE a.aid = #{upload_id};" results = mysql_query(sql) unless (row = results.first) - puts "Couldn't find attachment record for post.id = #{post.id}, import_id = #{post.custom_fields['import_id']}" + puts "Couldn't find attachment record for post.id = #{post.id}, import_id = #{post.custom_fields["import_id"]}" return nil end - filename = File.join(DISCUZX_BASE_DIR, ATTACHMENT_DIR, row['attachment_path']) + filename = File.join(DISCUZX_BASE_DIR, ATTACHMENT_DIR, row["attachment_path"]) unless File.exist?(filename) puts "Attachment file doesn't exist: #{filename}" return nil end - real_filename = row['real_filename'] - real_filename.prepend SecureRandom.hex if real_filename[0] == '.' + real_filename = row["real_filename"] + real_filename.prepend SecureRandom.hex if real_filename[0] == "." upload = create_upload(user.id, filename, real_filename) if upload.nil? || !upload.valid? @@ -950,7 +1157,7 @@ class ImportScripts::DiscuzX < ImportScripts::Base end def first_exists(*items) - items.find { |item|!item.blank? } || '' + items.find { |item| !item.blank? } || "" end def mysql_query(sql) diff --git a/script/import_scripts/disqus.rb b/script/import_scripts/disqus.rb index 5dbeb08775..1b6f4185a5 100644 --- a/script/import_scripts/disqus.rb +++ b/script/import_scripts/disqus.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'nokogiri' -require 'optparse' +require "nokogiri" +require "optparse" require File.expand_path(File.dirname(__FILE__) + "/base") class ImportScripts::Disqus < ImportScripts::Base @@ -35,7 +35,7 @@ class ImportScripts::Disqus < ImportScripts::Base by_email = {} @parser.posts.each do |id, p| - next if p[:is_spam] == 'true' || p[:is_deleted] == 'true' + next if p[:is_spam] == "true" || p[:is_deleted] == "true" by_email[p[:author_email]] = { name: p[:author_name], username: p[:author_username] } end @@ -45,13 +45,7 @@ class ImportScripts::Disqus < ImportScripts::Base create_users(by_email.keys) do |email| user = by_email[email] - { - id: email, - email: email, - username: user[:username], - name: user[:name], - merge: true - } + { id: email, email: email, username: user[:username], name: user[:name], merge: true } end end @@ -59,7 +53,6 @@ class ImportScripts::Disqus < ImportScripts::Base puts "", "importing topics..." @parser.threads.each do |id, t| - title = t[:title] title.gsub!(/“/, '"') title.gsub!(/”/, '"') @@ -79,7 +72,7 @@ class ImportScripts::Disqus < ImportScripts::Base if post.present? && post.topic.posts_count <= 1 (t[:posts] || []).each do |p| - post_user = find_existing_user(p[:author_email] || '', p[:author_username]) + post_user = find_existing_user(p[:author_email] || "", p[:author_username]) next unless post_user.present? attrs = { @@ -87,7 +80,7 @@ class ImportScripts::Disqus < ImportScripts::Base topic_id: post.topic_id, raw: p[:cooked], cooked: p[:cooked], - created_at: Date.parse(p[:created_at]) + created_at: Date.parse(p[:created_at]), } if p[:parent_id] @@ -125,23 +118,22 @@ class DisqusSAX < Nokogiri::XML::SAX::Document end def start_element(name, attrs = []) - hashed = Hash[attrs] case name - when 'post' + when "post" @post = {} - @post[:id] = hashed['dsq:id'] if @post - when 'thread' - id = hashed['dsq:id'] + @post[:id] = hashed["dsq:id"] if @post + when "thread" + id = hashed["dsq:id"] if @post thread = @threads[id] thread[:posts] << @post else @thread = { id: id, posts: [] } end - when 'parent' + when "parent" if @post - id = hashed['dsq:id'] + id = hashed["dsq:id"] @post[:parent_id] = id end end @@ -151,10 +143,10 @@ class DisqusSAX < Nokogiri::XML::SAX::Document def end_element(name) case name - when 'post' + when "post" @posts[@post[:id]] = @post @post = nil - when 'thread' + when "thread" if @post.nil? @threads[@thread[:id]] = @thread @thread = nil @@ -165,25 +157,25 @@ class DisqusSAX < Nokogiri::XML::SAX::Document end def characters(str) - record(@post, :author_email, str, 'author', 'email') - record(@post, :author_name, str, 'author', 'name') - record(@post, :author_username, str, 'author', 'username') - record(@post, :author_anonymous, str, 'author', 'isAnonymous') - record(@post, :created_at, str, 'createdAt') - record(@post, :is_deleted, str, 'isDeleted') - record(@post, :is_spam, str, 'isSpam') + record(@post, :author_email, str, "author", "email") + record(@post, :author_name, str, "author", "name") + record(@post, :author_username, str, "author", "username") + record(@post, :author_anonymous, str, "author", "isAnonymous") + record(@post, :created_at, str, "createdAt") + record(@post, :is_deleted, str, "isDeleted") + record(@post, :is_spam, str, "isSpam") - record(@thread, :link, str, 'link') - record(@thread, :title, str, 'title') - record(@thread, :created_at, str, 'createdAt') - record(@thread, :author_email, str, 'author', 'email') - record(@thread, :author_name, str, 'author', 'name') - record(@thread, :author_username, str, 'author', 'username') - record(@thread, :author_anonymous, str, 'author', 'isAnonymous') + record(@thread, :link, str, "link") + record(@thread, :title, str, "title") + record(@thread, :created_at, str, "createdAt") + record(@thread, :author_email, str, "author", "email") + record(@thread, :author_name, str, "author", "name") + record(@thread, :author_username, str, "author", "username") + record(@thread, :author_anonymous, str, "author", "isAnonymous") end def cdata_block(str) - record(@post, :cooked, str, 'message') + record(@post, :cooked, str, "message") end def record(target, sym, str, *params) @@ -205,7 +197,7 @@ class DisqusSAX < Nokogiri::XML::SAX::Document # Remove any threads that have no posts @threads.delete(id) else - t[:posts].delete_if { |p| p[:is_spam] == 'true' || p[:is_deleted] == 'true' } + t[:posts].delete_if { |p| p[:is_spam] == "true" || p[:is_deleted] == "true" } end end diff --git a/script/import_scripts/drupal-6.rb b/script/import_scripts/drupal-6.rb index 182596c63c..3e27c0becd 100644 --- a/script/import_scripts/drupal-6.rb +++ b/script/import_scripts/drupal-6.rb @@ -4,19 +4,19 @@ require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::Drupal < ImportScripts::Base - - DRUPAL_DB = ENV['DRUPAL_DB'] || "newsite3" - VID = ENV['DRUPAL_VID'] || 1 + DRUPAL_DB = ENV["DRUPAL_DB"] || "newsite3" + VID = ENV["DRUPAL_VID"] || 1 def initialize super - @client = Mysql2::Client.new( - host: "localhost", - username: "root", - #password: "password", - database: DRUPAL_DB - ) + @client = + Mysql2::Client.new( + host: "localhost", + username: "root", + #password: "password", + database: DRUPAL_DB, + ) end def categories_query @@ -25,7 +25,12 @@ class ImportScripts::Drupal < ImportScripts::Base def execute create_users(@client.query("SELECT uid id, name, mail email, created FROM users;")) do |row| - { id: row['id'], username: row['name'], email: row['email'], created_at: Time.zone.at(row['created']) } + { + id: row["id"], + username: row["name"], + email: row["email"], + created_at: Time.zone.at(row["created"]), + } end # You'll need to edit the following query for your Drupal install: @@ -34,38 +39,36 @@ class ImportScripts::Drupal < ImportScripts::Base # * Table name may be term_data. # * May need to select a vid other than 1. create_categories(categories_query) do |c| - { id: c['tid'], name: c['name'], description: c['description'] } + { id: c["tid"], name: c["name"], description: c["description"] } end # "Nodes" in Drupal are divided into types. Here we import two types, # and will later import all the comments/replies for each node. # You will need to figure out what the type names are on your install and edit the queries to match. - if ENV['DRUPAL_IMPORT_BLOG'] - create_blog_topics - end + create_blog_topics if ENV["DRUPAL_IMPORT_BLOG"] create_forum_topics create_replies begin - create_admin(email: 'neil.lalonde@discourse.org', username: UserNameSuggester.suggest('neil')) + create_admin(email: "neil.lalonde@discourse.org", username: UserNameSuggester.suggest("neil")) rescue => e - puts '', "Failed to create admin user" + puts "", "Failed to create admin user" puts e.message end end def create_blog_topics - puts '', "creating blog topics" + puts "", "creating blog topics" - create_category({ - name: 'Blog', - user_id: -1, - description: "Articles from the blog" - }, nil) unless Category.find_by_name('Blog') + unless Category.find_by_name("Blog") + create_category({ name: "Blog", user_id: -1, description: "Articles from the blog" }, nil) + end - results = @client.query(" + results = + @client.query( + " SELECT n.nid nid, n.title title, n.uid uid, @@ -76,37 +79,48 @@ class ImportScripts::Drupal < ImportScripts::Base LEFT JOIN node_revisions nr ON nr.vid=n.vid WHERE n.type = 'blog' AND n.status = 1 - ", cache_rows: false) + ", + cache_rows: false, + ) create_posts(results) do |row| { - id: "nid:#{row['nid']}", - user_id: user_id_from_imported_user_id(row['uid']) || -1, - category: 'Blog', - raw: row['body'], - created_at: Time.zone.at(row['created']), - pinned_at: row['sticky'].to_i == 1 ? Time.zone.at(row['created']) : nil, - title: row['title'].try(:strip), - custom_fields: { import_id: "nid:#{row['nid']}" } + id: "nid:#{row["nid"]}", + user_id: user_id_from_imported_user_id(row["uid"]) || -1, + category: "Blog", + raw: row["body"], + created_at: Time.zone.at(row["created"]), + pinned_at: row["sticky"].to_i == 1 ? Time.zone.at(row["created"]) : nil, + title: row["title"].try(:strip), + custom_fields: { + import_id: "nid:#{row["nid"]}", + }, } end end def create_forum_topics - puts '', "creating forum topics" + puts "", "creating forum topics" - total_count = @client.query(" + total_count = + @client.query( + " SELECT COUNT(*) count FROM node n LEFT JOIN forum f ON f.vid=n.vid WHERE n.type = 'forum' AND n.status = 1 - ").first['count'] + ", + ).first[ + "count" + ] batch_size = 1000 batches(batch_size) do |offset| - results = @client.query(" + results = + @client.query( + " SELECT n.nid nid, n.title title, f.tid tid, @@ -121,48 +135,57 @@ class ImportScripts::Drupal < ImportScripts::Base AND n.status = 1 LIMIT #{batch_size} OFFSET #{offset}; - ", cache_rows: false) + ", + cache_rows: false, + ) break if results.size < 1 - next if all_records_exist? :posts, results.map { |p| "nid:#{p['nid']}" } + next if all_records_exist? :posts, results.map { |p| "nid:#{p["nid"]}" } create_posts(results, total: total_count, offset: offset) do |row| { - id: "nid:#{row['nid']}", - user_id: user_id_from_imported_user_id(row['uid']) || -1, - category: category_id_from_imported_category_id(row['tid']), - raw: row['body'], - created_at: Time.zone.at(row['created']), - pinned_at: row['sticky'].to_i == 1 ? Time.zone.at(row['created']) : nil, - title: row['title'].try(:strip) + id: "nid:#{row["nid"]}", + user_id: user_id_from_imported_user_id(row["uid"]) || -1, + category: category_id_from_imported_category_id(row["tid"]), + raw: row["body"], + created_at: Time.zone.at(row["created"]), + pinned_at: row["sticky"].to_i == 1 ? Time.zone.at(row["created"]) : nil, + title: row["title"].try(:strip), } end end end def create_replies - puts '', "creating replies in topics" + puts "", "creating replies in topics" - if ENV['DRUPAL_IMPORT_BLOG'] + if ENV["DRUPAL_IMPORT_BLOG"] node_types = "('forum','blog')" else node_types = "('forum')" end - total_count = @client.query(" + total_count = + @client.query( + " SELECT COUNT(*) count FROM comments c LEFT JOIN node n ON n.nid=c.nid WHERE n.type IN #{node_types} AND n.status = 1 AND c.status=0; - ").first['count'] + ", + ).first[ + "count" + ] batch_size = 1000 batches(batch_size) do |offset| - results = @client.query(" + results = + @client.query( + " SELECT c.cid, c.pid, c.nid, @@ -176,37 +199,36 @@ class ImportScripts::Drupal < ImportScripts::Base AND c.status=0 LIMIT #{batch_size} OFFSET #{offset}; - ", cache_rows: false) + ", + cache_rows: false, + ) break if results.size < 1 - next if all_records_exist? :posts, results.map { |p| "cid:#{p['cid']}" } + next if all_records_exist? :posts, results.map { |p| "cid:#{p["cid"]}" } create_posts(results, total: total_count, offset: offset) do |row| - topic_mapping = topic_lookup_from_imported_post_id("nid:#{row['nid']}") + topic_mapping = topic_lookup_from_imported_post_id("nid:#{row["nid"]}") if topic_mapping && topic_id = topic_mapping[:topic_id] h = { - id: "cid:#{row['cid']}", + id: "cid:#{row["cid"]}", topic_id: topic_id, - user_id: user_id_from_imported_user_id(row['uid']) || -1, - raw: row['body'], - created_at: Time.zone.at(row['timestamp']), + user_id: user_id_from_imported_user_id(row["uid"]) || -1, + raw: row["body"], + created_at: Time.zone.at(row["timestamp"]), } - if row['pid'] - parent = topic_lookup_from_imported_post_id("cid:#{row['pid']}") + if row["pid"] + parent = topic_lookup_from_imported_post_id("cid:#{row["pid"]}") h[:reply_to_post_number] = parent[:post_number] if parent && parent[:post_number] > (1) end h else - puts "No topic found for comment #{row['cid']}" + puts "No topic found for comment #{row["cid"]}" nil end end end end - end -if __FILE__ == $0 - ImportScripts::Drupal.new.perform -end +ImportScripts::Drupal.new.perform if __FILE__ == $0 diff --git a/script/import_scripts/drupal.rb b/script/import_scripts/drupal.rb index 2350a4efbf..ac01a2daa4 100644 --- a/script/import_scripts/drupal.rb +++ b/script/import_scripts/drupal.rb @@ -5,9 +5,8 @@ require "htmlentities" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::Drupal < ImportScripts::Base - - DRUPAL_DB = ENV['DRUPAL_DB'] || "drupal" - VID = ENV['DRUPAL_VID'] || 1 + DRUPAL_DB = ENV["DRUPAL_DB"] || "drupal" + VID = ENV["DRUPAL_VID"] || 1 BATCH_SIZE = 1000 ATTACHMENT_DIR = "/root/files/upload" @@ -16,25 +15,23 @@ class ImportScripts::Drupal < ImportScripts::Base @htmlentities = HTMLEntities.new - @client = Mysql2::Client.new( - host: "localhost", - username: "root", - #password: "password", - database: DRUPAL_DB - ) + @client = + Mysql2::Client.new( + host: "localhost", + username: "root", + #password: "password", + database: DRUPAL_DB, + ) end def execute - import_users import_categories # "Nodes" in Drupal are divided into types. Here we import two types, # and will later import all the comments/replies for each node. # You will need to figure out what the type names are on your install and edit the queries to match. - if ENV['DRUPAL_IMPORT_BLOG'] - import_blog_topics - end + import_blog_topics if ENV["DRUPAL_IMPORT_BLOG"] import_forum_topics @@ -56,7 +53,7 @@ class ImportScripts::Drupal < ImportScripts::Base last_user_id = -1 batches(BATCH_SIZE) do |offset| - users = mysql_query(<<-SQL + users = mysql_query(<<-SQL).to_a SELECT uid, name username, mail email, @@ -66,7 +63,6 @@ class ImportScripts::Drupal < ImportScripts::Base ORDER BY uid LIMIT #{BATCH_SIZE} SQL - ).to_a break if users.empty? @@ -80,12 +76,7 @@ class ImportScripts::Drupal < ImportScripts::Base username = @htmlentities.decode(user["username"]).strip - { - id: user["uid"], - name: username, - email: email, - created_at: Time.zone.at(user["created"]) - } + { id: user["uid"], name: username, email: email, created_at: Time.zone.at(user["created"]) } end end end @@ -99,35 +90,31 @@ class ImportScripts::Drupal < ImportScripts::Base puts "", "importing categories" - categories = mysql_query(<<-SQL + categories = mysql_query(<<-SQL).to_a SELECT tid, name, description FROM taxonomy_term_data WHERE vid = #{VID} SQL - ).to_a create_categories(categories) do |category| { - id: category['tid'], - name: @htmlentities.decode(category['name']).strip, - description: @htmlentities.decode(category['description']).strip + id: category["tid"], + name: @htmlentities.decode(category["name"]).strip, + description: @htmlentities.decode(category["description"]).strip, } end end def import_blog_topics - puts '', "importing blog topics" + puts "", "importing blog topics" - create_category( - { - name: 'Blog', - description: "Articles from the blog" - }, - nil) unless Category.find_by_name('Blog') + unless Category.find_by_name("Blog") + create_category({ name: "Blog", description: "Articles from the blog" }, nil) + end - blogs = mysql_query(<<-SQL + blogs = mysql_query(<<-SQL).to_a SELECT n.nid nid, n.title title, n.uid uid, n.created created, n.sticky sticky, f.body_value body FROM node n, @@ -136,38 +123,38 @@ class ImportScripts::Drupal < ImportScripts::Base AND n.nid = f.entity_id AND n.status = 1 SQL - ).to_a - category_id = Category.find_by_name('Blog').id + category_id = Category.find_by_name("Blog").id create_posts(blogs) do |topic| { - id: "nid:#{topic['nid']}", - user_id: user_id_from_imported_user_id(topic['uid']) || -1, + id: "nid:#{topic["nid"]}", + user_id: user_id_from_imported_user_id(topic["uid"]) || -1, category: category_id, - raw: topic['body'], - created_at: Time.zone.at(topic['created']), - pinned_at: topic['sticky'].to_i == 1 ? Time.zone.at(topic['created']) : nil, - title: topic['title'].try(:strip), - custom_fields: { import_id: "nid:#{topic['nid']}" } + raw: topic["body"], + created_at: Time.zone.at(topic["created"]), + pinned_at: topic["sticky"].to_i == 1 ? Time.zone.at(topic["created"]) : nil, + title: topic["title"].try(:strip), + custom_fields: { + import_id: "nid:#{topic["nid"]}", + }, } end end def import_forum_topics - puts '', "importing forum topics" + puts "", "importing forum topics" - total_count = mysql_query(<<-SQL + total_count = mysql_query(<<-SQL).first["count"] SELECT COUNT(*) count FROM forum_index fi, node n WHERE n.type = 'forum' AND fi.nid = n.nid AND n.status = 1 SQL - ).first['count'] batches(BATCH_SIZE) do |offset| - results = mysql_query(<<-SQL + results = mysql_query(<<-SQL).to_a SELECT fi.nid nid, fi.title title, fi.tid tid, @@ -188,34 +175,33 @@ class ImportScripts::Drupal < ImportScripts::Base LIMIT #{BATCH_SIZE} OFFSET #{offset}; SQL - ).to_a break if results.size < 1 - next if all_records_exist? :posts, results.map { |p| "nid:#{p['nid']}" } + next if all_records_exist? :posts, results.map { |p| "nid:#{p["nid"]}" } create_posts(results, total: total_count, offset: offset) do |row| - raw = preprocess_raw(row['body']) + raw = preprocess_raw(row["body"]) topic = { - id: "nid:#{row['nid']}", - user_id: user_id_from_imported_user_id(row['uid']) || -1, - category: category_id_from_imported_category_id(row['tid']), + id: "nid:#{row["nid"]}", + user_id: user_id_from_imported_user_id(row["uid"]) || -1, + category: category_id_from_imported_category_id(row["tid"]), raw: raw, - created_at: Time.zone.at(row['created']), - pinned_at: row['sticky'].to_i == 1 ? Time.zone.at(row['created']) : nil, - title: row['title'].try(:strip), - views: row['views'] + created_at: Time.zone.at(row["created"]), + pinned_at: row["sticky"].to_i == 1 ? Time.zone.at(row["created"]) : nil, + title: row["title"].try(:strip), + views: row["views"], } - topic[:custom_fields] = { import_solved: true } if row['solved'].present? + topic[:custom_fields] = { import_solved: true } if row["solved"].present? topic end end end def import_replies - puts '', "creating replies in topics" + puts "", "creating replies in topics" - total_count = mysql_query(<<-SQL + total_count = mysql_query(<<-SQL).first["count"] SELECT COUNT(*) count FROM comment c, node n @@ -224,10 +210,9 @@ class ImportScripts::Drupal < ImportScripts::Base AND n.type IN ('article', 'forum') AND n.status = 1 SQL - ).first['count'] batches(BATCH_SIZE) do |offset| - results = mysql_query(<<-SQL + results = mysql_query(<<-SQL).to_a SELECT c.cid, c.pid, c.nid, c.uid, c.created, f.comment_body_value body FROM comment c, @@ -241,30 +226,29 @@ class ImportScripts::Drupal < ImportScripts::Base LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ).to_a break if results.size < 1 - next if all_records_exist? :posts, results.map { |p| "cid:#{p['cid']}" } + next if all_records_exist? :posts, results.map { |p| "cid:#{p["cid"]}" } create_posts(results, total: total_count, offset: offset) do |row| - topic_mapping = topic_lookup_from_imported_post_id("nid:#{row['nid']}") + topic_mapping = topic_lookup_from_imported_post_id("nid:#{row["nid"]}") if topic_mapping && topic_id = topic_mapping[:topic_id] - raw = preprocess_raw(row['body']) + raw = preprocess_raw(row["body"]) h = { - id: "cid:#{row['cid']}", + id: "cid:#{row["cid"]}", topic_id: topic_id, - user_id: user_id_from_imported_user_id(row['uid']) || -1, + user_id: user_id_from_imported_user_id(row["uid"]) || -1, raw: raw, - created_at: Time.zone.at(row['created']), + created_at: Time.zone.at(row["created"]), } - if row['pid'] - parent = topic_lookup_from_imported_post_id("cid:#{row['pid']}") + if row["pid"] + parent = topic_lookup_from_imported_post_id("cid:#{row["pid"]}") h[:reply_to_post_number] = parent[:post_number] if parent && parent[:post_number] > (1) end h else - puts "No topic found for comment #{row['cid']}" + puts "No topic found for comment #{row["cid"]}" nil end end @@ -275,7 +259,7 @@ class ImportScripts::Drupal < ImportScripts::Base puts "", "importing post likes" batches(BATCH_SIZE) do |offset| - likes = mysql_query(<<-SQL + likes = mysql_query(<<-SQL).to_a SELECT flagging_id, fid, entity_id, @@ -286,17 +270,20 @@ class ImportScripts::Drupal < ImportScripts::Base LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ).to_a break if likes.empty? likes.each do |l| - identifier = l['fid'] == 5 ? 'nid' : 'cid' - next unless user_id = user_id_from_imported_user_id(l['uid']) - next unless post_id = post_id_from_imported_post_id("#{identifier}:#{l['entity_id']}") + identifier = l["fid"] == 5 ? "nid" : "cid" + next unless user_id = user_id_from_imported_user_id(l["uid"]) + next unless post_id = post_id_from_imported_post_id("#{identifier}:#{l["entity_id"]}") next unless user = User.find_by(id: user_id) next unless post = Post.find_by(id: post_id) - PostActionCreator.like(user, post) rescue nil + begin + PostActionCreator.like(user, post) + rescue StandardError + nil + end end end end @@ -304,7 +291,8 @@ class ImportScripts::Drupal < ImportScripts::Base def mark_topics_as_solved puts "", "marking topics as solved" - solved_topics = TopicCustomField.where(name: "import_solved").where(value: true).pluck(:topic_id) + solved_topics = + TopicCustomField.where(name: "import_solved").where(value: true).pluck(:topic_id) solved_topics.each do |topic_id| next unless topic = Topic.find(topic_id) @@ -336,8 +324,13 @@ class ImportScripts::Drupal < ImportScripts::Base begin current_count += 1 print_status(current_count, total_count, start_time) - SingleSignOnRecord.create!(user_id: user.id, external_id: external_id, external_email: user.email, last_payload: '') - rescue + SingleSignOnRecord.create!( + user_id: user.id, + external_id: external_id, + external_email: user.email, + last_payload: "", + ) + rescue StandardError next end end @@ -350,14 +343,13 @@ class ImportScripts::Drupal < ImportScripts::Base success_count = 0 fail_count = 0 - total_count = mysql_query(<<-SQL + total_count = mysql_query(<<-SQL).first["count"] SELECT count(field_post_attachment_fid) count FROM field_data_field_post_attachment SQL - ).first["count"] batches(BATCH_SIZE) do |offset| - attachments = mysql_query(<<-SQL + attachments = mysql_query(<<-SQL).to_a SELECT * FROM field_data_field_post_attachment fp LEFT JOIN file_managed fm @@ -365,7 +357,6 @@ class ImportScripts::Drupal < ImportScripts::Base LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ).to_a break if attachments.size < 1 @@ -373,9 +364,11 @@ class ImportScripts::Drupal < ImportScripts::Base current_count += 1 print_status current_count, total_count - identifier = attachment['entity_type'] == "comment" ? "cid" : "nid" - next unless user_id = user_id_from_imported_user_id(attachment['uid']) - next unless post_id = post_id_from_imported_post_id("#{identifier}:#{attachment['entity_id']}") + identifier = attachment["entity_type"] == "comment" ? "cid" : "nid" + next unless user_id = user_id_from_imported_user_id(attachment["uid"]) + unless post_id = post_id_from_imported_post_id("#{identifier}:#{attachment["entity_id"]}") + next + end next unless user = User.find(user_id) next unless post = Post.find(post_id) @@ -392,9 +385,14 @@ class ImportScripts::Drupal < ImportScripts::Base new_raw = "#{new_raw}\n\n#{upload_html}" unless new_raw.include?(upload_html) if new_raw != post.raw - PostRevisor.new(post).revise!(post.user, { raw: new_raw }, bypass_bump: true, edit_reason: "Import attachment from Drupal") + PostRevisor.new(post).revise!( + post.user, + { raw: new_raw }, + bypass_bump: true, + edit_reason: "Import attachment from Drupal", + ) else - puts '', 'Skipped upload: already imported' + puts "", "Skipped upload: already imported" end success_count += 1 @@ -406,13 +404,13 @@ class ImportScripts::Drupal < ImportScripts::Base end def create_permalinks - puts '', 'creating permalinks...' + puts "", "creating permalinks..." Topic.listable_topics.find_each do |topic| begin tcf = topic.custom_fields - if tcf && tcf['import_id'] - node_id = tcf['import_id'][/nid:(\d+)/, 1] + if tcf && tcf["import_id"] + node_id = tcf["import_id"][/nid:(\d+)/, 1] slug = "/node/#{node_id}" Permalink.create(url: slug, topic_id: topic.id) end @@ -424,18 +422,16 @@ class ImportScripts::Drupal < ImportScripts::Base end def find_upload(post, attachment) - uri = attachment['uri'][/public:\/\/upload\/(.+)/, 1] + uri = attachment["uri"][%r{public://upload/(.+)}, 1] real_filename = CGI.unescapeHTML(uri) file = File.join(ATTACHMENT_DIR, real_filename) unless File.exist?(file) - puts "Attachment file #{attachment['filename']} doesn't exist" + puts "Attachment file #{attachment["filename"]} doesn't exist" tmpfile = "attachments_failed.txt" - filename = File.join('/tmp/', tmpfile) - File.open(filename, 'a') { |f| - f.puts attachment['filename'] - } + filename = File.join("/tmp/", tmpfile) + File.open(filename, "a") { |f| f.puts attachment["filename"] } end upload = create_upload(post.user.id || -1, file, real_filename) @@ -452,13 +448,13 @@ class ImportScripts::Drupal < ImportScripts::Base def preprocess_raw(raw) return if raw.blank? # quotes on new lines - raw.gsub!(/\[quote\](.+?)\[\/quote\]/im) { |quote| - quote.gsub!(/\[quote\](.+?)\[\/quote\]/im) { "\n#{$1}\n" } + raw.gsub!(%r{\[quote\](.+?)\[/quote\]}im) do |quote| + quote.gsub!(%r{\[quote\](.+?)\[/quote\]}im) { "\n#{$1}\n" } quote.gsub!(/\n(.+?)/) { "\n> #{$1}" } - } + end # [QUOTE=]...[/QUOTE] - raw.gsub!(/\[quote=([^;\]]+)\](.+?)\[\/quote\]/im) do + raw.gsub!(%r{\[quote=([^;\]]+)\](.+?)\[/quote\]}im) do username, quote = $1, $2 "\n[quote=\"#{username}\"]\n#{quote}\n[/quote]\n" end @@ -468,7 +464,7 @@ class ImportScripts::Drupal < ImportScripts::Base end def postprocess_posts - puts '', 'postprocessing posts' + puts "", "postprocessing posts" current = 0 max = Post.count @@ -479,7 +475,7 @@ class ImportScripts::Drupal < ImportScripts::Base new_raw = raw.dup # replace old topic to new topic links - new_raw.gsub!(/https:\/\/site.com\/forum\/topic\/(\d+)/im) do + new_raw.gsub!(%r{https://site.com/forum/topic/(\d+)}im) do post_id = post_id_from_imported_post_id("nid:#{$1}") next unless post_id topic = Post.find(post_id).topic @@ -487,7 +483,7 @@ class ImportScripts::Drupal < ImportScripts::Base end # replace old comment to reply links - new_raw.gsub!(/https:\/\/site.com\/comment\/(\d+)#comment-\d+/im) do + new_raw.gsub!(%r{https://site.com/comment/(\d+)#comment-\d+}im) do post_id = post_id_from_imported_post_id("cid:#{$1}") next unless post_id post_ref = Post.find(post_id) @@ -498,8 +494,8 @@ class ImportScripts::Drupal < ImportScripts::Base post.raw = new_raw post.save end - rescue - puts '', "Failed rewrite on post: #{post.id}" + rescue StandardError + puts "", "Failed rewrite on post: #{post.id}" ensure print_status(current += 1, max) end @@ -507,15 +503,15 @@ class ImportScripts::Drupal < ImportScripts::Base end def import_gravatars - puts '', 'importing gravatars' + puts "", "importing gravatars" current = 0 max = User.count User.find_each do |user| begin user.create_user_avatar(user_id: user.id) unless user.user_avatar user.user_avatar.update_gravatar! - rescue - puts '', 'Failed avatar update on user #{user.id}' + rescue StandardError + puts "", 'Failed avatar update on user #{user.id}' ensure print_status(current += 1, max) end @@ -523,15 +519,12 @@ class ImportScripts::Drupal < ImportScripts::Base end def parse_datetime(time) - DateTime.strptime(time, '%s') + DateTime.strptime(time, "%s") end def mysql_query(sql) @client.query(sql, cache_rows: true) end - end -if __FILE__ == $0 - ImportScripts::Drupal.new.perform -end +ImportScripts::Drupal.new.perform if __FILE__ == $0 diff --git a/script/import_scripts/drupal_json.rb b/script/import_scripts/drupal_json.rb index d69f21e01b..f97ae683e1 100644 --- a/script/import_scripts/drupal_json.rb +++ b/script/import_scripts/drupal_json.rb @@ -5,7 +5,6 @@ require File.expand_path(File.dirname(__FILE__) + "/base.rb") # Edit the constants and initialize method for your import data. class ImportScripts::DrupalJson < ImportScripts::Base - JSON_FILES_DIR = "/Users/techapj/Documents" def initialize @@ -28,20 +27,18 @@ class ImportScripts::DrupalJson < ImportScripts::Base end def import_users - puts '', "Importing users" + puts "", "Importing users" create_users(@users_json) do |u| { id: u["uid"], name: u["name"], email: u["mail"], - created_at: Time.zone.at(u["created"].to_i) + created_at: Time.zone.at(u["created"].to_i), } end EmailToken.delete_all end end -if __FILE__ == $0 - ImportScripts::DrupalJson.new.perform -end +ImportScripts::DrupalJson.new.perform if __FILE__ == $0 diff --git a/script/import_scripts/drupal_qa.rb b/script/import_scripts/drupal_qa.rb index a8febbd41c..948b04590d 100644 --- a/script/import_scripts/drupal_qa.rb +++ b/script/import_scripts/drupal_qa.rb @@ -5,41 +5,51 @@ require File.expand_path(File.dirname(__FILE__) + "/base.rb") require File.expand_path(File.dirname(__FILE__) + "/drupal.rb") class ImportScripts::DrupalQA < ImportScripts::Drupal - def categories_query - result = @client.query("SELECT n.nid, GROUP_CONCAT(ti.tid) AS tids + result = + @client.query( + "SELECT n.nid, GROUP_CONCAT(ti.tid) AS tids FROM node AS n INNER JOIN taxonomy_index AS ti ON ti.nid = n.nid WHERE n.type = 'question' AND n.status = 1 - GROUP BY n.nid") + GROUP BY n.nid", + ) categories = {} result.each do |r| - tids = r['tids'] + tids = r["tids"] if tids.present? - tids = tids.split(',') + tids = tids.split(",") categories[tids[0].to_i] = true end end - @client.query("SELECT tid, name, description FROM taxonomy_term_data WHERE tid IN (#{categories.keys.join(',')})") + @client.query( + "SELECT tid, name, description FROM taxonomy_term_data WHERE tid IN (#{categories.keys.join(",")})", + ) end def create_forum_topics + puts "", "creating forum topics" - puts '', "creating forum topics" - - total_count = @client.query(" + total_count = + @client.query( + " SELECT COUNT(*) count FROM node n WHERE n.type = 'question' - AND n.status = 1;").first['count'] + AND n.status = 1;", + ).first[ + "count" + ] batch_size = 1000 batches(batch_size) do |offset| - results = @client.query(" + results = + @client.query( + " SELECT n.nid, n.title, GROUP_CONCAT(t.tid) AS tid, @@ -54,40 +64,48 @@ class ImportScripts::DrupalQA < ImportScripts::Drupal GROUP BY n.nid, n.title, n.uid, n.created, f.body_value LIMIT #{batch_size} OFFSET #{offset} - ", cache_rows: false) + ", + cache_rows: false, + ) break if results.size < 1 - next if all_records_exist? :posts, results.map { |p| "nid:#{p['nid']}" } + next if all_records_exist? :posts, results.map { |p| "nid:#{p["nid"]}" } create_posts(results, total: total_count, offset: offset) do |row| { - id: "nid:#{row['nid']}", - user_id: user_id_from_imported_user_id(row['uid']) || -1, - category: category_id_from_imported_category_id((row['tid'] || '').split(',')[0]), - raw: row['body'], - created_at: Time.zone.at(row['created']), + id: "nid:#{row["nid"]}", + user_id: user_id_from_imported_user_id(row["uid"]) || -1, + category: category_id_from_imported_category_id((row["tid"] || "").split(",")[0]), + raw: row["body"], + created_at: Time.zone.at(row["created"]), pinned_at: nil, - title: row['title'].try(:strip) + title: row["title"].try(:strip), } end end end def create_direct_replies - puts '', "creating replies in topics" + puts "", "creating replies in topics" - total_count = @client.query(" + total_count = + @client.query( + " SELECT COUNT(*) count FROM node n WHERE n.type = 'answer' - AND n.status = 1;").first['count'] + AND n.status = 1;", + ).first[ + "count" + ] batch_size = 1000 batches(batch_size) do |offset| - - results = @client.query(" + results = + @client.query( + " SELECT n.nid AS cid, q.field_answer_question_nid AS nid, n.uid, @@ -100,25 +118,27 @@ class ImportScripts::DrupalQA < ImportScripts::Drupal AND n.type = 'answer' LIMIT #{batch_size} OFFSET #{offset} - ", cache_rows: false) + ", + cache_rows: false, + ) break if results.size < 1 - next if all_records_exist? :posts, results.map { |p| "cid:#{p['cid']}" } + next if all_records_exist? :posts, results.map { |p| "cid:#{p["cid"]}" } create_posts(results, total: total_count, offset: offset) do |row| - topic_mapping = topic_lookup_from_imported_post_id("nid:#{row['nid']}") + topic_mapping = topic_lookup_from_imported_post_id("nid:#{row["nid"]}") if topic_mapping && topic_id = topic_mapping[:topic_id] h = { - id: "cid:#{row['cid']}", + id: "cid:#{row["cid"]}", topic_id: topic_id, - user_id: user_id_from_imported_user_id(row['uid']) || -1, - raw: row['body'], - created_at: Time.zone.at(row['created']), + user_id: user_id_from_imported_user_id(row["uid"]) || -1, + raw: row["body"], + created_at: Time.zone.at(row["created"]), } h else - puts "No topic found for answer #{row['cid']}" + puts "No topic found for answer #{row["cid"]}" nil end end @@ -126,21 +146,27 @@ class ImportScripts::DrupalQA < ImportScripts::Drupal end def create_nested_replies - puts '', "creating nested replies to posts in topics" + puts "", "creating nested replies to posts in topics" - total_count = @client.query(" + total_count = + @client.query( + " SELECT COUNT(c.cid) count FROM node n INNER JOIN comment AS c ON n.nid = c.nid WHERE n.type = 'question' - AND n.status = 1;").first['count'] + AND n.status = 1;", + ).first[ + "count" + ] batch_size = 1000 batches(batch_size) do |offset| - # WARNING: If there are more than 1000000 this might have to be revisited - results = @client.query(" + results = + @client.query( + " SELECT (c.cid + 1000000) as cid, c.nid, c.uid, @@ -153,45 +179,53 @@ class ImportScripts::DrupalQA < ImportScripts::Drupal AND n.type = 'question' LIMIT #{batch_size} OFFSET #{offset} - ", cache_rows: false) + ", + cache_rows: false, + ) break if results.size < 1 - next if all_records_exist? :posts, results.map { |p| "cid:#{p['cid']}" } + next if all_records_exist? :posts, results.map { |p| "cid:#{p["cid"]}" } create_posts(results, total: total_count, offset: offset) do |row| - topic_mapping = topic_lookup_from_imported_post_id("nid:#{row['nid']}") + topic_mapping = topic_lookup_from_imported_post_id("nid:#{row["nid"]}") if topic_mapping && topic_id = topic_mapping[:topic_id] h = { - id: "cid:#{row['cid']}", + id: "cid:#{row["cid"]}", topic_id: topic_id, - user_id: user_id_from_imported_user_id(row['uid']) || -1, - raw: row['body'], - created_at: Time.zone.at(row['created']), + user_id: user_id_from_imported_user_id(row["uid"]) || -1, + raw: row["body"], + created_at: Time.zone.at(row["created"]), } h else - puts "No topic found for comment #{row['cid']}" + puts "No topic found for comment #{row["cid"]}" nil end end end - puts '', "creating nested replies to answers in topics" + puts "", "creating nested replies to answers in topics" - total_count = @client.query(" + total_count = + @client.query( + " SELECT COUNT(c.cid) count FROM node n INNER JOIN comment AS c ON n.nid = c.nid WHERE n.type = 'answer' - AND n.status = 1;").first['count'] + AND n.status = 1;", + ).first[ + "count" + ] batch_size = 1000 batches(batch_size) do |offset| - # WARNING: If there are more than 1000000 this might have to be revisited - results = @client.query(" + results = + @client.query( + " SELECT (c.cid + 1000000) as cid, q.field_answer_question_nid AS nid, c.uid, @@ -205,25 +239,27 @@ class ImportScripts::DrupalQA < ImportScripts::Drupal AND n.type = 'answer' LIMIT #{batch_size} OFFSET #{offset} - ", cache_rows: false) + ", + cache_rows: false, + ) break if results.size < 1 - next if all_records_exist? :posts, results.map { |p| "cid:#{p['cid']}" } + next if all_records_exist? :posts, results.map { |p| "cid:#{p["cid"]}" } create_posts(results, total: total_count, offset: offset) do |row| - topic_mapping = topic_lookup_from_imported_post_id("nid:#{row['nid']}") + topic_mapping = topic_lookup_from_imported_post_id("nid:#{row["nid"]}") if topic_mapping && topic_id = topic_mapping[:topic_id] h = { - id: "cid:#{row['cid']}", + id: "cid:#{row["cid"]}", topic_id: topic_id, - user_id: user_id_from_imported_user_id(row['uid']) || -1, - raw: row['body'], - created_at: Time.zone.at(row['created']), + user_id: user_id_from_imported_user_id(row["uid"]) || -1, + raw: row["body"], + created_at: Time.zone.at(row["created"]), } h else - puts "No topic found for comment #{row['cid']}" + puts "No topic found for comment #{row["cid"]}" nil end end @@ -234,9 +270,6 @@ class ImportScripts::DrupalQA < ImportScripts::Drupal create_direct_replies create_nested_replies end - end -if __FILE__ == $0 - ImportScripts::DrupalQA.new.perform -end +ImportScripts::DrupalQA.new.perform if __FILE__ == $0 diff --git a/script/import_scripts/elgg.rb b/script/import_scripts/elgg.rb index 6eb62eb031..1293e17179 100644 --- a/script/import_scripts/elgg.rb +++ b/script/import_scripts/elgg.rb @@ -1,22 +1,16 @@ # frozen_string_literal: true -require 'mysql2' +require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::Elgg < ImportScripts::Base - BATCH_SIZE ||= 1000 def initialize super - @client = Mysql2::Client.new( - host: "127.0.0.1", - port: "3306", - username: "", - database: "", - password: "" - ) + @client = + Mysql2::Client.new(host: "127.0.0.1", port: "3306", username: "", database: "", password: "") SiteSetting.max_username_length = 50 end @@ -31,7 +25,7 @@ class ImportScripts::Elgg < ImportScripts::Base def create_avatar(user, guid) puts "#{@path}" # Put your avatar at the root of discourse in this folder: - path_prefix = 'import/data/www/' + path_prefix = "import/data/www/" # https://github.com/Elgg/Elgg/blob/2fc9c1910a9169bbe4010026c61d8e41a5b56239/engine/classes/ElggDiskFilestore.php#L24 # const BUCKET_SIZE = 5000; bucket_size = 5000 @@ -40,13 +34,11 @@ class ImportScripts::Elgg < ImportScripts::Base bucket_id = [guid / bucket_size * bucket_size, 1].max avatar_path = File.join(path_prefix, bucket_id.to_s, "/#{guid}/profile/#{guid}master.jpg") - if File.exist?(avatar_path) - @uploader.create_avatar(user, avatar_path) - end + @uploader.create_avatar(user, avatar_path) if File.exist?(avatar_path) end def grant_admin(user, is_admin) - if is_admin == 'yes' + if is_admin == "yes" puts "", "#{user.username} is granted admin!" user.grant_admin! end @@ -56,10 +48,11 @@ class ImportScripts::Elgg < ImportScripts::Base puts "", "importing users..." last_user_id = -1 - total_users = mysql_query("select count(*) from elgg_users_entity where banned='no'").first["count"] + total_users = + mysql_query("select count(*) from elgg_users_entity where banned='no'").first["count"] batches(BATCH_SIZE) do |offset| - users = mysql_query(<<-SQL + users = mysql_query(<<-SQL).to_a select eue.guid, eue.username, eue.name, eue.email, eue.admin, max(case when ems1.string='cae_structure' then ems2.string end)cae_structure, max(case when ems1.string='location' then ems2.string end)location, @@ -76,7 +69,6 @@ class ImportScripts::Elgg < ImportScripts::Base group by eue.guid LIMIT #{BATCH_SIZE} SQL - ).to_a break if users.empty? @@ -97,11 +89,12 @@ class ImportScripts::Elgg < ImportScripts::Base name: u["name"], website: u["website"], bio_raw: u["briefdescription"].to_s + " " + u["cae_structure"].to_s, - post_create_action: proc do |user| - create_avatar(user, u["guid"]) - #add_user_to_group(user, u["cae_structure"]) - grant_admin(user, u["admin"]) - end + post_create_action: + proc do |user| + create_avatar(user, u["guid"]) + #add_user_to_group(user, u["cae_structure"]) + grant_admin(user, u["admin"]) + end, } end end @@ -115,9 +108,9 @@ class ImportScripts::Elgg < ImportScripts::Base create_categories(categories) do |c| { - id: c['guid'], - name: CGI.unescapeHTML(c['name']), - description: CGI.unescapeHTML(c['description']) + id: c["guid"], + name: CGI.unescapeHTML(c["name"]), + description: CGI.unescapeHTML(c["description"]), } end end @@ -125,10 +118,13 @@ class ImportScripts::Elgg < ImportScripts::Base def import_topics puts "", "creating topics" - total_count = mysql_query("select count(*) count from elgg_entities where subtype = 32;").first["count"] + total_count = + mysql_query("select count(*) count from elgg_entities where subtype = 32;").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query(" + results = + mysql_query( + " SELECT ee.guid id, owner_guid user_id, @@ -143,30 +139,35 @@ class ImportScripts::Elgg < ImportScripts::Base ORDER BY ee.guid LIMIT #{BATCH_SIZE} OFFSET #{offset}; - ") + ", + ) break if results.size < 1 - next if all_records_exist? :posts, results.map { |m| m['id'].to_i } + next if all_records_exist? :posts, results.map { |m| m["id"].to_i } create_posts(results, total: total_count, offset: offset) do |m| { - id: m['id'], - user_id: user_id_from_imported_user_id(m['user_id']) || -1, - raw: CGI.unescapeHTML(m['raw']), - created_at: Time.zone.at(m['created_at']), - category: category_id_from_imported_category_id(m['category_id']), - title: CGI.unescapeHTML(m['title']), - post_create_action: proc do |post| - tag_names = mysql_query(" + id: m["id"], + user_id: user_id_from_imported_user_id(m["user_id"]) || -1, + raw: CGI.unescapeHTML(m["raw"]), + created_at: Time.zone.at(m["created_at"]), + category: category_id_from_imported_category_id(m["category_id"]), + title: CGI.unescapeHTML(m["title"]), + post_create_action: + proc do |post| + tag_names = + mysql_query( + " select ms.string from elgg_metadata md join elgg_metastrings ms on md.value_id = ms.id where name_id = 43 - and entity_guid = #{m['id']}; - ").map { |tag| tag['string'] } - DiscourseTagging.tag_topic_by_names(post.topic, staff_guardian, tag_names) - end + and entity_guid = #{m["id"]}; + ", + ).map { |tag| tag["string"] } + DiscourseTagging.tag_topic_by_names(post.topic, staff_guardian, tag_names) + end, } end end @@ -179,10 +180,13 @@ class ImportScripts::Elgg < ImportScripts::Base def import_posts puts "", "creating posts" - total_count = mysql_query("SELECT count(*) count FROM elgg_entities WHERE subtype = 42").first["count"] + total_count = + mysql_query("SELECT count(*) count FROM elgg_entities WHERE subtype = 42").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query(" + results = + mysql_query( + " SELECT ee.guid id, container_guid topic_id, @@ -195,19 +199,20 @@ class ImportScripts::Elgg < ImportScripts::Base ORDER BY ee.guid LIMIT #{BATCH_SIZE} OFFSET #{offset}; - ") + ", + ) break if results.size < 1 - next if all_records_exist? :posts, results.map { |m| m['id'].to_i } + next if all_records_exist? :posts, results.map { |m| m["id"].to_i } create_posts(results, total: total_count, offset: offset) do |m| { - id: m['id'], - user_id: user_id_from_imported_user_id(m['user_id']) || -1, - topic_id: topic_lookup_from_imported_post_id(m['topic_id'])[:topic_id], - raw: CGI.unescapeHTML(m['raw']), - created_at: Time.zone.at(m['created_at']), + id: m["id"], + user_id: user_id_from_imported_user_id(m["user_id"]) || -1, + topic_id: topic_lookup_from_imported_post_id(m["topic_id"])[:topic_id], + raw: CGI.unescapeHTML(m["raw"]), + created_at: Time.zone.at(m["created_at"]), } end end @@ -216,7 +221,6 @@ class ImportScripts::Elgg < ImportScripts::Base def mysql_query(sql) @client.query(sql, cache_rows: false) end - end ImportScripts::Elgg.new.perform diff --git a/script/import_scripts/flarum_import.rb b/script/import_scripts/flarum_import.rb index ea4eb3dddf..737ee3a86e 100644 --- a/script/import_scripts/flarum_import.rb +++ b/script/import_scripts/flarum_import.rb @@ -1,60 +1,62 @@ # frozen_string_literal: true require "mysql2" -require 'time' -require 'date' +require "time" +require "date" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::FLARUM < ImportScripts::Base #SET THE APPROPRIATE VALUES FOR YOUR MYSQL CONNECTION - FLARUM_HOST ||= ENV['FLARUM_HOST'] || "db_host" - FLARUM_DB ||= ENV['FLARUM_DB'] || "db_name" + FLARUM_HOST ||= ENV["FLARUM_HOST"] || "db_host" + FLARUM_DB ||= ENV["FLARUM_DB"] || "db_name" BATCH_SIZE ||= 1000 - FLARUM_USER ||= ENV['FLARUM_USER'] || "db_user" - FLARUM_PW ||= ENV['FLARUM_PW'] || "db_user_pass" + FLARUM_USER ||= ENV["FLARUM_USER"] || "db_user" + FLARUM_PW ||= ENV["FLARUM_PW"] || "db_user_pass" def initialize super - @client = Mysql2::Client.new( - host: FLARUM_HOST, - username: FLARUM_USER, - password: FLARUM_PW, - database: FLARUM_DB - ) + @client = + Mysql2::Client.new( + host: FLARUM_HOST, + username: FLARUM_USER, + password: FLARUM_PW, + database: FLARUM_DB, + ) end def execute - import_users import_categories import_posts - end def import_users - puts '', "creating users" - total_count = mysql_query("SELECT count(*) count FROM users;").first['count'] + puts "", "creating users" + total_count = mysql_query("SELECT count(*) count FROM users;").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query( - "SELECT id, username, email, joined_at, last_seen_at + results = + mysql_query( + "SELECT id, username, email, joined_at, last_seen_at FROM users LIMIT #{BATCH_SIZE} - OFFSET #{offset};") + OFFSET #{offset};", + ) break if results.size < 1 next if all_records_exist? :users, results.map { |u| u["id"].to_i } create_users(results, total: total_count, offset: offset) do |user| - { id: user['id'], - email: user['email'], - username: user['username'], - name: user['username'], - created_at: user['joined_at'], - last_seen_at: user['last_seen_at'] + { + id: user["id"], + email: user["email"], + username: user["username"], + name: user["username"], + created_at: user["joined_at"], + last_seen_at: user["last_seen_at"], } end end @@ -63,30 +65,31 @@ class ImportScripts::FLARUM < ImportScripts::Base def import_categories puts "", "importing top level categories..." - categories = mysql_query(" + categories = + mysql_query( + " SELECT id, name, description, position FROM tags ORDER BY position ASC - ").to_a + ", + ).to_a - create_categories(categories) do |category| - { - id: category["id"], - name: category["name"] - } - end + create_categories(categories) { |category| { id: category["id"], name: category["name"] } } puts "", "importing children categories..." - children_categories = mysql_query(" + children_categories = + mysql_query( + " SELECT id, name, description, position FROM tags ORDER BY position - ").to_a + ", + ).to_a create_categories(children_categories) do |category| { - id: "child##{category['id']}", + id: "child##{category["id"]}", name: category["name"], description: category["description"], } @@ -99,7 +102,9 @@ class ImportScripts::FLARUM < ImportScripts::Base total_count = mysql_query("SELECT count(*) count from posts").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query(" + results = + mysql_query( + " SELECT p.id id, d.id topic_id, d.title title, @@ -116,29 +121,30 @@ class ImportScripts::FLARUM < ImportScripts::Base ORDER BY p.created_at LIMIT #{BATCH_SIZE} OFFSET #{offset}; - ").to_a + ", + ).to_a break if results.size < 1 - next if all_records_exist? :posts, results.map { |m| m['id'].to_i } + next if all_records_exist? :posts, results.map { |m| m["id"].to_i } create_posts(results, total: total_count, offset: offset) do |m| skip = false mapped = {} - mapped[:id] = m['id'] - mapped[:user_id] = user_id_from_imported_user_id(m['user_id']) || -1 - mapped[:raw] = process_FLARUM_post(m['raw'], m['id']) - mapped[:created_at] = Time.zone.at(m['created_at']) + mapped[:id] = m["id"] + mapped[:user_id] = user_id_from_imported_user_id(m["user_id"]) || -1 + mapped[:raw] = process_FLARUM_post(m["raw"], m["id"]) + mapped[:created_at] = Time.zone.at(m["created_at"]) - if m['id'] == m['first_post_id'] - mapped[:category] = category_id_from_imported_category_id("child##{m['category_id']}") - mapped[:title] = CGI.unescapeHTML(m['title']) + if m["id"] == m["first_post_id"] + mapped[:category] = category_id_from_imported_category_id("child##{m["category_id"]}") + mapped[:title] = CGI.unescapeHTML(m["title"]) else - parent = topic_lookup_from_imported_post_id(m['first_post_id']) + parent = topic_lookup_from_imported_post_id(m["first_post_id"]) if parent mapped[:topic_id] = parent[:topic_id] else - puts "Parent post #{m['first_post_id']} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" + puts "Parent post #{m["first_post_id"]} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" skip = true end end diff --git a/script/import_scripts/fluxbb.rb b/script/import_scripts/fluxbb.rb index 38b84ed3c1..9af64457c8 100644 --- a/script/import_scripts/fluxbb.rb +++ b/script/import_scripts/fluxbb.rb @@ -17,23 +17,23 @@ export FLUXBB_PREFIX="" # Call it like this: # RAILS_ENV=production bundle exec ruby script/import_scripts/fluxbb.rb class ImportScripts::FluxBB < ImportScripts::Base - - FLUXBB_HOST ||= ENV['FLUXBB_HOST'] || "localhost" - FLUXBB_DB ||= ENV['FLUXBB_DB'] || "fluxbb" + FLUXBB_HOST ||= ENV["FLUXBB_HOST"] || "localhost" + FLUXBB_DB ||= ENV["FLUXBB_DB"] || "fluxbb" BATCH_SIZE ||= 1000 - FLUXBB_USER ||= ENV['FLUXBB_USER'] || "root" - FLUXBB_PW ||= ENV['FLUXBB_PW'] || "" - FLUXBB_PREFIX ||= ENV['FLUXBB_PREFIX'] || "" + FLUXBB_USER ||= ENV["FLUXBB_USER"] || "root" + FLUXBB_PW ||= ENV["FLUXBB_PW"] || "" + FLUXBB_PREFIX ||= ENV["FLUXBB_PREFIX"] || "" def initialize super - @client = Mysql2::Client.new( - host: FLUXBB_HOST, - username: FLUXBB_USER, - password: FLUXBB_PW, - database: FLUXBB_DB - ) + @client = + Mysql2::Client.new( + host: FLUXBB_HOST, + username: FLUXBB_USER, + password: FLUXBB_PW, + database: FLUXBB_DB, + ) end def execute @@ -45,64 +45,67 @@ class ImportScripts::FluxBB < ImportScripts::Base end def import_groups - puts '', "creating groups" + puts "", "creating groups" - results = mysql_query( - "SELECT g_id id, g_title name, g_user_title title - FROM #{FLUXBB_PREFIX}groups") + results = + mysql_query( + "SELECT g_id id, g_title name, g_user_title title + FROM #{FLUXBB_PREFIX}groups", + ) - customgroups = results.select { |group| group['id'] > 2 } + customgroups = results.select { |group| group["id"] > 2 } create_groups(customgroups) do |group| - { id: group['id'], - name: group['name'], - title: group['title'] } + { id: group["id"], name: group["name"], title: group["title"] } end end def import_users - puts '', "creating users" + puts "", "creating users" - total_count = mysql_query("SELECT count(*) count FROM #{FLUXBB_PREFIX}users;").first['count'] + total_count = mysql_query("SELECT count(*) count FROM #{FLUXBB_PREFIX}users;").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query( - "SELECT id, username, realname name, url website, email email, registered created_at, + results = + mysql_query( + "SELECT id, username, realname name, url website, email email, registered created_at, registration_ip registration_ip_address, last_visit last_visit_time, last_email_sent last_emailed_at, location, group_id FROM #{FLUXBB_PREFIX}users LIMIT #{BATCH_SIZE} - OFFSET #{offset};") + OFFSET #{offset};", + ) break if results.size < 1 next if all_records_exist? :users, results.map { |u| u["id"].to_i } create_users(results, total: total_count, offset: offset) do |user| - { id: user['id'], - email: user['email'], - username: user['username'], - name: user['name'], - created_at: Time.zone.at(user['created_at']), - website: user['website'], - registration_ip_address: user['registration_ip_address'], - last_seen_at: Time.zone.at(user['last_visit_time']), - last_emailed_at: user['last_emailed_at'] == nil ? 0 : Time.zone.at(user['last_emailed_at']), - location: user['location'], - moderator: user['group_id'] == 2, - admin: user['group_id'] == 1 } + { + id: user["id"], + email: user["email"], + username: user["username"], + name: user["name"], + created_at: Time.zone.at(user["created_at"]), + website: user["website"], + registration_ip_address: user["registration_ip_address"], + last_seen_at: Time.zone.at(user["last_visit_time"]), + last_emailed_at: + user["last_emailed_at"] == nil ? 0 : Time.zone.at(user["last_emailed_at"]), + location: user["location"], + moderator: user["group_id"] == 2, + admin: user["group_id"] == 1, + } end - groupusers = results.select { |user| user['group_id'] > 2 } + groupusers = results.select { |user| user["group_id"] > 2 } groupusers.each do |user| - if user['group_id'] - user_id = user_id_from_imported_user_id(user['id']) - group_id = group_id_from_imported_group_id(user['group_id']) + if user["group_id"] + user_id = user_id_from_imported_user_id(user["id"]) + group_id = group_id_from_imported_group_id(user["group_id"]) - if user_id && group_id - GroupUser.find_or_create_by(user_id: user_id, group_id: group_id) - end + GroupUser.find_or_create_by(user_id: user_id, group_id: group_id) if user_id && group_id end end end @@ -111,33 +114,34 @@ class ImportScripts::FluxBB < ImportScripts::Base def import_categories puts "", "importing top level categories..." - categories = mysql_query(" + categories = + mysql_query( + " SELECT id, cat_name name, disp_position position FROM #{FLUXBB_PREFIX}categories ORDER BY id ASC - ").to_a + ", + ).to_a - create_categories(categories) do |category| - { - id: category["id"], - name: category["name"] - } - end + create_categories(categories) { |category| { id: category["id"], name: category["name"] } } puts "", "importing children categories..." - children_categories = mysql_query(" + children_categories = + mysql_query( + " SELECT id, forum_name name, forum_desc description, disp_position position, cat_id parent_category_id FROM #{FLUXBB_PREFIX}forums ORDER BY id - ").to_a + ", + ).to_a create_categories(children_categories) do |category| { - id: "child##{category['id']}", + id: "child##{category["id"]}", name: category["name"], description: category["description"], - parent_category_id: category_id_from_imported_category_id(category["parent_category_id"]) + parent_category_id: category_id_from_imported_category_id(category["parent_category_id"]), } end end @@ -148,7 +152,9 @@ class ImportScripts::FluxBB < ImportScripts::Base total_count = mysql_query("SELECT count(*) count from #{FLUXBB_PREFIX}posts").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query(" + results = + mysql_query( + " SELECT p.id id, t.id topic_id, t.forum_id category_id, @@ -163,29 +169,30 @@ class ImportScripts::FluxBB < ImportScripts::Base ORDER BY p.posted LIMIT #{BATCH_SIZE} OFFSET #{offset}; - ").to_a + ", + ).to_a break if results.size < 1 - next if all_records_exist? :posts, results.map { |m| m['id'].to_i } + next if all_records_exist? :posts, results.map { |m| m["id"].to_i } create_posts(results, total: total_count, offset: offset) do |m| skip = false mapped = {} - mapped[:id] = m['id'] - mapped[:user_id] = user_id_from_imported_user_id(m['user_id']) || -1 - mapped[:raw] = process_fluxbb_post(m['raw'], m['id']) - mapped[:created_at] = Time.zone.at(m['created_at']) + mapped[:id] = m["id"] + mapped[:user_id] = user_id_from_imported_user_id(m["user_id"]) || -1 + mapped[:raw] = process_fluxbb_post(m["raw"], m["id"]) + mapped[:created_at] = Time.zone.at(m["created_at"]) - if m['id'] == m['first_post_id'] - mapped[:category] = category_id_from_imported_category_id("child##{m['category_id']}") - mapped[:title] = CGI.unescapeHTML(m['title']) + if m["id"] == m["first_post_id"] + mapped[:category] = category_id_from_imported_category_id("child##{m["category_id"]}") + mapped[:title] = CGI.unescapeHTML(m["title"]) else - parent = topic_lookup_from_imported_post_id(m['first_post_id']) + parent = topic_lookup_from_imported_post_id(m["first_post_id"]) if parent mapped[:topic_id] = parent[:topic_id] else - puts "Parent post #{m['first_post_id']} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" + puts "Parent post #{m["first_post_id"]} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" skip = true end end @@ -196,16 +203,16 @@ class ImportScripts::FluxBB < ImportScripts::Base end def suspend_users - puts '', "updating banned users" + puts "", "updating banned users" banned = 0 failed = 0 - total = mysql_query("SELECT count(*) count FROM #{FLUXBB_PREFIX}bans").first['count'] + total = mysql_query("SELECT count(*) count FROM #{FLUXBB_PREFIX}bans").first["count"] system_user = Discourse.system_user mysql_query("SELECT username, email FROM #{FLUXBB_PREFIX}bans").each do |b| - user = User.find_by_email(b['email']) + user = User.find_by_email(b["email"]) if user user.suspended_at = Time.now user.suspended_till = 200.years.from_now @@ -218,7 +225,7 @@ class ImportScripts::FluxBB < ImportScripts::Base failed += 1 end else - puts "Not found: #{b['email']}" + puts "Not found: #{b["email"]}" failed += 1 end @@ -233,15 +240,15 @@ class ImportScripts::FluxBB < ImportScripts::Base s.gsub!(/(?:.*)/, '\1') # Some links look like this: http://www.onegameamonth.com - s.gsub!(/(.+)<\/a>/, '[\2](\1)') + s.gsub!(%r{(.+)}, '[\2](\1)') # Many bbcode tags have a hash attached to them. Examples: # [url=https://google.com:1qh1i7ky]click here[/url:1qh1i7ky] # [quote="cybereality":b0wtlzex]Some text.[/quote:b0wtlzex] - s.gsub!(/:(?:\w{8})\]/, ']') + s.gsub!(/:(?:\w{8})\]/, "]") # Remove video tags. - s.gsub!(/(^\[video=.*?\])|(\[\/video\]$)/, '') + s.gsub!(%r{(^\[video=.*?\])|(\[/video\]$)}, "") s = CGI.unescapeHTML(s) @@ -249,7 +256,7 @@ class ImportScripts::FluxBB < ImportScripts::Base # [http://answers.yahoo.com/question/index ... 223AAkkPli](http://answers.yahoo.com/question/index?qid=20070920134223AAkkPli) # # Work around it for now: - s.gsub!(/\[http(s)?:\/\/(www\.)?/, '[') + s.gsub!(%r{\[http(s)?://(www\.)?}, "[") s end diff --git a/script/import_scripts/friendsmegplus.rb b/script/import_scripts/friendsmegplus.rb index d66eed12cc..3bcab17c90 100644 --- a/script/import_scripts/friendsmegplus.rb +++ b/script/import_scripts/friendsmegplus.rb @@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require 'csv' +require "csv" # Importer for Friends+Me Google+ Exporter (F+MG+E) output. # @@ -32,18 +32,18 @@ require 'csv' # Edit values at the top of the script to fit your preferences class ImportScripts::FMGP < ImportScripts::Base - def initialize super # Set this to the base URL for the site; required for importing videos # typically just 'https:' in production - @site_base_url = 'http://localhost:3000' + @site_base_url = "http://localhost:3000" @system_user = Discourse.system_user - SiteSetting.max_image_size_kb = 40960 - SiteSetting.max_attachment_size_kb = 40960 + SiteSetting.max_image_size_kb = 40_960 + SiteSetting.max_attachment_size_kb = 40_960 # handle the same video extension as the rest of Discourse - SiteSetting.authorized_extensions = (SiteSetting.authorized_extensions.split("|") + ['mp4', 'mov', 'webm', 'ogv']).uniq.join("|") + SiteSetting.authorized_extensions = + (SiteSetting.authorized_extensions.split("|") + %w[mp4 mov webm ogv]).uniq.join("|") @invalid_bounce_score = 5.0 @min_title_words = 3 @max_title_words = 14 @@ -76,7 +76,7 @@ class ImportScripts::FMGP < ImportScripts::Base @allowlist = nil # Tags to apply to every topic; empty Array to not have any tags applied everywhere - @globaltags = [ "gplus" ] + @globaltags = ["gplus"] @imagefiles = nil @@ -101,34 +101,30 @@ class ImportScripts::FMGP < ImportScripts::Base @first_date = nil # every argument is a filename, do the right thing based on the file name ARGV.each do |arg| - if arg.end_with?('.csv') + if arg.end_with?(".csv") # CSV files produced by F+MG+E have "URL";"IsDownloaded";"FileName";"FilePath";"FileSize" - CSV.foreach(arg, headers: true, col_sep: ';') do |row| - @images[row[0]] = { - filename: row[2], - filepath: row[3], - filesize: row[4] - } + CSV.foreach(arg, headers: true, col_sep: ";") do |row| + @images[row[0]] = { filename: row[2], filepath: row[3], filesize: row[4] } end elsif arg.end_with?("upload-paths.txt") @imagefiles = File.open(arg, "w") - elsif arg.end_with?('categories.json') + elsif arg.end_with?("categories.json") @categories_filename = arg @categories = load_fmgp_json(arg) elsif arg.end_with?("usermap.json") @usermap = load_fmgp_json(arg) - elsif arg.end_with?('blocklist.json') + elsif arg.end_with?("blocklist.json") @blocklist = load_fmgp_json(arg).map { |i| i.to_s }.to_set - elsif arg.end_with?('allowlist.json') + elsif arg.end_with?("allowlist.json") @allowlist = load_fmgp_json(arg).map { |i| i.to_s }.to_set - elsif arg.end_with?('.json') + elsif arg.end_with?(".json") @feeds << load_fmgp_json(arg) - elsif arg == '--dry-run' + elsif arg == "--dry-run" @dryrun = true elsif arg.start_with?("--last-date=") - @last_date = Time.zone.parse(arg.gsub(/.*=/, '')) + @last_date = Time.zone.parse(arg.gsub(/.*=/, "")) elsif arg.start_with?("--first-date=") - @first_date = Time.zone.parse(arg.gsub(/.*=/, '')) + @first_date = Time.zone.parse(arg.gsub(/.*=/, "")) else raise RuntimeError.new("unknown argument #{arg}") end @@ -153,7 +149,6 @@ class ImportScripts::FMGP < ImportScripts::Base @blocked_posts = 0 # count uploaded file size @totalsize = 0 - end def execute @@ -222,7 +217,9 @@ class ImportScripts::FMGP < ImportScripts::Base categories_new = "#{@categories_filename}.new" File.open(categories_new, "w") do |f| f.write(@categories.to_json) - raise RuntimeError.new("Category file missing categories for #{incomplete_categories}, edit #{categories_new} and rename it to #{@category_filename} before running the same import") + raise RuntimeError.new( + "Category file missing categories for #{incomplete_categories}, edit #{categories_new} and rename it to #{@category_filename} before running the same import", + ) end end end @@ -233,28 +230,32 @@ class ImportScripts::FMGP < ImportScripts::Base @categories.each do |id, cat| if cat["parent"].present? && !cat["parent"].empty? # Two separate sub-categories can have the same name, so need to identify by parent - Category.where(name: cat["category"]).each do |category| - parent = Category.where(id: category.parent_category_id).first - @cats[id] = category if parent.name == cat["parent"] - end + Category + .where(name: cat["category"]) + .each do |category| + parent = Category.where(id: category.parent_category_id).first + @cats[id] = category if parent.name == cat["parent"] + end else if category = Category.where(name: cat["category"]).first @cats[id] = category elsif @create_categories params = {} - params[:name] = cat['category'] + params[:name] = cat["category"] params[:id] = id - puts "Creating #{cat['category']}" + puts "Creating #{cat["category"]}" category = create_category(params, id) @cats[id] = category end end - raise RuntimeError.new("Could not find category #{cat["category"]} for #{cat}") if @cats[id].nil? + if @cats[id].nil? + raise RuntimeError.new("Could not find category #{cat["category"]} for #{cat}") + end end end def import_users - puts '', "Importing Google+ post and comment author users..." + puts "", "Importing Google+ post and comment author users..." # collect authors of both posts and comments @feeds.each do |feed| @@ -263,14 +264,10 @@ class ImportScripts::FMGP < ImportScripts::Base community["categories"].each do |category| category["posts"].each do |post| import_author_user(post["author"]) - if post["message"].present? - import_message_users(post["message"]) - end + import_message_users(post["message"]) if post["message"].present? post["comments"].each do |comment| import_author_user(comment["author"]) - if comment["message"].present? - import_message_users(comment["message"]) - end + import_message_users(comment["message"]) if comment["message"].present? end end end @@ -282,12 +279,7 @@ class ImportScripts::FMGP < ImportScripts::Base # now create them all create_users(@newusers) do |id, u| - { - id: id, - email: u[:email], - name: u[:name], - post_create_action: u[:post_create_action] - } + { id: id, email: u[:email], name: u[:name], post_create_action: u[:post_create_action] } end end @@ -308,7 +300,8 @@ class ImportScripts::FMGP < ImportScripts::Base def import_google_user(id, name) if !@emails[id].present? - google_user_info = UserAssociatedAccount.find_by(provider_name: 'google_oauth2', provider_uid: id.to_i) + google_user_info = + UserAssociatedAccount.find_by(provider_name: "google_oauth2", provider_uid: id.to_i) if google_user_info.nil? # create new google user on system; expect this user to merge # when they later log in with google authentication @@ -320,36 +313,39 @@ class ImportScripts::FMGP < ImportScripts::Base @newusers[id] = { email: email, name: name, - post_create_action: proc do |newuser| - newuser.approved = true - newuser.approved_by_id = @system_user.id - newuser.approved_at = newuser.created_at - if @blocklist.include?(id.to_s) - now = DateTime.now - forever = 1000.years.from_now - # you can suspend as well if you want your blocklist to - # be hard to recover from - #newuser.suspended_at = now - #newuser.suspended_till = forever - newuser.silenced_till = forever - end - newuser.save - @users[id] = newuser - UserAssociatedAccount.create(provider_name: 'google_oauth2', user_id: newuser.id, provider_uid: id) - # Do not send email to the invalid email addresses - # this can be removed after merging with #7162 - s = UserStat.where(user_id: newuser.id).first - s.bounce_score = @invalid_bounce_score - s.reset_bounce_score_after = 1000.years.from_now - s.save - end + post_create_action: + proc do |newuser| + newuser.approved = true + newuser.approved_by_id = @system_user.id + newuser.approved_at = newuser.created_at + if @blocklist.include?(id.to_s) + now = DateTime.now + forever = 1000.years.from_now + # you can suspend as well if you want your blocklist to + # be hard to recover from + #newuser.suspended_at = now + #newuser.suspended_till = forever + newuser.silenced_till = forever + end + newuser.save + @users[id] = newuser + UserAssociatedAccount.create( + provider_name: "google_oauth2", + user_id: newuser.id, + provider_uid: id, + ) + # Do not send email to the invalid email addresses + # this can be removed after merging with #7162 + s = UserStat.where(user_id: newuser.id).first + s.bounce_score = @invalid_bounce_score + s.reset_bounce_score_after = 1000.years.from_now + s.save + end, } else # user already on system u = User.find(google_user_info.user_id) - if u.silenced? || u.suspended? - @blocklist.add(id) - end + @blocklist.add(id) if u.silenced? || u.suspended? @users[id] = u email = u.email end @@ -362,7 +358,7 @@ class ImportScripts::FMGP < ImportScripts::Base # - A google+ post is a discourse topic # - A google+ comment is a discourse post - puts '', "Importing Google+ posts and comments..." + puts "", "Importing Google+ posts and comments..." @feeds.each do |feed| feed["accounts"].each do |account| @@ -371,14 +367,16 @@ class ImportScripts::FMGP < ImportScripts::Base category["posts"].each do |post| # G+ post / Discourse topic import_topic(post, category) - print("\r#{@topics_imported}/#{@posts_imported} topics/posts (skipped: #{@topics_skipped}/#{@posts_skipped} blocklisted: #{@blocked_topics}/#{@blocked_posts}) ") + print( + "\r#{@topics_imported}/#{@posts_imported} topics/posts (skipped: #{@topics_skipped}/#{@posts_skipped} blocklisted: #{@blocked_topics}/#{@blocked_posts}) ", + ) end end end end end - puts '' + puts "" end def import_topic(post, category) @@ -431,9 +429,7 @@ class ImportScripts::FMGP < ImportScripts::Base return nil if !@frst_date.nil? && created_at < @first_date user_id = user_id_from_imported_user_id(post_author_id) - if user_id.nil? - user_id = @users[post["author"]["id"]].id - end + user_id = @users[post["author"]["id"]].id if user_id.nil? mapped = { id: post["id"], @@ -472,7 +468,8 @@ class ImportScripts::FMGP < ImportScripts::Base def title_text(post, created_at) words = message_text(post["message"]) - if words.empty? || words.join("").length < @min_title_characters || words.length < @min_title_words + if words.empty? || words.join("").length < @min_title_characters || + words.length < @min_title_words # database has minimum length # short posts appear not to work well as titles most of the time (in practice) return untitled(post["author"]["name"], created_at) @@ -483,17 +480,13 @@ class ImportScripts::FMGP < ImportScripts::Base (@min_title_words..(words.length - 1)).each do |i| # prefer full stop - if words[i].end_with?(".") - lastword = i - end + lastword = i if words[i].end_with?(".") end if lastword.nil? # fall back on other punctuation (@min_title_words..(words.length - 1)).each do |i| - if words[i].end_with?(',', ';', ':', '?') - lastword = i - end + lastword = i if words[i].end_with?(",", ";", ":", "?") end end @@ -516,9 +509,7 @@ class ImportScripts::FMGP < ImportScripts::Base text_types = [0, 3] message.each do |fragment| if text_types.include?(fragment[0]) - fragment[1].split().each do |word| - words << word - end + fragment[1].split().each { |word| words << word } elsif fragment[0] == 2 # use the display text of a link words << fragment[1] @@ -543,14 +534,10 @@ class ImportScripts::FMGP < ImportScripts::Base lines << "\n#{formatted_link(post["image"]["proxy"])}\n" end if post["images"].present? - post["images"].each do |image| - lines << "\n#{formatted_link(image["proxy"])}\n" - end + post["images"].each { |image| lines << "\n#{formatted_link(image["proxy"])}\n" } end if post["videos"].present? - post["videos"].each do |video| - lines << "\n#{formatted_link(video["proxy"])}\n" - end + post["videos"].each { |video| lines << "\n#{formatted_link(video["proxy"])}\n" } end if post["link"].present? && post["link"]["url"].present? url = post["link"]["url"] @@ -575,12 +562,8 @@ class ImportScripts::FMGP < ImportScripts::Base if fragment[2].nil? text else - if fragment[2]["italic"].present? - text = "#{text}" - end - if fragment[2]["bold"].present? - text = "#{text}" - end + text = "#{text}" if fragment[2]["italic"].present? + text = "#{text}" if fragment[2]["bold"].present? if fragment[2]["strikethrough"].present? # s more likely than del to represent user intent? text = "#{text}" @@ -594,9 +577,7 @@ class ImportScripts::FMGP < ImportScripts::Base formatted_link_text(fragment[2], fragment[1]) elsif fragment[0] == 3 # reference to a user - if @usermap.include?(fragment[2].to_s) - return "@#{@usermap[fragment[2].to_s]}" - end + return "@#{@usermap[fragment[2].to_s]}" if @usermap.include?(fragment[2].to_s) if fragment[2].nil? # deleted G+ users show up with a null ID return "+#{fragment[1]}" @@ -606,12 +587,18 @@ class ImportScripts::FMGP < ImportScripts::Base # user was in this import's authors "@#{user.username} " else - if google_user_info = UserAssociatedAccount.find_by(provider_name: 'google_oauth2', provider_uid: fragment[2]) + if google_user_info = + UserAssociatedAccount.find_by( + provider_name: "google_oauth2", + provider_uid: fragment[2], + ) # user was not in this import, but has logged in or been imported otherwise user = User.find(google_user_info.user_id) "@#{user.username} " else - raise RuntimeError.new("Google user #{fragment[1]} (id #{fragment[2]}) not imported") if !@dryrun + if !@dryrun + raise RuntimeError.new("Google user #{fragment[1]} (id #{fragment[2]}) not imported") + end # if you want to fall back to their G+ name, just erase the raise above, # but this should not happen "+#{fragment[1]}" @@ -681,6 +668,4 @@ class ImportScripts::FMGP < ImportScripts::Base end end -if __FILE__ == $0 - ImportScripts::FMGP.new.perform -end +ImportScripts::FMGP.new.perform if __FILE__ == $0 diff --git a/script/import_scripts/getsatisfaction.rb b/script/import_scripts/getsatisfaction.rb index 50f8613e68..0458c84f94 100644 --- a/script/import_scripts/getsatisfaction.rb +++ b/script/import_scripts/getsatisfaction.rb @@ -22,15 +22,14 @@ # that correctly and will import the replies in the wrong order. # You should run `rake posts:reorder_posts` after the import. -require 'csv' -require 'set' +require "csv" +require "set" require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require 'reverse_markdown' # gem 'reverse_markdown' +require "reverse_markdown" # gem 'reverse_markdown' # Call it like this: # RAILS_ENV=production bundle exec ruby script/import_scripts/getsatisfaction.rb DIRNAME class ImportScripts::GetSatisfaction < ImportScripts::Base - IMPORT_ARCHIVED_TOPICS = false # The script classifies each topic as private when at least one associated category @@ -85,22 +84,24 @@ class ImportScripts::GetSatisfaction < ImportScripts::Base previous_line = nil File.open(target_filename, "w") do |file| - File.open(source_filename).each_line do |line| - line.gsub!(/(?\s*)?(.*?)<\/code>(\s*<\/pre>)?/mi) do + raw.gsub!(%r{(
\s*)?(.*?)(\s*
)?}mi) do code = $2 hoist = SecureRandom.hex # tidy code, wow, this is impressively crazy @@ -350,9 +347,7 @@ class ImportScripts::GetSatisfaction < ImportScripts::Base # in this case double space works best ... so odd raw.gsub!(" ", "\n\n") - hoisted.each do |hoist, code| - raw.gsub!(hoist, "\n```\n#{code}\n```\n") - end + hoisted.each { |hoist, code| raw.gsub!(hoist, "\n```\n#{code}\n```\n") } raw = CGI.unescapeHTML(raw) raw = ReverseMarkdown.convert(raw) @@ -360,7 +355,7 @@ class ImportScripts::GetSatisfaction < ImportScripts::Base end def create_permalinks - puts '', 'Creating Permalinks...', '' + puts "", "Creating Permalinks...", "" Topic.listable_topics.find_each do |topic| tcf = topic.first_post.custom_fields @@ -372,7 +367,6 @@ class ImportScripts::GetSatisfaction < ImportScripts::Base end end end - end unless ARGV[0] && Dir.exist?(ARGV[0]) diff --git a/script/import_scripts/google_groups.rb b/script/import_scripts/google_groups.rb index 494346292c..1b6fa4b420 100755 --- a/script/import_scripts/google_groups.rb +++ b/script/import_scripts/google_groups.rb @@ -20,19 +20,18 @@ DEFAULT_COOKIES_TXT = "/shared/import/cookies.txt" ABORT_AFTER_SKIPPED_TOPIC_COUNT = 10 def driver - @driver ||= begin - chrome_args = ["disable-gpu"] - chrome_args << "headless" unless ENV["NOT_HEADLESS"] == '1' - chrome_args << "no-sandbox" if inside_container? - options = Selenium::WebDriver::Chrome::Options.new(args: chrome_args) - Selenium::WebDriver.for(:chrome, options: options) - end + @driver ||= + begin + chrome_args = ["disable-gpu"] + chrome_args << "headless" unless ENV["NOT_HEADLESS"] == "1" + chrome_args << "no-sandbox" if inside_container? + options = Selenium::WebDriver::Chrome::Options.new(args: chrome_args) + Selenium::WebDriver.for(:chrome, options: options) + end end def inside_container? - File.foreach("/proc/1/cgroup") do |line| - return true if line.include?("docker") - end + File.foreach("/proc/1/cgroup") { |line| return true if line.include?("docker") } false end @@ -79,35 +78,38 @@ def base_url end def crawl_topics - 1.step(nil, 100).each do |start| - url = "#{base_url}/#{@groupname}[#{start}-#{start + 99}]" - get(url) + 1 + .step(nil, 100) + .each do |start| + url = "#{base_url}/#{@groupname}[#{start}-#{start + 99}]" + get(url) - begin - if start == 1 && find("h2").text == "Error 403" - exit_with_error(<<~TEXT.red.bold) + begin + exit_with_error(<<~TEXT.red.bold) if start == 1 && find("h2").text == "Error 403" Unable to find topics. Try running the script with the "--domain example.com" option if you are a G Suite user and your group's URL contains a path with your domain that looks like "/a/example.com". TEXT + rescue Selenium::WebDriver::Error::NoSuchElementError + # Ignore this error. It simply means there wasn't an error. end - rescue Selenium::WebDriver::Error::NoSuchElementError - # Ignore this error. It simply means there wasn't an error. - end - topic_urls = extract(".subject a[href*='#{@groupname}']") { |a| a["href"].sub("/d/topic/", "/forum/?_escaped_fragment_=topic/") } - break if topic_urls.size == 0 + topic_urls = + extract(".subject a[href*='#{@groupname}']") do |a| + a["href"].sub("/d/topic/", "/forum/?_escaped_fragment_=topic/") + end + break if topic_urls.size == 0 - topic_urls.each do |topic_url| - crawl_topic(topic_url) + topic_urls.each do |topic_url| + crawl_topic(topic_url) - # abort if this in an incremental crawl and there were too many consecutive, skipped topics - if @finished && @skipped_topic_count > ABORT_AFTER_SKIPPED_TOPIC_COUNT - puts "Skipping all other topics, because this is an incremental crawl.".green - return + # abort if this in an incremental crawl and there were too many consecutive, skipped topics + if @finished && @skipped_topic_count > ABORT_AFTER_SKIPPED_TOPIC_COUNT + puts "Skipping all other topics, because this is an incremental crawl.".green + return + end end end - end end def crawl_topic(url) @@ -126,17 +128,14 @@ def crawl_topic(url) messages_crawled = false extract(".subject a[href*='#{@groupname}']") do |a| - [ - a["href"].sub("/d/msg/", "/forum/message/raw?msg="), - a["title"].empty? - ] + [a["href"].sub("/d/msg/", "/forum/message/raw?msg="), a["title"].empty?] end.each do |msg_url, might_be_deleted| messages_crawled |= crawl_message(msg_url, might_be_deleted) end @skipped_topic_count = skippable && messages_crawled ? 0 : @skipped_topic_count + 1 @scraped_topic_urls << url -rescue +rescue StandardError puts "Failed to scrape topic at #{url}".red raise if @abort_on_error end @@ -144,18 +143,16 @@ end def crawl_message(url, might_be_deleted) get(url) - filename = File.join(@path, "#{url[/#{@groupname}\/(.+)/, 1].sub("/", "-")}.eml") + filename = File.join(@path, "#{url[%r{#{@groupname}/(.+)}, 1].sub("/", "-")}.eml") content = find("pre")["innerText"] if !@first_message_checked @first_message_checked = true - if content.match?(/From:.*\.\.\.@.*/i) && !@force_import - exit_with_error(<<~TEXT.red.bold) + exit_with_error(<<~TEXT.red.bold) if content.match?(/From:.*\.\.\.@.*/i) && !@force_import It looks like you do not have permissions to see email addresses. Aborting. Use the --force option to import anyway. TEXT - end end old_md5 = Digest::MD5.file(filename) if File.exist?(filename) @@ -169,7 +166,7 @@ rescue Selenium::WebDriver::Error::NoSuchElementError puts "Failed to scrape message at #{url}".red raise if @abort_on_error end -rescue +rescue StandardError puts "Failed to scrape message at #{url}".red raise if @abort_on_error end @@ -178,10 +175,7 @@ def login puts "Logging in..." get("https://google.com/404") - add_cookies( - "myaccount.google.com", - "google.com" - ) + add_cookies("myaccount.google.com", "google.com") get("https://myaccount.google.com/?utm_source=sign_in_no_continue") @@ -193,20 +187,24 @@ def login end def add_cookies(*domains) - File.readlines(@cookies).each do |line| - parts = line.chomp.split("\t") - next if parts.size != 7 || !domains.any? { |domain| parts[0] =~ /^\.?#{Regexp.escape(domain)}$/ } + File + .readlines(@cookies) + .each do |line| + parts = line.chomp.split("\t") + if parts.size != 7 || !domains.any? { |domain| parts[0] =~ /^\.?#{Regexp.escape(domain)}$/ } + next + end - driver.manage.add_cookie( - domain: parts[0], - httpOnly: "true".casecmp?(parts[1]), - path: parts[2], - secure: "true".casecmp?(parts[3]), - expires: parts[4] == "0" ? nil : DateTime.strptime(parts[4], "%s"), - name: parts[5], - value: parts[6] - ) - end + driver.manage.add_cookie( + domain: parts[0], + httpOnly: "true".casecmp?(parts[1]), + path: parts[2], + secure: "true".casecmp?(parts[3]), + expires: parts[4] == "0" ? nil : DateTime.strptime(parts[4], "%s"), + name: parts[5], + value: parts[6], + ) + end end def wait_for_url @@ -240,10 +238,7 @@ def crawl crawl_topics @finished = true ensure - File.write(status_filename, { - finished: @finished, - urls: @scraped_topic_urls - }.to_yaml) + File.write(status_filename, { finished: @finished, urls: @scraped_topic_urls }.to_yaml) end elapsed = Time.now - start_time @@ -258,20 +253,25 @@ def parse_arguments @abort_on_error = false @cookies = DEFAULT_COOKIES_TXT if File.exist?(DEFAULT_COOKIES_TXT) - parser = OptionParser.new do |opts| - opts.banner = "Usage: google_groups.rb [options]" + parser = + OptionParser.new do |opts| + opts.banner = "Usage: google_groups.rb [options]" - opts.on("-g", "--groupname GROUPNAME") { |v| @groupname = v } - opts.on("-d", "--domain DOMAIN") { |v| @domain = v } - opts.on("-c", "--cookies PATH", "path to cookies.txt") { |v| @cookies = v } - opts.on("--path PATH", "output path for emails") { |v| @path = v } - opts.on("-f", "--force", "force import when user isn't allowed to see email addresses") { @force_import = true } - opts.on("-a", "--abort-on-error", "abort crawl on error instead of skipping message") { @abort_on_error = true } - opts.on("-h", "--help") do - puts opts - exit + opts.on("-g", "--groupname GROUPNAME") { |v| @groupname = v } + opts.on("-d", "--domain DOMAIN") { |v| @domain = v } + opts.on("-c", "--cookies PATH", "path to cookies.txt") { |v| @cookies = v } + opts.on("--path PATH", "output path for emails") { |v| @path = v } + opts.on("-f", "--force", "force import when user isn't allowed to see email addresses") do + @force_import = true + end + opts.on("-a", "--abort-on-error", "abort crawl on error instead of skipping message") do + @abort_on_error = true + end + opts.on("-h", "--help") do + puts opts + exit + end end - end begin parser.parse! @@ -279,10 +279,12 @@ def parse_arguments exit_with_error(e.message, "", parser) end - mandatory = [:groupname, :cookies] + mandatory = %i[groupname cookies] missing = mandatory.select { |name| instance_variable_get("@#{name}").nil? } - exit_with_error("Missing arguments: #{missing.join(', ')}".red.bold, "", parser, "") if missing.any? + if missing.any? + exit_with_error("Missing arguments: #{missing.join(", ")}".red.bold, "", parser, "") + end exit_with_error("cookies.txt not found at #{@cookies}".red.bold, "") if !File.exist?(@cookies) @path = File.join(DEFAULT_OUTPUT_PATH, @groupname) if @path.nil? diff --git a/script/import_scripts/higher_logic.rb b/script/import_scripts/higher_logic.rb index fe6e19641d..0682031126 100644 --- a/script/import_scripts/higher_logic.rb +++ b/script/import_scripts/higher_logic.rb @@ -4,7 +4,6 @@ require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::HigherLogic < ImportScripts::Base - HIGHERLOGIC_DB = "higherlogic" BATCH_SIZE = 1000 ATTACHMENT_DIR = "/shared/import/data/attachments" @@ -12,11 +11,7 @@ class ImportScripts::HigherLogic < ImportScripts::Base def initialize super - @client = Mysql2::Client.new( - host: "localhost", - username: "root", - database: HIGHERLOGIC_DB - ) + @client = Mysql2::Client.new(host: "localhost", username: "root", database: HIGHERLOGIC_DB) end def execute @@ -29,7 +24,7 @@ class ImportScripts::HigherLogic < ImportScripts::Base end def import_groups - puts '', 'importing groups' + puts "", "importing groups" groups = mysql_query <<-SQL SELECT CommunityKey, CommunityName @@ -37,16 +32,11 @@ class ImportScripts::HigherLogic < ImportScripts::Base ORDER BY CommunityName SQL - create_groups(groups) do |group| - { - id: group['CommunityKey'], - name: group['CommunityName'] - } - end + create_groups(groups) { |group| { id: group["CommunityKey"], name: group["CommunityName"] } } end def import_users - puts '', 'importing users' + puts "", "importing users" total_count = mysql_query("SELECT count(*) FROM Contact").first["count"] batches(BATCH_SIZE) do |offset| @@ -59,43 +49,42 @@ class ImportScripts::HigherLogic < ImportScripts::Base break if results.size < 1 - next if all_records_exist? :users, results.map { |u| u['ContactKey'] } + next if all_records_exist? :users, results.map { |u| u["ContactKey"] } create_users(results, total: total_count, offset: offset) do |user| - next if user['EmailAddress'].blank? + next if user["EmailAddress"].blank? { - id: user['ContactKey'], - email: user['EmailAddress'], - name: "#{user['FirstName']} #{user['LastName']}", - created_at: user['CreatedOn'] == nil ? 0 : Time.zone.at(user['CreatedOn']), - bio_raw: user['Bio'], - active: user['UserStatus'] == "Active", - admin: user['HLAdminFlag'] == 1 + id: user["ContactKey"], + email: user["EmailAddress"], + name: "#{user["FirstName"]} #{user["LastName"]}", + created_at: user["CreatedOn"] == nil ? 0 : Time.zone.at(user["CreatedOn"]), + bio_raw: user["Bio"], + active: user["UserStatus"] == "Active", + admin: user["HLAdminFlag"] == 1, } end end end def import_group_users - puts '', 'importing group users' + puts "", "importing group users" - group_users = mysql_query(<<-SQL + group_users = mysql_query(<<-SQL).to_a SELECT CommunityKey, ContactKey FROM CommunityMember SQL - ).to_a group_users.each do |row| - next unless user_id = user_id_from_imported_user_id(row['ContactKey']) - next unless group_id = group_id_from_imported_group_id(row['CommunityKey']) - puts '', '.' + next unless user_id = user_id_from_imported_user_id(row["ContactKey"]) + next unless group_id = group_id_from_imported_group_id(row["CommunityKey"]) + puts "", "." GroupUser.find_or_create_by(user_id: user_id, group_id: group_id) end end def import_categories - puts '', 'importing categories' + puts "", "importing categories" categories = mysql_query <<-SQL SELECT DiscussionKey, DiscussionName @@ -103,15 +92,12 @@ class ImportScripts::HigherLogic < ImportScripts::Base SQL create_categories(categories) do |category| - { - id: category['DiscussionKey'], - name: category['DiscussionName'] - } + { id: category["DiscussionKey"], name: category["DiscussionName"] } end end def import_posts - puts '', 'importing topics and posts' + puts "", "importing topics and posts" total_count = mysql_query("SELECT count(*) FROM DiscussionPost").first["count"] batches(BATCH_SIZE) do |offset| @@ -131,28 +117,28 @@ class ImportScripts::HigherLogic < ImportScripts::Base SQL break if results.size < 1 - next if all_records_exist? :posts, results.map { |p| p['MessageKey'] } + next if all_records_exist? :posts, results.map { |p| p["MessageKey"] } create_posts(results, total: total_count, offset: offset) do |post| - raw = preprocess_raw(post['Body']) + raw = preprocess_raw(post["Body"]) mapped = { - id: post['MessageKey'], - user_id: user_id_from_imported_user_id(post['ContactKey']), + id: post["MessageKey"], + user_id: user_id_from_imported_user_id(post["ContactKey"]), raw: raw, - created_at: Time.zone.at(post['CreatedOn']), + created_at: Time.zone.at(post["CreatedOn"]), } - if post['ParentMessageKey'].nil? - mapped[:category] = category_id_from_imported_category_id(post['DiscussionKey']).to_i - mapped[:title] = CGI.unescapeHTML(post['Subject']) - mapped[:pinned] = post['PinnedFlag'] == 1 + if post["ParentMessageKey"].nil? + mapped[:category] = category_id_from_imported_category_id(post["DiscussionKey"]).to_i + mapped[:title] = CGI.unescapeHTML(post["Subject"]) + mapped[:pinned] = post["PinnedFlag"] == 1 else - topic = topic_lookup_from_imported_post_id(post['ParentMessageKey']) + topic = topic_lookup_from_imported_post_id(post["ParentMessageKey"]) if topic.present? mapped[:topic_id] = topic[:topic_id] else - puts "Parent post #{post['ParentMessageKey']} doesn't exist. Skipping." + puts "Parent post #{post["ParentMessageKey"]} doesn't exist. Skipping." next end end @@ -163,20 +149,19 @@ class ImportScripts::HigherLogic < ImportScripts::Base end def import_attachments - puts '', 'importing attachments' + puts "", "importing attachments" count = 0 - total_attachments = mysql_query(<<-SQL + total_attachments = mysql_query(<<-SQL).first["count"] SELECT COUNT(*) count FROM LibraryEntryFile l JOIN DiscussionPost p ON p.AttachmentDocumentKey = l.DocumentKey WHERE p.CreatedOn > '2020-01-01 00:00:00' SQL - ).first['count'] batches(BATCH_SIZE) do |offset| - attachments = mysql_query(<<-SQL + attachments = mysql_query(<<-SQL).to_a SELECT l.VersionName, l.FileExtension, p.MessageKey @@ -186,17 +171,16 @@ class ImportScripts::HigherLogic < ImportScripts::Base LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ).to_a break if attachments.empty? attachments.each do |a| print_status(count += 1, total_attachments, get_start_time("attachments")) - original_filename = "#{a['VersionName']}.#{a['FileExtension']}" + original_filename = "#{a["VersionName"]}.#{a["FileExtension"]}" path = File.join(ATTACHMENT_DIR, original_filename) if File.exist?(path) - if post = Post.find(post_id_from_imported_post_id(a['MessageKey'])) + if post = Post.find(post_id_from_imported_post_id(a["MessageKey"])) filename = File.basename(original_filename) upload = create_upload(post.user.id, path, filename) @@ -205,7 +189,9 @@ class ImportScripts::HigherLogic < ImportScripts::Base post.raw << "\n\n" << html post.save! - PostUpload.create!(post: post, upload: upload) unless PostUpload.where(post: post, upload: upload).exists? + unless PostUpload.where(post: post, upload: upload).exists? + PostUpload.create!(post: post, upload: upload) + end end end end @@ -217,7 +203,7 @@ class ImportScripts::HigherLogic < ImportScripts::Base raw = body.dup # trim off any post text beyond ---- to remove email threading - raw = raw.slice(0..(raw.index('------'))) || raw + raw = raw.slice(0..(raw.index("------"))) || raw raw = HtmlToMarkdown.new(raw).to_markdown raw diff --git a/script/import_scripts/ipboard.rb b/script/import_scripts/ipboard.rb index dcd8cb03f5..4f5c5ed0bd 100644 --- a/script/import_scripts/ipboard.rb +++ b/script/import_scripts/ipboard.rb @@ -3,13 +3,13 @@ require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require 'htmlentities' +require "htmlentities" begin - require 'reverse_markdown' # https://github.com/jqr/php-serialize + require "reverse_markdown" # https://github.com/jqr/php-serialize rescue LoadError puts - puts 'reverse_markdown not found.' - puts 'Add to Gemfile, like this: ' + puts "reverse_markdown not found." + puts "Add to Gemfile, like this: " puts puts "echo gem \\'reverse_markdown\\' >> Gemfile" puts "bundle install" @@ -32,28 +32,27 @@ export USERDIR="user" =end class ImportScripts::IpboardSQL < ImportScripts::Base - - DB_HOST ||= ENV['DB_HOST'] || "localhost" - DB_NAME ||= ENV['DB_NAME'] || "ipboard" - DB_PW ||= ENV['DB_PW'] || "ipboard" - DB_USER ||= ENV['DB_USER'] || "ipboard" - TABLE_PREFIX ||= ENV['TABLE_PREFIX'] || "ipb_" - IMPORT_AFTER ||= ENV['IMPORT_AFTER'] || "1970-01-01" - UPLOADS ||= ENV['UPLOADS'] || "http://UPLOADS+LOCATION+IS+NOT+SET/uploads" - USERDIR ||= ENV['USERDIR'] || "user" - URL ||= ENV['URL'] || "https://forum.example.com" - AVATARS_DIR ||= ENV['AVATARS_DIR'] || '/home/pfaffman/data/example.com/avatars/' + DB_HOST ||= ENV["DB_HOST"] || "localhost" + DB_NAME ||= ENV["DB_NAME"] || "ipboard" + DB_PW ||= ENV["DB_PW"] || "ipboard" + DB_USER ||= ENV["DB_USER"] || "ipboard" + TABLE_PREFIX ||= ENV["TABLE_PREFIX"] || "ipb_" + IMPORT_AFTER ||= ENV["IMPORT_AFTER"] || "1970-01-01" + UPLOADS ||= ENV["UPLOADS"] || "http://UPLOADS+LOCATION+IS+NOT+SET/uploads" + USERDIR ||= ENV["USERDIR"] || "user" + URL ||= ENV["URL"] || "https://forum.example.com" + AVATARS_DIR ||= ENV["AVATARS_DIR"] || "/home/pfaffman/data/example.com/avatars/" BATCH_SIZE = 1000 ID_FIRST = true QUIET = true DEBUG = false - GALLERY_CAT_ID = 1234567 - GALLERY_CAT_NAME = 'galeria' - EMO_DIR ||= ENV['EMO_DIR'] || "default" + GALLERY_CAT_ID = 1_234_567 + GALLERY_CAT_NAME = "galeria" + EMO_DIR ||= ENV["EMO_DIR"] || "default" OLD_FORMAT = false if OLD_FORMAT MEMBERS_TABLE = "#{TABLE_PREFIX}core_members" - FORUMS_TABLE = "#{TABLE_PREFIX}forums_forums" + FORUMS_TABLE = "#{TABLE_PREFIX}forums_forums" POSTS_TABLE = "#{TABLE_PREFIX}forums_posts" TOPICS_TABLE = "#{TABLE_PREFIX}forums_topics" else @@ -89,8 +88,8 @@ class ImportScripts::IpboardSQL < ImportScripts::Base # TODO figure this out puts "WARNING: permalink_normalizations not set!!!" sleep 1 - #raw = "[ORIGINAL POST](#{URL}/topic/#{id}-#{slug})\n\n" + raw - #SiteSetting.permalink_normalizations='/topic/(.*t)\?.*/\1' + #raw = "[ORIGINAL POST](#{URL}/topic/#{id}-#{slug})\n\n" + raw + #SiteSetting.permalink_normalizations='/topic/(.*t)\?.*/\1' else # remove stuff after a "?" and work for urls that end in .html SiteSetting.permalink_normalizations = '/(.*t)[?.].*/\1' @@ -98,21 +97,15 @@ class ImportScripts::IpboardSQL < ImportScripts::Base end def initialize - if IMPORT_AFTER > "1970-01-01" - print_warning("Importing data after #{IMPORT_AFTER}") - end + print_warning("Importing data after #{IMPORT_AFTER}") if IMPORT_AFTER > "1970-01-01" super @htmlentities = HTMLEntities.new begin - @client = Mysql2::Client.new( - host: DB_HOST, - username: DB_USER, - password: DB_PW, - database: DB_NAME - ) + @client = + Mysql2::Client.new(host: DB_HOST, username: DB_USER, password: DB_PW, database: DB_NAME) rescue Exception => e - puts '=' * 50 + puts "=" * 50 puts e.message puts <<~TEXT Cannot log in to database. @@ -151,18 +144,24 @@ class ImportScripts::IpboardSQL < ImportScripts::Base # NOT SUPPORTED import_gallery_topics update_tl0 create_permalinks - end def import_users - puts '', "creating users" + puts "", "creating users" - total_count = mysql_query("SELECT count(*) count FROM #{MEMBERS_TABLE} - WHERE last_activity > UNIX_TIMESTAMP(STR_TO_DATE('#{IMPORT_AFTER}', '%Y-%m-%d'));").first['count'] + total_count = + mysql_query( + "SELECT count(*) count FROM #{MEMBERS_TABLE} + WHERE last_activity > UNIX_TIMESTAMP(STR_TO_DATE('#{IMPORT_AFTER}', '%Y-%m-%d'));", + ).first[ + "count" + ] batches(BATCH_SIZE) do |offset| #notes: no location, url, - results = mysql_query(" + results = + mysql_query( + " SELECT member_id id, name username, member_group_id usergroup, @@ -184,58 +183,64 @@ class ImportScripts::IpboardSQL < ImportScripts::Base AND member_group_id = g_id order by member_id ASC LIMIT #{BATCH_SIZE} - OFFSET #{offset};") + OFFSET #{offset};", + ) break if results.size < 1 - next if all_records_exist? :users, results.map { |u| u['id'].to_i } + next if all_records_exist? :users, results.map { |u| u["id"].to_i } create_users(results, total: total_count, offset: offset) do |user| - next if user['email'].blank? - next if user['username'].blank? - next if @lookup.user_id_from_imported_user_id(user['id']) + next if user["email"].blank? + next if user["username"].blank? + next if @lookup.user_id_from_imported_user_id(user["id"]) - birthday = Date.parse("#{user['bday_year']}-#{user['bday_month']}-#{user['bday_day']}") rescue nil - # TODO: what about timezones? - next if user['id'] == 0 - { id: user['id'], - email: user['email'], - username: user['username'], - avatar_url: user['avatar_url'], - title: user['member_type'], - created_at: user['created_at'] == nil ? 0 : Time.zone.at(user['created_at']), - # bio_raw: user['bio_raw'], - registration_ip_address: user['registration_ip_address'], - # birthday: birthday, - last_seen_at: user['last_seen_at'] == nil ? 0 : Time.zone.at(user['last_seen_at']), - admin: /^Admin/.match(user['member_type']) ? true : false, - moderator: /^MOD/.match(user['member_type']) ? true : false, - post_create_action: proc do |newuser| - if user['avatar_url'] && user['avatar_url'].length > 0 - photo_path = AVATARS_DIR + user['avatar_url'] - if File.exist?(photo_path) - begin - upload = create_upload(newuser.id, photo_path, File.basename(photo_path)) - if upload && upload.persisted? - newuser.import_mode = false - newuser.create_user_avatar - newuser.import_mode = true - newuser.user_avatar.update(custom_upload_id: upload.id) - newuser.update(uploaded_avatar_id: upload.id) - else - puts "Error: Upload did not persist for #{photo_path}!" - end - rescue SystemCallError => err - puts "Could not import avatar #{photo_path}: #{err.message}" - end - else - puts "avatar file not found at #{photo_path}" - end - end - if user['banned'] != 0 - suspend_user(newuser) - end + birthday = + begin + Date.parse("#{user["bday_year"]}-#{user["bday_month"]}-#{user["bday_day"]}") + rescue StandardError + nil end + # TODO: what about timezones? + next if user["id"] == 0 + { + id: user["id"], + email: user["email"], + username: user["username"], + avatar_url: user["avatar_url"], + title: user["member_type"], + created_at: user["created_at"] == nil ? 0 : Time.zone.at(user["created_at"]), + # bio_raw: user['bio_raw'], + registration_ip_address: user["registration_ip_address"], + # birthday: birthday, + last_seen_at: user["last_seen_at"] == nil ? 0 : Time.zone.at(user["last_seen_at"]), + admin: /^Admin/.match(user["member_type"]) ? true : false, + moderator: /^MOD/.match(user["member_type"]) ? true : false, + post_create_action: + proc do |newuser| + if user["avatar_url"] && user["avatar_url"].length > 0 + photo_path = AVATARS_DIR + user["avatar_url"] + if File.exist?(photo_path) + begin + upload = create_upload(newuser.id, photo_path, File.basename(photo_path)) + if upload && upload.persisted? + newuser.import_mode = false + newuser.create_user_avatar + newuser.import_mode = true + newuser.user_avatar.update(custom_upload_id: upload.id) + newuser.update(uploaded_avatar_id: upload.id) + else + puts "Error: Upload did not persist for #{photo_path}!" + end + rescue SystemCallError => err + puts "Could not import avatar #{photo_path}: #{err.message}" + end + else + puts "avatar file not found at #{photo_path}" + end + end + suspend_user(newuser) if user["banned"] != 0 + end, } end end @@ -244,7 +249,7 @@ class ImportScripts::IpboardSQL < ImportScripts::Base def suspend_user(user) user.suspended_at = Time.now user.suspended_till = 200.years.from_now - ban_reason = 'Account deactivated by administrator' + ban_reason = "Account deactivated by administrator" user_option = user.user_option user_option.email_digests = false @@ -266,45 +271,50 @@ class ImportScripts::IpboardSQL < ImportScripts::Base def import_image_categories puts "", "importing image categories..." - categories = mysql_query(" + categories = + mysql_query( + " SELECT category_id id, category_name_seo name, category_parent_id as parent_id FROM #{TABLE_PREFIX}gallery_categories ORDER BY id ASC - ").to_a + ", + ).to_a - category_names = mysql_query(" + category_names = + mysql_query( + " SELECT DISTINCT word_key, word_default title FROM #{TABLE_PREFIX}core_sys_lang_words where word_app='gallery' AND word_key REGEXP 'gallery_category_[0-9]+$' ORDER BY word_key ASC - ").to_a + ", + ).to_a cat_map = {} puts "Creating gallery_cat_map" category_names.each do |name| - title = name['title'] - word_key = name['word_key'] + title = name["title"] + word_key = name["word_key"] puts "Processing #{word_key}: #{title}" - id = word_key.gsub('gallery_category_', '') + id = word_key.gsub("gallery_category_", "") next if cat_map[id] cat_map[id] = cat_map.has_value?(title) ? title + " " + id : title puts "#{id} => #{cat_map[id]}" end - params = { id: GALLERY_CAT_ID, - name: GALLERY_CAT_NAME } + params = { id: GALLERY_CAT_ID, name: GALLERY_CAT_NAME } create_category(params, params[:id]) create_categories(categories) do |category| - id = (category['id']).to_s + id = (category["id"]).to_s name = CGI.unescapeHTML(cat_map[id]) { - id: id + 'gal', + id: id + "gal", name: name, parent_category_id: @lookup.category_id_from_imported_category_id(GALLERY_CAT_ID), - color: random_category_color + color: random_category_color, } end end @@ -312,34 +322,34 @@ class ImportScripts::IpboardSQL < ImportScripts::Base def import_categories puts "", "importing categories..." - categories = mysql_query(" + categories = + mysql_query( + " SELECT id, name name, parent_id as parent_id FROM #{FORUMS_TABLE} ORDER BY parent_id ASC - ").to_a + ", + ).to_a top_level_categories = categories.select { |c| c["parent.id"] == -1 } create_categories(top_level_categories) do |category| - id = category['id'].to_s - name = category['name'] - { - id: id, - name: name, - } + id = category["id"].to_s + name = category["name"] + { id: id, name: name } end children_categories = categories.select { |c| c["parent.id"] != -1 } create_categories(children_categories) do |category| - id = category['id'].to_s - name = category['name'] + id = category["id"].to_s + name = category["name"] { id: id, name: name, - parent_category_id: @lookup.category_id_from_imported_category_id(category['parent_id']), - color: random_category_color + parent_category_id: @lookup.category_id_from_imported_category_id(category["parent_id"]), + color: random_category_color, } end end @@ -347,13 +357,17 @@ class ImportScripts::IpboardSQL < ImportScripts::Base def import_topics puts "", "importing topics..." - total_count = mysql_query("SELECT count(*) count FROM #{POSTS_TABLE} + total_count = + mysql_query( + "SELECT count(*) count FROM #{POSTS_TABLE} WHERE post_date > UNIX_TIMESTAMP(STR_TO_DATE('#{IMPORT_AFTER}', '%Y-%m-%d')) - AND new_topic=1;") - .first['count'] + AND new_topic=1;", + ).first[ + "count" + ] batches(BATCH_SIZE) do |offset| - discussions = mysql_query(<<-SQL + discussions = mysql_query(<<-SQL) SELECT #{TOPICS_TABLE}.tid tid, #{TOPICS_TABLE}.forum_id category, #{POSTS_TABLE}.pid pid, @@ -371,29 +385,29 @@ class ImportScripts::IpboardSQL < ImportScripts::Base LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ) break if discussions.size < 1 - next if all_records_exist? :posts, discussions.map { |t| "discussion#" + t['tid'].to_s } + next if all_records_exist? :posts, discussions.map { |t| "discussion#" + t["tid"].to_s } create_posts(discussions, total: total_count, offset: offset) do |discussion| - slug = discussion['slug'] - id = discussion['tid'] - raw = clean_up(discussion['raw']) + slug = discussion["slug"] + id = discussion["tid"] + raw = clean_up(discussion["raw"]) { - id: "discussion#" + discussion['tid'].to_s, - user_id: user_id_from_imported_user_id(discussion['user_id']) || Discourse::SYSTEM_USER_ID, - title: CGI.unescapeHTML(discussion['title']), - category: category_id_from_imported_category_id(discussion['category'].to_s), + id: "discussion#" + discussion["tid"].to_s, + user_id: + user_id_from_imported_user_id(discussion["user_id"]) || Discourse::SYSTEM_USER_ID, + title: CGI.unescapeHTML(discussion["title"]), + category: category_id_from_imported_category_id(discussion["category"].to_s), raw: raw, - pinned_at: discussion['pinned'].to_i == 1 ? Time.zone.at(discussion['created_at']) : nil, - created_at: Time.zone.at(discussion['created_at']), + pinned_at: discussion["pinned"].to_i == 1 ? Time.zone.at(discussion["created_at"]) : nil, + created_at: Time.zone.at(discussion["created_at"]), } end end end - def array_from_members_string(invited_members = 'a:3:{i:0;i:22629;i:1;i:21837;i:2;i:22234;}') + def array_from_members_string(invited_members = "a:3:{i:0;i:22629;i:1;i:21837;i:2;i:22234;}") out = [] count_regex = /a:(\d)+:/ count = count_regex.match(invited_members)[1] @@ -403,7 +417,7 @@ class ImportScripts::IpboardSQL < ImportScripts::Base i = m[1] rest.sub!(i_regex, "") puts "i: #{i}, #{rest}" - out += [ i.to_i ] + out += [i.to_i] end out end @@ -411,12 +425,13 @@ class ImportScripts::IpboardSQL < ImportScripts::Base def import_private_messages puts "", "importing private messages..." - topic_count = mysql_query("SELECT COUNT(msg_id) count FROM #{TABLE_PREFIX}message_posts").first["count"] + topic_count = + mysql_query("SELECT COUNT(msg_id) count FROM #{TABLE_PREFIX}message_posts").first["count"] last_private_message_topic_id = -1 batches(BATCH_SIZE) do |offset| - private_messages = mysql_query(<<-SQL + private_messages = mysql_query(<<-SQL) SELECT msg_id pmtextid, msg_topic_id topic_id, msg_author_id fromuserid, @@ -433,12 +448,12 @@ class ImportScripts::IpboardSQL < ImportScripts::Base LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ) puts "Processing #{private_messages.count} messages" break if private_messages.count < 1 puts "Processing . . . " - private_messages = private_messages.reject { |pm| @lookup.post_already_imported?("pm-#{pm['pmtextid']}") } + private_messages = + private_messages.reject { |pm| @lookup.post_already_imported?("pm-#{pm["pmtextid"]}") } title_username_of_pm_first_post = {} @@ -446,11 +461,16 @@ class ImportScripts::IpboardSQL < ImportScripts::Base skip = false mapped = {} - mapped[:id] = "pm-#{m['pmtextid']}" - mapped[:user_id] = user_id_from_imported_user_id(m['fromuserid']) || Discourse::SYSTEM_USER_ID - mapped[:raw] = clean_up(m['message']) rescue nil - mapped[:created_at] = Time.zone.at(m['dateline']) - title = @htmlentities.decode(m['title']).strip[0...255] + mapped[:id] = "pm-#{m["pmtextid"]}" + mapped[:user_id] = user_id_from_imported_user_id(m["fromuserid"]) || + Discourse::SYSTEM_USER_ID + mapped[:raw] = begin + clean_up(m["message"]) + rescue StandardError + nil + end + mapped[:created_at] = Time.zone.at(m["dateline"]) + title = @htmlentities.decode(m["title"]).strip[0...255] topic_id = nil next if mapped[:raw].blank? @@ -459,9 +479,9 @@ class ImportScripts::IpboardSQL < ImportScripts::Base target_usernames = [] target_userids = [] begin - to_user_array = [ m['to_user_id'] ] + array_from_members_string(m['touserarray']) - rescue - puts "#{m['pmtextid']} -- #{m['touserarray']}" + to_user_array = [m["to_user_id"]] + array_from_members_string(m["touserarray"]) + rescue StandardError + puts "#{m["pmtextid"]} -- #{m["touserarray"]}" skip = true end @@ -477,8 +497,8 @@ class ImportScripts::IpboardSQL < ImportScripts::Base puts "Can't find user: #{to_user}" end end - rescue - puts "skipping pm-#{m['pmtextid']} `to_user_array` is broken -- #{to_user_array.inspect}" + rescue StandardError + puts "skipping pm-#{m["pmtextid"]} `to_user_array` is broken -- #{to_user_array.inspect}" skip = true end @@ -486,30 +506,32 @@ class ImportScripts::IpboardSQL < ImportScripts::Base participants << mapped[:user_id] begin participants.sort! - rescue + rescue StandardError puts "one of the participant's id is nil -- #{participants.inspect}" end - if last_private_message_topic_id != m['topic_id'] - last_private_message_topic_id = m['topic_id'] - puts "New message: #{m['topic_id']}: #{title} from #{m['fromuserid']} (#{mapped[:user_id]})" unless QUIET + if last_private_message_topic_id != m["topic_id"] + last_private_message_topic_id = m["topic_id"] + unless QUIET + puts "New message: #{m["topic_id"]}: #{title} from #{m["fromuserid"]} (#{mapped[:user_id]})" + end # topic post message - topic_id = m['topic_id'] + topic_id = m["topic_id"] mapped[:title] = title mapped[:archetype] = Archetype.private_message - mapped[:target_usernames] = target_usernames.join(',') + mapped[:target_usernames] = target_usernames.join(",") if mapped[:target_usernames].size < 1 # pm with yourself? # skip = true mapped[:target_usernames] = "system" - puts "pm-#{m['pmtextid']} has no target (#{m['touserarray']})" + puts "pm-#{m["pmtextid"]} has no target (#{m["touserarray"]})" end else # reply topic_id = topic_lookup_from_imported_post_id("pm-#{topic_id}") - if !topic_id - skip = true - end + skip = true if !topic_id mapped[:topic_id] = topic_id - puts "Reply message #{topic_id}: #{m['topic_id']}: from #{m['fromuserid']} (#{mapped[:user_id]})" unless QUIET + unless QUIET + puts "Reply message #{topic_id}: #{m["topic_id"]}: from #{m["fromuserid"]} (#{mapped[:user_id]})" + end end # puts "#{target_usernames} -- #{mapped[:target_usernames]}" # puts "Adding #{mapped}" @@ -524,9 +546,13 @@ class ImportScripts::IpboardSQL < ImportScripts::Base puts "", "importing gallery albums..." gallery_count = 0 - total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}gallery_images - ;") - .first['count'] + total_count = + mysql_query( + "SELECT count(*) count FROM #{TABLE_PREFIX}gallery_images + ;", + ).first[ + "count" + ] # NOTE: for imports with huge numbers of galleries, this needs to use limits @@ -546,7 +572,7 @@ class ImportScripts::IpboardSQL < ImportScripts::Base # SQL # ) - images = mysql_query(<<-SQL + images = mysql_query(<<-SQL) SELECT #{TABLE_PREFIX}gallery_albums.album_id tid, #{TABLE_PREFIX}gallery_albums.album_category_id category, @@ -570,43 +596,46 @@ class ImportScripts::IpboardSQL < ImportScripts::Base SQL - ) - break if images.size < 1 - next if all_records_exist? :posts, images.map { |t| "gallery#" + t['tid'].to_s + t['image_id'].to_s } + if all_records_exist? :posts, + images.map { |t| "gallery#" + t["tid"].to_s + t["image_id"].to_s } + next + end - last_id = images.first['tid'] - raw = "Gallery ID: #{last_id}\n" + clean_up(images.first['raw']) - raw += "#{clean_up(images.first['description'])}\n" + last_id = images.first["tid"] + raw = "Gallery ID: #{last_id}\n" + clean_up(images.first["raw"]) + raw += "#{clean_up(images.first["description"])}\n" last_gallery = images.first.dup create_posts(images, total: total_count, offset: offset) do |gallery| - id = gallery['tid'].to_i + id = gallery["tid"].to_i #puts "ID: #{id}, last_id: #{last_id}, image: #{gallery['image_id']}" if id == last_id - raw += "### #{gallery['caption']}\n" - raw += "#{UPLOADS}/#{gallery['orig']}\n" + raw += "### #{gallery["caption"]}\n" + raw += "#{UPLOADS}/#{gallery["orig"]}\n" last_gallery = gallery.dup next else insert_raw = raw.dup - last_id = gallery['tid'] + last_id = gallery["tid"] if DEBUG - raw = "Gallery ID: #{last_id}\n" + clean_up(gallery['raw']) - raw += "Cat: #{last_gallery['category'].to_s} - #{category_id_from_imported_category_id(last_gallery['category'].to_s + 'gal')}" + raw = "Gallery ID: #{last_id}\n" + clean_up(gallery["raw"]) + raw += + "Cat: #{last_gallery["category"].to_s} - #{category_id_from_imported_category_id(last_gallery["category"].to_s + "gal")}" end - raw += "#{clean_up(images.first['description'])}\n" - raw += "### #{gallery['caption']}\n" - if DEBUG - raw += "User #{gallery['user_id']}, image_id: #{gallery['image_id']}\n" - end - raw += "#{UPLOADS}/#{gallery['orig']}\n" + raw += "#{clean_up(images.first["description"])}\n" + raw += "### #{gallery["caption"]}\n" + raw += "User #{gallery["user_id"]}, image_id: #{gallery["image_id"]}\n" if DEBUG + raw += "#{UPLOADS}/#{gallery["orig"]}\n" gallery_count += 1 - puts "#{gallery_count}--Cat: #{last_gallery['category'].to_s} ==> #{category_id_from_imported_category_id(last_gallery['category'].to_s + 'gal')}" unless QUIET + unless QUIET + puts "#{gallery_count}--Cat: #{last_gallery["category"].to_s} ==> #{category_id_from_imported_category_id(last_gallery["category"].to_s + "gal")}" + end { - id: "gallery#" + last_gallery['tid'].to_s + last_gallery['image_id'].to_s, - user_id: user_id_from_imported_user_id(last_gallery['user_id']) || Discourse::SYSTEM_USER_ID, - title: CGI.unescapeHTML(last_gallery['title']), - category: category_id_from_imported_category_id(last_gallery['category'].to_s + 'gal'), + id: "gallery#" + last_gallery["tid"].to_s + last_gallery["image_id"].to_s, + user_id: + user_id_from_imported_user_id(last_gallery["user_id"]) || Discourse::SYSTEM_USER_ID, + title: CGI.unescapeHTML(last_gallery["title"]), + category: category_id_from_imported_category_id(last_gallery["category"].to_s + "gal"), raw: insert_raw, } end @@ -630,11 +659,11 @@ class ImportScripts::IpboardSQL < ImportScripts::Base def import_comments puts "", "importing gallery comments..." - total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}gallery_comments;") - .first['count'] + total_count = + mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}gallery_comments;").first["count"] batches(BATCH_SIZE) do |offset| - comments = mysql_query(<<-SQL + comments = mysql_query(<<-SQL) SELECT #{TABLE_PREFIX}gallery_comments.tid tid, #{TABLE_PREFIX}gallery_topics.forum_id category, @@ -652,20 +681,19 @@ class ImportScripts::IpboardSQL < ImportScripts::Base OFFSET #{offset} SQL - ) break if comments.size < 1 - next if all_records_exist? :posts, comments.map { |comment| "comment#" + comment['pid'].to_s } + next if all_records_exist? :posts, comments.map { |comment| "comment#" + comment["pid"].to_s } create_posts(comments, total: total_count, offset: offset) do |comment| - next unless t = topic_lookup_from_imported_post_id("discussion#" + comment['tid'].to_s) - next if comment['raw'].blank? + next unless t = topic_lookup_from_imported_post_id("discussion#" + comment["tid"].to_s) + next if comment["raw"].blank? { - id: "comment#" + comment['pid'].to_s, - user_id: user_id_from_imported_user_id(comment['user_id']) || Discourse::SYSTEM_USER_ID, + id: "comment#" + comment["pid"].to_s, + user_id: user_id_from_imported_user_id(comment["user_id"]) || Discourse::SYSTEM_USER_ID, topic_id: t[:topic_id], - raw: clean_up(comment['raw']), - created_at: Time.zone.at(comment['created_at']) + raw: clean_up(comment["raw"]), + created_at: Time.zone.at(comment["created_at"]), } end end @@ -674,13 +702,17 @@ class ImportScripts::IpboardSQL < ImportScripts::Base def import_posts puts "", "importing posts..." - total_count = mysql_query("SELECT count(*) count FROM #{POSTS_TABLE} + total_count = + mysql_query( + "SELECT count(*) count FROM #{POSTS_TABLE} WHERE post_date > UNIX_TIMESTAMP(STR_TO_DATE('#{IMPORT_AFTER}', '%Y-%m-%d')) - AND new_topic=0;") - .first['count'] + AND new_topic=0;", + ).first[ + "count" + ] batches(BATCH_SIZE) do |offset| - comments = mysql_query(<<-SQL + comments = mysql_query(<<-SQL) SELECT #{TOPICS_TABLE}.tid tid, #{TOPICS_TABLE}.forum_id category, #{POSTS_TABLE}.pid pid, @@ -696,20 +728,19 @@ class ImportScripts::IpboardSQL < ImportScripts::Base LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ) break if comments.size < 1 - next if all_records_exist? :posts, comments.map { |comment| "comment#" + comment['pid'].to_s } + next if all_records_exist? :posts, comments.map { |comment| "comment#" + comment["pid"].to_s } create_posts(comments, total: total_count, offset: offset) do |comment| - next unless t = topic_lookup_from_imported_post_id("discussion#" + comment['tid'].to_s) - next if comment['raw'].blank? + next unless t = topic_lookup_from_imported_post_id("discussion#" + comment["tid"].to_s) + next if comment["raw"].blank? { - id: "comment#" + comment['pid'].to_s, - user_id: user_id_from_imported_user_id(comment['user_id']) || Discourse::SYSTEM_USER_ID, + id: "comment#" + comment["pid"].to_s, + user_id: user_id_from_imported_user_id(comment["user_id"]) || Discourse::SYSTEM_USER_ID, topic_id: t[:topic_id], - raw: clean_up(comment['raw']), - created_at: Time.zone.at(comment['created_at']) + raw: clean_up(comment["raw"]), + created_at: Time.zone.at(comment["created_at"]), } end end @@ -719,59 +750,61 @@ class ImportScripts::IpboardSQL < ImportScripts::Base # this makes proper quotes with user/topic/post references. # I'm not clear if it is for just some bizarre imported data, or it might ever be useful # It should be integrated into the Nokogiri section of clean_up, though. - @doc = Nokogiri::XML("" + raw + "") + @doc = Nokogiri.XML("" + raw + "") # handle
s with links to original post - @doc.css('blockquote[class=ipsQuote]').each do |b| - # puts "\n#{'#'*50}\n#{b}\n\nCONTENT: #{b['data-ipsquote-contentid']}" - # b.options = Nokogiri::XML::ParseOptions::STRICT - imported_post_id = b['data-ipsquote-contentcommentid'].to_s - content_type = b['data-ipsquote-contenttype'].to_s - content_class = b['data-ipsquote-contentclass'].to_s - content_id = b['data-ipsquote-contentid'].to_s || b['data-cid'].to_s - topic_lookup = topic_lookup_from_imported_post_id("comment#" + imported_post_id) - post_lookup = topic_lookup_from_imported_post_id("discussion#" + content_id) - post = topic_lookup ? topic_lookup[:post_number] : nil - topic = topic_lookup ? topic_lookup[:topic_id] : nil - post ||= post_lookup ? post_lookup[:post_number] : nil - topic ||= post_lookup ? post_lookup[:topic_id] : nil + @doc + .css("blockquote[class=ipsQuote]") + .each do |b| + # puts "\n#{'#'*50}\n#{b}\n\nCONTENT: #{b['data-ipsquote-contentid']}" + # b.options = Nokogiri::XML::ParseOptions::STRICT + imported_post_id = b["data-ipsquote-contentcommentid"].to_s + content_type = b["data-ipsquote-contenttype"].to_s + content_class = b["data-ipsquote-contentclass"].to_s + content_id = b["data-ipsquote-contentid"].to_s || b["data-cid"].to_s + topic_lookup = topic_lookup_from_imported_post_id("comment#" + imported_post_id) + post_lookup = topic_lookup_from_imported_post_id("discussion#" + content_id) + post = topic_lookup ? topic_lookup[:post_number] : nil + topic = topic_lookup ? topic_lookup[:topic_id] : nil + post ||= post_lookup ? post_lookup[:post_number] : nil + topic ||= post_lookup ? post_lookup[:topic_id] : nil - # TODO: consider:
- # consider:
-      # TODO make sure it's the imported username
-      # TODO: do _s still get \-escaped?
-      ips_username = b['data-ipsquote-username'] || b['data-author']
-      username = ips_username
-      new_text = ""
-      if DEBUG
-        # new_text += "post: #{imported_post_id} --> #{post_lookup} --> |#{post}|
\n" - # new_text += "topic: #{content_id} --> #{topic_lookup} --> |#{topic}|
\n" - # new_text += "user: #{ips_username} --> |#{username}|
\n" - # new_text += "class: #{content_class}
\n" - # new_text += "type: #{content_type}
\n" - if content_class.length > 0 && content_class != "forums_Topic" - new_text += "UNEXPECTED CONTENT CLASS! #{content_class}
\n" + # TODO: consider:
+ # consider:
+        # TODO make sure it's the imported username
+        # TODO: do _s still get \-escaped?
+        ips_username = b["data-ipsquote-username"] || b["data-author"]
+        username = ips_username
+        new_text = ""
+        if DEBUG
+          # new_text += "post: #{imported_post_id} --> #{post_lookup} --> |#{post}|
\n" + # new_text += "topic: #{content_id} --> #{topic_lookup} --> |#{topic}|
\n" + # new_text += "user: #{ips_username} --> |#{username}|
\n" + # new_text += "class: #{content_class}
\n" + # new_text += "type: #{content_type}
\n" + if content_class.length > 0 && content_class != "forums_Topic" + new_text += "UNEXPECTED CONTENT CLASS! #{content_class}
\n" + end + if content_type.length > 0 && content_type != "forums" + new_text += "UNEXPECTED CONTENT TYPE! #{content_type}
\n" + end + # puts "#{'-'*20} and NOWWWWW!!!! \n #{new_text}" end - if content_type.length > 0 && content_type != "forums" - new_text += "UNEXPECTED CONTENT TYPE! #{content_type}
\n" - end - # puts "#{'-'*20} and NOWWWWW!!!! \n #{new_text}" - end - if post && topic && username - quote = "\n[quote=\"#{username}, post:#{post}, topic: #{topic}\"]\n\n" - else - if username && username.length > 1 - quote = "\n[quote=\"#{username}\"]\n\n" + if post && topic && username + quote = "\n[quote=\"#{username}, post:#{post}, topic: #{topic}\"]\n\n" else - quote = "\n[quote]\n" + if username && username.length > 1 + quote = "\n[quote=\"#{username}\"]\n\n" + else + quote = "\n[quote]\n" + end + # new_doc = Nokogiri::XML("
#{new_text}
") end - # new_doc = Nokogiri::XML("
#{new_text}
") + puts "QUOTE: #{quote}" + sleep 1 + b.content = quote + b.content + "\n[/quote]\n" + b.name = "div" end - puts "QUOTE: #{quote}" - sleep 1 - b.content = quote + b.content + "\n[/quote]\n" - b.name = 'div' - end raw = @doc.to_html end @@ -783,24 +816,30 @@ class ImportScripts::IpboardSQL < ImportScripts::Base # TODO what about uploads? # raw.gsub!(//,UPLOADS) raw.gsub!(/
/, "\n\n") - raw.gsub!(/
/, "\n\n") - raw.gsub!(/

 <\/p>/, "\n\n") + raw.gsub!(%r{
}, "\n\n") + raw.gsub!(%r{

 

}, "\n\n") raw.gsub!(/\[hr\]/, "\n***\n") raw.gsub!(/'/, "'") - raw.gsub!(/\[url="(.+?)"\]http.+?\[\/url\]/, "\\1\n") - raw.gsub!(/\[media\](.+?)\[\/media\]/, "\n\\1\n\n") - raw.gsub!(/\[php\](.+?)\[\/php\]/m) { |m| "\n\n```php\n\n" + @htmlentities.decode($1.gsub(/\n\n/, "\n")) + "\n\n```\n\n" } - raw.gsub!(/\[code\](.+?)\[\/code\]/m) { |m| "\n\n```\n\n" + @htmlentities.decode($1.gsub(/\n\n/, "\n")) + "\n\n```\n\n" } - raw.gsub!(/\[list\](.+?)\[\/list\]/m) { |m| "\n" + $1.gsub(/\[\*\]/, "\n- ") + "\n\n" } + raw.gsub!(%r{\[url="(.+?)"\]http.+?\[/url\]}, "\\1\n") + raw.gsub!(%r{\[media\](.+?)\[/media\]}, "\n\\1\n\n") + raw.gsub!(%r{\[php\](.+?)\[/php\]}m) do |m| + "\n\n```php\n\n" + @htmlentities.decode($1.gsub(/\n\n/, "\n")) + "\n\n```\n\n" + end + raw.gsub!(%r{\[code\](.+?)\[/code\]}m) do |m| + "\n\n```\n\n" + @htmlentities.decode($1.gsub(/\n\n/, "\n")) + "\n\n```\n\n" + end + raw.gsub!(%r{\[list\](.+?)\[/list\]}m) { |m| "\n" + $1.gsub(/\[\*\]/, "\n- ") + "\n\n" } raw.gsub!(/\[quote\]/, "\n[quote]\n") - raw.gsub!(/\[\/quote\]/, "\n[/quote]\n") - raw.gsub!(/date=\'(.+?)\'/, '') - raw.gsub!(/timestamp=\'(.+?)\' /, '') + raw.gsub!(%r{\[/quote\]}, "\n[/quote]\n") + raw.gsub!(/date=\'(.+?)\'/, "") + raw.gsub!(/timestamp=\'(.+?)\' /, "") quote_regex = /\[quote name=\'(.+?)\'\s+post=\'(\d+?)\'\s*\]/ while quote = quote_regex.match(raw) # get IPB post number and find Discourse post and topic number - puts "----------------------------------------\nName: #{quote[1]}, post: #{quote[2]}" unless QUIET + unless QUIET + puts "----------------------------------------\nName: #{quote[1]}, post: #{quote[2]}" + end imported_post_id = quote[2].to_s topic_lookup = topic_lookup_from_imported_post_id("comment#" + imported_post_id) post_lookup = topic_lookup_from_imported_post_id("discussion#" + imported_post_id) @@ -826,21 +865,24 @@ class ImportScripts::IpboardSQL < ImportScripts::Base while attach = attach_regex.match(raw) attach_id = attach[1] attachments = - mysql_query("SELECT attach_location as loc, + mysql_query( + "SELECT attach_location as loc, attach_file as filename FROM #{ATTACHMENT_TABLE} - WHERE attach_id=#{attach_id}") + WHERE attach_id=#{attach_id}", + ) if attachments.count < 1 puts "Attachment #{attach_id} not found." attach_string = "Attachment #{attach_id} not found." else - attach_url = "#{UPLOADS}/#{attachments.first['loc'].gsub(' ', '%20')}" - if attachments.first['filename'].match(/(png|jpg|jpeg|gif)$/) + attach_url = "#{UPLOADS}/#{attachments.first["loc"].gsub(" ", "%20")}" + if attachments.first["filename"].match(/(png|jpg|jpeg|gif)$/) # images are rendered as a link that contains the image - attach_string = "#{attach_id}\n\n[![#{attachments.first['filename']}](#{attach_url})](#{attach_url})\n" + attach_string = + "#{attach_id}\n\n[![#{attachments.first["filename"]}](#{attach_url})](#{attach_url})\n" else # other attachments are simple download links - attach_string = "#{attach_id}\n\n[#{attachments.first['filename']}](#{attach_url})\n" + attach_string = "#{attach_id}\n\n[#{attachments.first["filename"]}](#{attach_url})\n" end end raw.sub!(attach_regex, attach_string) @@ -850,7 +892,7 @@ class ImportScripts::IpboardSQL < ImportScripts::Base end def random_category_color - colors = SiteSetting.category_colors.split('|') + colors = SiteSetting.category_colors.split("|") colors[rand(colors.count)] end @@ -865,78 +907,78 @@ class ImportScripts::IpboardSQL < ImportScripts::Base raw.gsub!(//, UPLOADS) raw.gsub!(/
/, "\n") - @doc = Nokogiri::XML("" + raw + "") + @doc = Nokogiri.XML("" + raw + "") # handle
s with links to original post - @doc.css('blockquote[class=ipsQuote]').each do |b| - imported_post_id = b['data-ipsquote-contentcommentid'].to_s - content_type = b['data-ipsquote-contenttype'].to_s - content_class = b['data-ipsquote-contentclass'].to_s - content_id = b['data-ipsquote-contentid'].to_s || b['data-cid'].to_s - topic_lookup = topic_lookup_from_imported_post_id("comment#" + imported_post_id) - post_lookup = topic_lookup_from_imported_post_id("discussion#" + content_id) - post = topic_lookup ? topic_lookup[:post_number] : nil - topic = topic_lookup ? topic_lookup[:topic_id] : nil - post ||= post_lookup ? post_lookup[:post_number] : nil - topic ||= post_lookup ? post_lookup[:topic_id] : nil + @doc + .css("blockquote[class=ipsQuote]") + .each do |b| + imported_post_id = b["data-ipsquote-contentcommentid"].to_s + content_type = b["data-ipsquote-contenttype"].to_s + content_class = b["data-ipsquote-contentclass"].to_s + content_id = b["data-ipsquote-contentid"].to_s || b["data-cid"].to_s + topic_lookup = topic_lookup_from_imported_post_id("comment#" + imported_post_id) + post_lookup = topic_lookup_from_imported_post_id("discussion#" + content_id) + post = topic_lookup ? topic_lookup[:post_number] : nil + topic = topic_lookup ? topic_lookup[:topic_id] : nil + post ||= post_lookup ? post_lookup[:post_number] : nil + topic ||= post_lookup ? post_lookup[:topic_id] : nil - # TODO: consider:
- # consider:
-      ips_username = b['data-ipsquote-username'] || b['data-author']
-      username = ips_username
-      new_text = ""
-      if DEBUG
-        if content_class.length > 0 && content_class != "forums_Topic"
-          new_text += "UNEXPECTED CONTENT CLASS! #{content_class}
\n" + # TODO: consider:
+ # consider:
+        ips_username = b["data-ipsquote-username"] || b["data-author"]
+        username = ips_username
+        new_text = ""
+        if DEBUG
+          if content_class.length > 0 && content_class != "forums_Topic"
+            new_text += "UNEXPECTED CONTENT CLASS! #{content_class}
\n" + end + if content_type.length > 0 && content_type != "forums" + new_text += "UNEXPECTED CONTENT TYPE! #{content_type}
\n" + end end - if content_type.length > 0 && content_type != "forums" - new_text += "UNEXPECTED CONTENT TYPE! #{content_type}
\n" - end - end - if post && topic && username - quote = "[quote=\"#{username}, post:#{post}, topic: #{topic}\"]\n\n" - else - if username && username.length > 1 - quote = "[quote=\"#{username}\"]\n\n" + if post && topic && username + quote = "[quote=\"#{username}, post:#{post}, topic: #{topic}\"]\n\n" else - quote = "[quote]\n" + if username && username.length > 1 + quote = "[quote=\"#{username}\"]\n\n" + else + quote = "[quote]\n" + end end + b.content = quote + b.content + "\n[/quote]\n" + b.name = "div" end - b.content = quote + b.content + "\n[/quote]\n" - b.name = 'div' - end - @doc.css('object param embed').each do |embed| - embed.replace("\n#{embed['src']}\n") - end + @doc.css("object param embed").each { |embed| embed.replace("\n#{embed["src"]}\n") } # handle }mix youtube_cooked.gsub!(re) { "\n#{$1}\n" } - re = //mix + re = %r{}mix youtube_cooked.gsub!(re) { "\n#{$1}\n" } - youtube_cooked.gsub!(/^\/\//, "https://") # make sure it has a protocol + youtube_cooked.gsub!(%r{^//}, "https://") # make sure it has a protocol unless /http/.match(youtube_cooked) # handle case of only youtube object number if youtube_cooked.length < 8 || /[<>=]/.match(youtube_cooked) # probably not a youtube id youtube_cooked = "" else - youtube_cooked = 'https://www.youtube.com/watch?v=' + youtube_cooked + youtube_cooked = "https://www.youtube.com/watch?v=" + youtube_cooked end end - print_warning("#{'-' * 40}\nBefore: #{youtube_raw}\nAfter: #{youtube_cooked}") unless QUIET + print_warning("#{"-" * 40}\nBefore: #{youtube_raw}\nAfter: #{youtube_cooked}") unless QUIET youtube_cooked end @@ -313,73 +334,79 @@ class ImportScripts::MylittleforumSQL < ImportScripts::Base raw = raw.gsub("\\'", "'") raw = raw.gsub(/\[b\]/i, "") - raw = raw.gsub(/\[\/b\]/i, "") + raw = raw.gsub(%r{\[/b\]}i, "") raw = raw.gsub(/\[i\]/i, "") - raw = raw.gsub(/\[\/i\]/i, "") + raw = raw.gsub(%r{\[/i\]}i, "") raw = raw.gsub(/\[u\]/i, "") - raw = raw.gsub(/\[\/u\]/i, "") + raw = raw.gsub(%r{\[/u\]}i, "") - raw = raw.gsub(/\[url\](\S+)\[\/url\]/im) { "#{$1}" } - raw = raw.gsub(/\[link\](\S+)\[\/link\]/im) { "#{$1}" } + raw = raw.gsub(%r{\[url\](\S+)\[/url\]}im) { "#{$1}" } + raw = raw.gsub(%r{\[link\](\S+)\[/link\]}im) { "#{$1}" } # URL & LINK with text - raw = raw.gsub(/\[url=(\S+?)\](.*?)\[\/url\]/im) { "#{$2}" } - raw = raw.gsub(/\[link=(\S+?)\](.*?)\[\/link\]/im) { "#{$2}" } + raw = raw.gsub(%r{\[url=(\S+?)\](.*?)\[/url\]}im) { "#{$2}" } + raw = raw.gsub(%r{\[link=(\S+?)\](.*?)\[/link\]}im) { "#{$2}" } # remote images - raw = raw.gsub(/\[img\](https?:.+?)\[\/img\]/im) { "" } - raw = raw.gsub(/\[img=(https?.+?)\](.+?)\[\/img\]/im) { "\"#{$2}\"" } + raw = raw.gsub(%r{\[img\](https?:.+?)\[/img\]}im) { "" } + raw = raw.gsub(%r{\[img=(https?.+?)\](.+?)\[/img\]}im) { "\"#{$2}\"" } # local images - raw = raw.gsub(/\[img\](.+?)\[\/img\]/i) { "" } - raw = raw.gsub(/\[img=(.+?)\](https?.+?)\[\/img\]/im) { "\"#{$2}\"" } + raw = raw.gsub(%r{\[img\](.+?)\[/img\]}i) { "" } + raw = + raw.gsub(%r{\[img=(.+?)\](https?.+?)\[/img\]}im) do + "\"#{$2}\"" + end # Convert image bbcode - raw.gsub!(/\[img=(\d+),(\d+)\]([^\]]*)\[\/img\]/im, '') + raw.gsub!(%r{\[img=(\d+),(\d+)\]([^\]]*)\[/img\]}im, '') # [div]s are really [quote]s raw.gsub!(/\[div\]/mix, "[quote]") - raw.gsub!(/\[\/div\]/mix, "[/quote]") + raw.gsub!(%r{\[/div\]}mix, "[/quote]") # [postedby] -> link to @user - raw.gsub(/\[postedby\](.+?)\[b\](.+?)\[\/b\]\[\/postedby\]/i) { "#{$1}@#{$2}" } + raw.gsub(%r{\[postedby\](.+?)\[b\](.+?)\[/b\]\[/postedby\]}i) { "#{$1}@#{$2}" } # CODE (not tested) - raw = raw.gsub(/\[code\](\S+)\[\/code\]/im) { "```\n#{$1}\n```" } - raw = raw.gsub(/\[pre\](\S+)\[\/pre\]/im) { "```\n#{$1}\n```" } + raw = raw.gsub(%r{\[code\](\S+)\[/code\]}im) { "```\n#{$1}\n```" } + raw = raw.gsub(%r{\[pre\](\S+)\[/pre\]}im) { "```\n#{$1}\n```" } - raw = raw.gsub(/(https:\/\/youtu\S+)/i) { "\n#{$1}\n" } #youtube links on line by themselves + raw = raw.gsub(%r{(https://youtu\S+)}i) { "\n#{$1}\n" } #youtube links on line by themselves # no center - raw = raw.gsub(/\[\/?center\]/i, "") + raw = raw.gsub(%r{\[/?center\]}i, "") # no size - raw = raw.gsub(/\[\/?size.*?\]/i, "") + raw = raw.gsub(%r{\[/?size.*?\]}i, "") ### FROM VANILLA: # fix whitespaces - raw = raw.gsub(/(\\r)?\\n/, "\n") - .gsub("\\t", "\t") + raw = raw.gsub(/(\\r)?\\n/, "\n").gsub("\\t", "\t") unless CONVERT_HTML # replace all chevrons with HTML entities # NOTE: must be done # - AFTER all the "code" processing # - BEFORE the "quote" processing - raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub("<", "\u2603") + "`" } - .gsub("<", "<") - .gsub("\u2603", "<") + raw = + raw + .gsub(/`([^`]+)`/im) { "`" + $1.gsub("<", "\u2603") + "`" } + .gsub("<", "<") + .gsub("\u2603", "<") - raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub(">", "\u2603") + "`" } - .gsub(">", ">") - .gsub("\u2603", ">") + raw = + raw + .gsub(/`([^`]+)`/im) { "`" + $1.gsub(">", "\u2603") + "`" } + .gsub(">", ">") + .gsub("\u2603", ">") end # Remove the color tag raw.gsub!(/\[color=[#a-z0-9]+\]/i, "") - raw.gsub!(/\[\/color\]/i, "") + raw.gsub!(%r{\[/color\]}i, "") ### END VANILLA: raw @@ -395,54 +422,72 @@ class ImportScripts::MylittleforumSQL < ImportScripts::Base end def create_permalinks - puts '', 'Creating redirects...', '' + puts "", "Creating redirects...", "" - puts '', 'Users...', '' + puts "", "Users...", "" User.find_each do |u| ucf = u.custom_fields if ucf && ucf["import_id"] && ucf["import_username"] - Permalink.create(url: "#{BASE}/user-id-#{ucf['import_id']}.html", external_url: "/u/#{u.username}") rescue nil - print '.' + begin + Permalink.create( + url: "#{BASE}/user-id-#{ucf["import_id"]}.html", + external_url: "/u/#{u.username}", + ) + rescue StandardError + nil + end + print "." end end - puts '', 'Posts...', '' + puts "", "Posts...", "" Post.find_each do |post| pcf = post.custom_fields if pcf && pcf["import_id"] topic = post.topic - id = pcf["import_id"].split('#').last + id = pcf["import_id"].split("#").last if post.post_number == 1 - Permalink.create(url: "#{BASE}/forum_entry-id-#{id}.html", topic_id: topic.id) rescue nil + begin + Permalink.create(url: "#{BASE}/forum_entry-id-#{id}.html", topic_id: topic.id) + rescue StandardError + nil + end unless QUIET print_warning("forum_entry-id-#{id}.html --> http://localhost:3000/t/#{topic.id}") end else - Permalink.create(url: "#{BASE}/forum_entry-id-#{id}.html", post_id: post.id) rescue nil + begin + Permalink.create(url: "#{BASE}/forum_entry-id-#{id}.html", post_id: post.id) + rescue StandardError + nil + end unless QUIET - print_warning("forum_entry-id-#{id}.html --> http://localhost:3000/t/#{topic.id}/#{post.id}") + print_warning( + "forum_entry-id-#{id}.html --> http://localhost:3000/t/#{topic.id}/#{post.id}", + ) end end - print '.' + print "." end end - puts '', 'Categories...', '' + puts "", "Categories...", "" Category.find_each do |cat| ccf = cat.custom_fields next unless id = ccf["import_id"] - unless QUIET - print_warning("forum-category-#{id}.html --> /t/#{cat.id}") + print_warning("forum-category-#{id}.html --> /t/#{cat.id}") unless QUIET + begin + Permalink.create(url: "#{BASE}/forum-category-#{id}.html", category_id: cat.id) + rescue StandardError + nil end - Permalink.create(url: "#{BASE}/forum-category-#{id}.html", category_id: cat.id) rescue nil - print '.' + print "." end end def print_warning(message) $stderr.puts "#{message}" end - end ImportScripts::MylittleforumSQL.new.perform diff --git a/script/import_scripts/nabble.rb b/script/import_scripts/nabble.rb index e877e9058d..c43efe8e5c 100644 --- a/script/import_scripts/nabble.rb +++ b/script/import_scripts/nabble.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require 'pg' -require_relative 'base/uploader' +require "pg" +require_relative "base/uploader" =begin if you want to create mock users for posts made by anonymous participants, @@ -40,7 +40,7 @@ class ImportScripts::Nabble < ImportScripts::Base BATCH_SIZE = 1000 - DB_NAME = "nabble" + DB_NAME = "nabble" CATEGORY_ID = 6 def initialize @@ -64,14 +64,13 @@ class ImportScripts::Nabble < ImportScripts::Base total_count = @client.exec("SELECT COUNT(user_id) FROM user_")[0]["count"] batches(BATCH_SIZE) do |offset| - users = @client.query(<<-SQL + users = @client.query(<<-SQL) SELECT user_id, name, email, joined FROM user_ ORDER BY joined LIMIT #{BATCH_SIZE} OFFSET #{offset} SQL - ) break if users.ntuples() < 1 @@ -83,24 +82,23 @@ class ImportScripts::Nabble < ImportScripts::Base email: row["email"] || fake_email, created_at: Time.zone.at(@td.decode(row["joined"])), name: row["name"], - post_create_action: proc do |user| - import_avatar(user, row["user_id"]) - end + post_create_action: proc { |user| import_avatar(user, row["user_id"]) }, } end end end def import_avatar(user, org_id) - filename = 'avatar' + org_id.to_s - path = File.join('/tmp/nab', filename) - res = @client.exec("SELECT content FROM file_avatar WHERE name='avatar100.png' AND user_id = #{org_id} LIMIT 1") + filename = "avatar" + org_id.to_s + path = File.join("/tmp/nab", filename) + res = + @client.exec( + "SELECT content FROM file_avatar WHERE name='avatar100.png' AND user_id = #{org_id} LIMIT 1", + ) return if res.ntuples() < 1 - binary = res[0]['content'] - File.open(path, 'wb') { |f| - f.write(PG::Connection.unescape_bytea(binary)) - } + binary = res[0]["content"] + File.open(path, "wb") { |f| f.write(PG::Connection.unescape_bytea(binary)) } upload = @uploader.create_upload(user.id, path, filename) @@ -113,7 +111,6 @@ class ImportScripts::Nabble < ImportScripts::Base else Rails.logger.error("Could not persist avatar for user #{user.username}") end - end def parse_email(msg) @@ -128,11 +125,13 @@ class ImportScripts::Nabble < ImportScripts::Base def create_forum_topics puts "", "creating forum topics" - app_node_id = @client.exec("SELECT node_id FROM node WHERE is_app LIMIT 1")[0]['node_id'] - topic_count = @client.exec("SELECT COUNT(node_id) AS count FROM node WHERE parent_id = #{app_node_id}")[0]["count"] + app_node_id = @client.exec("SELECT node_id FROM node WHERE is_app LIMIT 1")[0]["node_id"] + topic_count = + @client.exec("SELECT COUNT(node_id) AS count FROM node WHERE parent_id = #{app_node_id}")[0][ + "count" + ] batches(BATCH_SIZE) do |offset| - topics = @client.exec <<-SQL SELECT n.node_id, n.subject, n.owner_id, n.when_created, nm.message, n.msg_fmt FROM node AS n @@ -145,43 +144,43 @@ class ImportScripts::Nabble < ImportScripts::Base break if topics.ntuples() < 1 - next if all_records_exist? :posts, topics.map { |t| t['node_id'].to_i } + next if all_records_exist? :posts, topics.map { |t| t["node_id"].to_i } create_posts(topics, total: topic_count, offset: offset) do |t| raw = body_from(t) next unless raw raw = process_content(raw) - raw = process_attachments(raw, t['node_id']) + raw = process_attachments(raw, t["node_id"]) { - id: t['node_id'], - title: t['subject'], + id: t["node_id"], + title: t["subject"], user_id: user_id_from_imported_user_id(t["owner_id"]) || Discourse::SYSTEM_USER_ID, created_at: Time.zone.at(@td.decode(t["when_created"])), category: CATEGORY_ID, raw: raw, - cook_method: Post.cook_methods[:regular] + cook_method: Post.cook_methods[:regular], } end end end def body_from(p) - %w(m s).include?(p['msg_fmt']) ? parse_email(p['message']) : p['message'] + %w[m s].include?(p["msg_fmt"]) ? parse_email(p["message"]) : p["message"] rescue Email::Receiver::EmptyEmailError - puts "Skipped #{p['node_id']}" + puts "Skipped #{p["node_id"]}" end def process_content(txt) txt.gsub! /\/, '[quote="\1"]' - txt.gsub! /\<\/quote\>/, '[/quote]' - txt.gsub!(/\(.*?)\<\/raw\>/m) do |match| + txt.gsub! %r{\}, "[/quote]" + txt.gsub!(%r{\(.*?)\}m) do |match| c = Regexp.last_match[1].indent(4) - "\n#{c}\n" + "\n#{c}\n" end # lines starting with # are comments, not headings, insert a space to prevent markdown - txt.gsub! /\n#/m, ' #' + txt.gsub! /\n#/m, " #" # in the languagetool forum, quite a lot of XML was not marked as raw # so we treat ... and ... as raw @@ -202,12 +201,10 @@ class ImportScripts::Nabble < ImportScripts::Base def process_attachments(txt, postid) txt.gsub!(//m) do |match| basename = Regexp.last_match[1] - get_attachment_upload(basename, postid) do |upload| - @uploader.embedded_image_html(upload) - end + get_attachment_upload(basename, postid) { |upload| @uploader.embedded_image_html(upload) } end - txt.gsub!(/(.*?)<\/nabble_a>/m) do |match| + txt.gsub!(%r{(.*?)}m) do |match| basename = Regexp.last_match[1] get_attachment_upload(basename, postid) do |upload| @uploader.attachment_html(upload, basename) @@ -217,13 +214,12 @@ class ImportScripts::Nabble < ImportScripts::Base end def get_attachment_upload(basename, postid) - contents = @client.exec("SELECT content FROM file_node WHERE name='#{basename}' AND node_id = #{postid}") + contents = + @client.exec("SELECT content FROM file_node WHERE name='#{basename}' AND node_id = #{postid}") if contents.any? - binary = contents[0]['content'] - fn = File.join('/tmp/nab', basename) - File.open(fn, 'wb') { |f| - f.write(PG::Connection.unescape_bytea(binary)) - } + binary = contents[0]["content"] + fn = File.join("/tmp/nab", basename) + File.open(fn, "wb") { |f| f.write(PG::Connection.unescape_bytea(binary)) } yield @uploader.create_upload(0, fn, basename) end end @@ -231,8 +227,11 @@ class ImportScripts::Nabble < ImportScripts::Base def import_replies puts "", "creating topic replies" - app_node_id = @client.exec("SELECT node_id FROM node WHERE is_app LIMIT 1")[0]['node_id'] - post_count = @client.exec("SELECT COUNT(node_id) AS count FROM node WHERE parent_id != #{app_node_id}")[0]["count"] + app_node_id = @client.exec("SELECT node_id FROM node WHERE is_app LIMIT 1")[0]["node_id"] + post_count = + @client.exec("SELECT COUNT(node_id) AS count FROM node WHERE parent_id != #{app_node_id}")[0][ + "count" + ] topic_ids = {} @@ -249,11 +248,11 @@ class ImportScripts::Nabble < ImportScripts::Base break if posts.ntuples() < 1 - next if all_records_exist? :posts, posts.map { |p| p['node_id'].to_i } + next if all_records_exist? :posts, posts.map { |p| p["node_id"].to_i } create_posts(posts, total: post_count, offset: offset) do |p| - parent_id = p['parent_id'] - id = p['node_id'] + parent_id = p["parent_id"] + id = p["node_id"] topic_id = topic_ids[parent_id] unless topic_id @@ -268,19 +267,21 @@ class ImportScripts::Nabble < ImportScripts::Base next unless raw raw = process_content(raw) raw = process_attachments(raw, id) - { id: id, + { + id: id, topic_id: topic_id, - user_id: user_id_from_imported_user_id(p['owner_id']) || Discourse::SYSTEM_USER_ID, + user_id: user_id_from_imported_user_id(p["owner_id"]) || Discourse::SYSTEM_USER_ID, created_at: Time.zone.at(@td.decode(p["when_created"])), raw: raw, - cook_method: Post.cook_methods[:regular] } + cook_method: Post.cook_methods[:regular], + } end end end end class String - def indent(count, char = ' ') + def indent(count, char = " ") gsub(/([^\n]*)(\n|$)/) do |match| last_iteration = ($1 == "" && $2 == "") line = +"" diff --git a/script/import_scripts/ning.rb b/script/import_scripts/ning.rb index 30612ec969..3af9b080d1 100644 --- a/script/import_scripts/ning.rb +++ b/script/import_scripts/ning.rb @@ -5,28 +5,28 @@ require File.expand_path(File.dirname(__FILE__) + "/base.rb") # Edit the constants and initialize method for your import data. class ImportScripts::Ning < ImportScripts::Base - JSON_FILES_DIR = "/Users/techapj/Downloads/ben/ADEM" - ATTACHMENT_PREFIXES = ["discussions", "pages", "blogs", "members", "photos"] - EXTRA_AUTHORIZED_EXTENSIONS = ["bmp", "ico", "txt", "pdf", "gif", "jpg", "jpeg", "html"] + ATTACHMENT_PREFIXES = %w[discussions pages blogs members photos] + EXTRA_AUTHORIZED_EXTENSIONS = %w[bmp ico txt pdf gif jpg jpeg html] def initialize super @system_user = Discourse.system_user - @users_json = load_ning_json("ning-members-local.json") + @users_json = load_ning_json("ning-members-local.json") @discussions_json = load_ning_json("ning-discussions-local.json") # An example of a custom category from Ning: @blogs_json = load_ning_json("ning-blogs-local.json") - @photos_json = load_ning_json("ning-photos-local.json") - @pages_json = load_ning_json("ning-pages-local.json") + @photos_json = load_ning_json("ning-photos-local.json") + @pages_json = load_ning_json("ning-pages-local.json") - SiteSetting.max_image_size_kb = 10240 - SiteSetting.max_attachment_size_kb = 10240 - SiteSetting.authorized_extensions = (SiteSetting.authorized_extensions.split("|") + EXTRA_AUTHORIZED_EXTENSIONS).uniq.join("|") + SiteSetting.max_image_size_kb = 10_240 + SiteSetting.max_attachment_size_kb = 10_240 + SiteSetting.authorized_extensions = + (SiteSetting.authorized_extensions.split("|") + EXTRA_AUTHORIZED_EXTENSIONS).uniq.join("|") # Example of importing a custom profile field: # @interests_field = UserField.find_by_name("My interests") @@ -60,23 +60,23 @@ class ImportScripts::Ning < ImportScripts::Base end def repair_json(arg) - arg.gsub!(/^\(/, "") # content of file is surround by ( ) + arg.gsub!(/^\(/, "") # content of file is surround by ( ) arg.gsub!(/\)$/, "") - arg.gsub!(/\]\]$/, "]") # there can be an extra ] at the end + arg.gsub!(/\]\]$/, "]") # there can be an extra ] at the end arg.gsub!(/\}\{/, "},{") # missing commas sometimes! - arg.gsub!("}]{", "},{") # surprise square brackets - arg.gsub!("}[{", "},{") # :troll: + arg.gsub!("}]{", "},{") # surprise square brackets + arg.gsub!("}[{", "},{") # :troll: arg end def import_users - puts '', "Importing users" + puts "", "Importing users" - staff_levels = ["admin", "moderator", "owner"] + staff_levels = %w[admin moderator owner] create_users(@users_json) do |u| { @@ -88,57 +88,58 @@ class ImportScripts::Ning < ImportScripts::Base location: "#{u["location"]} #{u["country"]}", avatar_url: u["profilePhoto"], bio_raw: u["profileQuestions"].is_a?(Hash) ? u["profileQuestions"]["About Me"] : nil, - post_create_action: proc do |newuser| - # if u["profileQuestions"].is_a?(Hash) - # newuser.custom_fields = {"user_field_#{@interests_field.id}" => u["profileQuestions"]["My interests"]} - # end + post_create_action: + proc do |newuser| + # if u["profileQuestions"].is_a?(Hash) + # newuser.custom_fields = {"user_field_#{@interests_field.id}" => u["profileQuestions"]["My interests"]} + # end - if staff_levels.include?(u["level"].downcase) - if u["level"].downcase == "admin" || u["level"].downcase == "owner" - newuser.admin = true - else - newuser.moderator = true - end - end - - # states: ["active", "suspended", "left", "pending"] - if u["state"] == "active" && newuser.approved_at.nil? - newuser.approved = true - newuser.approved_by_id = @system_user.id - newuser.approved_at = newuser.created_at - end - - newuser.save - - if u["profilePhoto"] && newuser.user_avatar.try(:custom_upload_id).nil? - photo_path = file_full_path(u["profilePhoto"]) - if File.exist?(photo_path) - begin - upload = create_upload(newuser.id, photo_path, File.basename(photo_path)) - if upload.persisted? - newuser.import_mode = false - newuser.create_user_avatar - newuser.import_mode = true - newuser.user_avatar.update(custom_upload_id: upload.id) - newuser.update(uploaded_avatar_id: upload.id) - else - puts "Error: Upload did not persist for #{photo_path}!" - end - rescue SystemCallError => err - puts "Could not import avatar #{photo_path}: #{err.message}" + if staff_levels.include?(u["level"].downcase) + if u["level"].downcase == "admin" || u["level"].downcase == "owner" + newuser.admin = true + else + newuser.moderator = true end - else - puts "avatar file not found at #{photo_path}" end - end - end + + # states: ["active", "suspended", "left", "pending"] + if u["state"] == "active" && newuser.approved_at.nil? + newuser.approved = true + newuser.approved_by_id = @system_user.id + newuser.approved_at = newuser.created_at + end + + newuser.save + + if u["profilePhoto"] && newuser.user_avatar.try(:custom_upload_id).nil? + photo_path = file_full_path(u["profilePhoto"]) + if File.exist?(photo_path) + begin + upload = create_upload(newuser.id, photo_path, File.basename(photo_path)) + if upload.persisted? + newuser.import_mode = false + newuser.create_user_avatar + newuser.import_mode = true + newuser.user_avatar.update(custom_upload_id: upload.id) + newuser.update(uploaded_avatar_id: upload.id) + else + puts "Error: Upload did not persist for #{photo_path}!" + end + rescue SystemCallError => err + puts "Could not import avatar #{photo_path}: #{err.message}" + end + else + puts "avatar file not found at #{photo_path}" + end + end + end, } end EmailToken.delete_all end def suspend_users - puts '', "Updating suspended users" + puts "", "Updating suspended users" count = 0 suspended = 0 @@ -151,7 +152,10 @@ class ImportScripts::Ning < ImportScripts::Base user.suspended_till = 200.years.from_now if user.save - StaffActionLogger.new(@system_user).log_user_suspend(user, "Import data indicates account is suspended.") + StaffActionLogger.new(@system_user).log_user_suspend( + user, + "Import data indicates account is suspended.", + ) suspended += 1 else puts "Failed to suspend user #{user.username}. #{user.errors.try(:full_messages).try(:inspect)}" @@ -168,13 +172,15 @@ class ImportScripts::Ning < ImportScripts::Base def import_categories puts "", "Importing categories" - create_categories((["Blog", "Pages", "Photos"] + @discussions_json.map { |d| d["category"] }).uniq.compact) do |name| + create_categories( + (%w[Blog Pages Photos] + @discussions_json.map { |d| d["category"] }).uniq.compact, + ) do |name| if name.downcase == "uncategorized" nil else { id: name, # ning has no id for categories, so use the name - name: name + name: name, } end end @@ -220,9 +226,7 @@ class ImportScripts::Ning < ImportScripts::Base unless topic["category"].nil? || topic["category"].downcase == "uncategorized" mapped[:category] = category_id_from_imported_category_id(topic["category"]) end - if topic["category"].nil? && default_category - mapped[:category] = default_category - end + mapped[:category] = default_category if topic["category"].nil? && default_category mapped[:title] = CGI.unescapeHTML(topic["title"]) mapped[:raw] = process_ning_post_body(topic["description"]) @@ -230,13 +234,9 @@ class ImportScripts::Ning < ImportScripts::Base mapped[:raw] = add_file_attachments(mapped[:raw], topic["fileAttachments"]) end - if topic["photoUrl"] - mapped[:raw] = add_photo(mapped[:raw], topic["photoUrl"]) - end + mapped[:raw] = add_photo(mapped[:raw], topic["photoUrl"]) if topic["photoUrl"] - if topic["embedCode"] - mapped[:raw] = add_video(mapped[:raw], topic["embedCode"]) - end + mapped[:raw] = add_video(mapped[:raw], topic["embedCode"]) if topic["embedCode"] parent_post = create_post(mapped, mapped[:id]) unless parent_post.is_a?(Post) @@ -247,23 +247,24 @@ class ImportScripts::Ning < ImportScripts::Base if topic["comments"].present? topic["comments"].reverse.each do |post| - if post_id_from_imported_post_id(post["id"]) next # already imported this post end raw = process_ning_post_body(post["description"]) - if post["fileAttachments"] - raw = add_file_attachments(raw, post["fileAttachments"]) - end + raw = add_file_attachments(raw, post["fileAttachments"]) if post["fileAttachments"] - new_post = create_post({ - id: post["id"], - topic_id: parent_post.topic_id, - user_id: user_id_from_imported_user_id(post["contributorName"]) || -1, - raw: raw, - created_at: Time.zone.parse(post["createdDate"]) - }, post["id"]) + new_post = + create_post( + { + id: post["id"], + topic_id: parent_post.topic_id, + user_id: user_id_from_imported_user_id(post["contributorName"]) || -1, + raw: raw, + created_at: Time.zone.parse(post["createdDate"]), + }, + post["id"], + ) if new_post.is_a?(Post) posts += 1 @@ -288,11 +289,17 @@ class ImportScripts::Ning < ImportScripts::Base end def attachment_regex - @_attachment_regex ||= Regexp.new(%Q[]*)href="(?:#{ATTACHMENT_PREFIXES.join('|')})\/(?:[^"]+)"(?:[^>]*)>]*)src="([^"]+)"(?:[^>]*)><\/a>]) + @_attachment_regex ||= + Regexp.new( + %Q[]*)href="(?:#{ATTACHMENT_PREFIXES.join("|")})\/(?:[^"]+)"(?:[^>]*)>]*)src="([^"]+)"(?:[^>]*)><\/a>], + ) end def youtube_iframe_regex - @_youtube_iframe_regex ||= Regexp.new(%Q[

]*)src="\/\/www.youtube.com\/embed\/([^"]+)"(?:[^>]*)><\/iframe>(?:[^<]*)<\/p>]) + @_youtube_iframe_regex ||= + Regexp.new( + %Q[

]*)src="\/\/www.youtube.com\/embed\/([^"]+)"(?:[^>]*)><\/iframe>(?:[^<]*)<\/p>], + ) end def process_ning_post_body(arg) @@ -382,15 +389,16 @@ class ImportScripts::Ning < ImportScripts::Base def add_video(arg, embed_code) raw = arg - youtube_regex = Regexp.new(%Q[]*)src="http:\/\/www.youtube.com\/embed\/([^"]+)"(?:[^>]*)><\/iframe>]) + youtube_regex = + Regexp.new( + %Q[]*)src="http:\/\/www.youtube.com\/embed\/([^"]+)"(?:[^>]*)><\/iframe>], + ) raw.gsub!(youtube_regex) do |s| matches = youtube_regex.match(s) video_id = matches[1].split("?").first - if video_id - raw += "\n\nhttps://www.youtube.com/watch?v=#{video_id}\n" - end + raw += "\n\nhttps://www.youtube.com/watch?v=#{video_id}\n" if video_id end raw += "\n" + embed_code + "\n" @@ -398,6 +406,4 @@ class ImportScripts::Ning < ImportScripts::Base end end -if __FILE__ == $0 - ImportScripts::Ning.new.perform -end +ImportScripts::Ning.new.perform if __FILE__ == $0 diff --git a/script/import_scripts/nodebb/mongo.rb b/script/import_scripts/nodebb/mongo.rb index 134704b2b2..696aec4393 100644 --- a/script/import_scripts/nodebb/mongo.rb +++ b/script/import_scripts/nodebb/mongo.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'mongo' +require "mongo" module NodeBB class Mongo @@ -43,8 +43,8 @@ module NodeBB user["joindate"] = timestamp_to_date(user["joindate"]) user["lastonline"] = timestamp_to_date(user["lastonline"]) - user['banned'] = user['banned'].to_s - user['uid'] = user['uid'].to_s + user["banned"] = user["banned"].to_s + user["uid"] = user["uid"].to_s user end @@ -56,17 +56,17 @@ module NodeBB category_keys.each do |category_key| category = mongo.find(_key: "category:#{category_key}").first - category['parentCid'] = category['parentCid'].to_s - category['disabled'] = category['disabled'].to_s - category['cid'] = category['cid'].to_s + category["parentCid"] = category["parentCid"].to_s + category["disabled"] = category["disabled"].to_s + category["cid"] = category["cid"].to_s - categories[category['cid']] = category + categories[category["cid"]] = category end end end def topics(offset = 0, page_size = 2000) - topic_keys = mongo.find(_key: 'topics:tid').skip(offset).limit(page_size).pluck(:value) + topic_keys = mongo.find(_key: "topics:tid").skip(offset).limit(page_size).pluck(:value) topic_keys.map { |topic_key| topic(topic_key) } end @@ -86,11 +86,11 @@ module NodeBB end def topic_count - mongo.find(_key: 'topics:tid').count + mongo.find(_key: "topics:tid").count end def posts(offset = 0, page_size = 2000) - post_keys = mongo.find(_key: 'posts:pid').skip(offset).limit(page_size).pluck(:value) + post_keys = mongo.find(_key: "posts:pid").skip(offset).limit(page_size).pluck(:value) post_keys.map { |post_key| post(post_key) } end @@ -111,7 +111,7 @@ module NodeBB end def post_count - mongo.find(_key: 'posts:pid').count + mongo.find(_key: "posts:pid").count end private diff --git a/script/import_scripts/nodebb/nodebb.rb b/script/import_scripts/nodebb/nodebb.rb index b29f5ee1c6..df575f78d3 100644 --- a/script/import_scripts/nodebb/nodebb.rb +++ b/script/import_scripts/nodebb/nodebb.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require_relative '../base' -require_relative './redis' -require_relative './mongo' +require_relative "../base" +require_relative "./redis" +require_relative "./mongo" class ImportScripts::NodeBB < ImportScripts::Base # CHANGE THESE BEFORE RUNNING THE IMPORTER # ATTACHMENT_DIR needs to be absolute, not relative path - ATTACHMENT_DIR = '/Users/orlando/www/orlando/NodeBB/public/uploads' + ATTACHMENT_DIR = "/Users/orlando/www/orlando/NodeBB/public/uploads" BATCH_SIZE = 2000 def initialize @@ -17,17 +17,13 @@ class ImportScripts::NodeBB < ImportScripts::Base # @client = adapter.new('mongodb://127.0.0.1:27017/nodebb') adapter = NodeBB::Redis - @client = adapter.new( - host: "localhost", - port: "6379", - db: 14 - ) + @client = adapter.new(host: "localhost", port: "6379", db: 14) load_merged_posts end def load_merged_posts - puts 'loading merged posts with topics...' + puts "loading merged posts with topics..." # we keep here the posts that were merged # as topics @@ -35,13 +31,16 @@ class ImportScripts::NodeBB < ImportScripts::Base # { post_id: discourse_post_id } @merged_posts_map = {} - PostCustomField.where(name: 'import_merged_post_id').pluck(:post_id, :value).each do |post_id, import_id| - post = Post.find(post_id) - topic_id = post.topic_id - nodebb_post_id = post.custom_fields['import_merged_post_id'] + PostCustomField + .where(name: "import_merged_post_id") + .pluck(:post_id, :value) + .each do |post_id, import_id| + post = Post.find(post_id) + topic_id = post.topic_id + nodebb_post_id = post.custom_fields["import_merged_post_id"] - @merged_posts_map[nodebb_post_id] = topic_id - end + @merged_posts_map[nodebb_post_id] = topic_id + end end def execute @@ -56,19 +55,14 @@ class ImportScripts::NodeBB < ImportScripts::Base end def import_groups - puts '', 'importing groups' + puts "", "importing groups" groups = @client.groups total_count = groups.count progress_count = 0 start_time = Time.now - create_groups(groups) do |group| - { - id: group["name"], - name: group["slug"] - } - end + create_groups(groups) { |group| { id: group["name"], name: group["slug"] } } end def import_categories @@ -107,15 +101,18 @@ class ImportScripts::NodeBB < ImportScripts::Base name: category["name"], position: category["order"], description: category["description"], - parent_category_id: category_id_from_imported_category_id(category["parentCid"]) + parent_category_id: category_id_from_imported_category_id(category["parentCid"]), } end categories.each do |source_category| - cid = category_id_from_imported_category_id(source_category['cid']) - Permalink.create(url: "/category/#{source_category['slug']}", category_id: cid) rescue nil + cid = category_id_from_imported_category_id(source_category["cid"]) + begin + Permalink.create(url: "/category/#{source_category["slug"]}", category_id: cid) + rescue StandardError + nil + end end - end def import_users @@ -158,12 +155,13 @@ class ImportScripts::NodeBB < ImportScripts::Base bio_raw: user["aboutme"], active: true, custom_fields: { - import_pass: user["password"] + import_pass: user["password"], }, - post_create_action: proc do |u| - import_profile_picture(user, u) - import_profile_background(user, u) - end + post_create_action: + proc do |u| + import_profile_picture(user, u) + import_profile_background(user, u) + end, } end end @@ -204,7 +202,7 @@ class ImportScripts::NodeBB < ImportScripts::Base end # write tmp file - file = Tempfile.new(filename, encoding: 'ascii-8bit') + file = Tempfile.new(filename, encoding: "ascii-8bit") file.write string_io.read file.rewind @@ -230,9 +228,21 @@ class ImportScripts::NodeBB < ImportScripts::Base imported_user.user_avatar.update(custom_upload_id: upload.id) imported_user.update(uploaded_avatar_id: upload.id) ensure - string_io.close rescue nil - file.close rescue nil - file.unlind rescue nil + begin + string_io.close + rescue StandardError + nil + end + begin + file.close + rescue StandardError + nil + end + begin + file.unlind + rescue StandardError + nil + end end def import_profile_background(old_user, imported_user) @@ -264,7 +274,7 @@ class ImportScripts::NodeBB < ImportScripts::Base end # write tmp file - file = Tempfile.new(filename, encoding: 'ascii-8bit') + file = Tempfile.new(filename, encoding: "ascii-8bit") file.write string_io.read file.rewind @@ -288,9 +298,21 @@ class ImportScripts::NodeBB < ImportScripts::Base imported_user.user_profile.upload_profile_background(upload) ensure - string_io.close rescue nil - file.close rescue nil - file.unlink rescue nil + begin + string_io.close + rescue StandardError + nil + end + begin + file.close + rescue StandardError + nil + end + begin + file.unlink + rescue StandardError + nil + end end def add_users_to_groups @@ -305,7 +327,7 @@ class ImportScripts::NodeBB < ImportScripts::Base dgroup = find_group_by_import_id(group["name"]) # do thing if we migrated this group already - next if dgroup.custom_fields['import_users_added'] + next if dgroup.custom_fields["import_users_added"] group_member_ids = group["member_ids"].map { |uid| user_id_from_imported_user_id(uid) } group_owner_ids = group["owner_ids"].map { |uid| user_id_from_imported_user_id(uid) } @@ -320,7 +342,7 @@ class ImportScripts::NodeBB < ImportScripts::Base owners = User.find(group_owner_ids) owners.each { |owner| dgroup.add_owner(owner) } - dgroup.custom_fields['import_users_added'] = true + dgroup.custom_fields["import_users_added"] = true dgroup.save progress_count += 1 @@ -357,12 +379,13 @@ class ImportScripts::NodeBB < ImportScripts::Base created_at: topic["timestamp"], views: topic["viewcount"], closed: topic["locked"] == "1", - post_create_action: proc do |p| - # keep track of this to use in import_posts - p.custom_fields["import_merged_post_id"] = topic["mainPid"] - p.save - @merged_posts_map[topic["mainPid"]] = p.id - end + post_create_action: + proc do |p| + # keep track of this to use in import_posts + p.custom_fields["import_merged_post_id"] = topic["mainPid"] + p.save + @merged_posts_map[topic["mainPid"]] = p.id + end, } data[:pinned_at] = data[:created_at] if topic["pinned"] == "1" @@ -372,7 +395,11 @@ class ImportScripts::NodeBB < ImportScripts::Base topics.each do |import_topic| topic = topic_lookup_from_imported_post_id("t#{import_topic["tid"]}") - Permalink.create(url: "/topic/#{import_topic['slug']}", topic_id: topic[:topic_id]) rescue nil + begin + Permalink.create(url: "/topic/#{import_topic["slug"]}", topic_id: topic[:topic_id]) + rescue StandardError + nil + end end end end @@ -411,21 +438,23 @@ class ImportScripts::NodeBB < ImportScripts::Base topic_id: topic[:topic_id], raw: raw, created_at: post["timestamp"], - post_create_action: proc do |p| - post["upvoted_by"].each do |upvoter_id| - user = User.new - user.id = user_id_from_imported_user_id(upvoter_id) || Discourse::SYSTEM_USER_ID - PostActionCreator.like(user, p) - end - end + post_create_action: + proc do |p| + post["upvoted_by"].each do |upvoter_id| + user = User.new + user.id = user_id_from_imported_user_id(upvoter_id) || Discourse::SYSTEM_USER_ID + PostActionCreator.like(user, p) + end + end, } - if post['toPid'] + if post["toPid"] # Look reply to topic - parent_id = topic_lookup_from_imported_post_id("t#{post['toPid']}").try(:[], :post_number) + parent_id = topic_lookup_from_imported_post_id("t#{post["toPid"]}").try(:[], :post_number) # Look reply post if topic is missing - parent_id ||= topic_lookup_from_imported_post_id("p#{post['toPid']}").try(:[], :post_number) + parent_id ||= + topic_lookup_from_imported_post_id("p#{post["toPid"]}").try(:[], :post_number) if parent_id data[:reply_to_post_number] = parent_id @@ -448,12 +477,12 @@ class ImportScripts::NodeBB < ImportScripts::Base Post.find_each do |post| begin - next if post.custom_fields['import_post_processing'] + next if post.custom_fields["import_post_processing"] new_raw = postprocess_post(post) if new_raw != post.raw post.raw = new_raw - post.custom_fields['import_post_processing'] = true + post.custom_fields["import_post_processing"] = true post.save end ensure @@ -463,7 +492,7 @@ class ImportScripts::NodeBB < ImportScripts::Base end def import_attachments - puts '', 'importing attachments...' + puts "", "importing attachments..." current = 0 max = Post.count @@ -474,7 +503,7 @@ class ImportScripts::NodeBB < ImportScripts::Base print_status(current, max, start_time) new_raw = post.raw.dup - new_raw.gsub!(/\[(.*)\]\((\/assets\/uploads\/files\/.*)\)/) do + new_raw.gsub!(%r{\[(.*)\]\((/assets/uploads/files/.*)\)}) do image_md = Regexp.last_match[0] text, filepath = $1, $2 filepath = filepath.gsub("/assets/uploads", ATTACHMENT_DIR) @@ -493,7 +522,12 @@ class ImportScripts::NodeBB < ImportScripts::Base end if new_raw != post.raw - PostRevisor.new(post).revise!(post.user, { raw: new_raw }, bypass_bump: true, edit_reason: 'Import attachments from NodeBB') + PostRevisor.new(post).revise!( + post.user, + { raw: new_raw }, + bypass_bump: true, + edit_reason: "Import attachments from NodeBB", + ) end end end @@ -502,28 +536,30 @@ class ImportScripts::NodeBB < ImportScripts::Base raw = post.raw # [link to post](/post/:id) - raw = raw.gsub(/\[(.*)\]\(\/post\/(\d+).*\)/) do - text, post_id = $1, $2 + raw = + raw.gsub(%r{\[(.*)\]\(/post/(\d+).*\)}) do + text, post_id = $1, $2 - if topic_lookup = topic_lookup_from_imported_post_id("p#{post_id}") - url = topic_lookup[:url] - "[#{text}](#{url})" - else - "/404" + if topic_lookup = topic_lookup_from_imported_post_id("p#{post_id}") + url = topic_lookup[:url] + "[#{text}](#{url})" + else + "/404" + end end - end # [link to topic](/topic/:id) - raw = raw.gsub(/\[(.*)\]\(\/topic\/(\d+).*\)/) do - text, topic_id = $1, $2 + raw = + raw.gsub(%r{\[(.*)\]\(/topic/(\d+).*\)}) do + text, topic_id = $1, $2 - if topic_lookup = topic_lookup_from_imported_post_id("t#{topic_id}") - url = topic_lookup[:url] - "[#{text}](#{url})" - else - "/404" + if topic_lookup = topic_lookup_from_imported_post_id("t#{topic_id}") + url = topic_lookup[:url] + "[#{text}](#{url})" + else + "/404" + end end - end raw end diff --git a/script/import_scripts/nodebb/redis.rb b/script/import_scripts/nodebb/redis.rb index f8877c5e15..3d1f08a3f3 100644 --- a/script/import_scripts/nodebb/redis.rb +++ b/script/import_scripts/nodebb/redis.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'redis' +require "redis" module NodeBB class Redis @@ -11,7 +11,7 @@ module NodeBB end def groups - group_keys = redis.zrange('groups:visible:createtime', 0, -1) + group_keys = redis.zrange("groups:visible:createtime", 0, -1) group_keys.map { |group_key| group(group_key) } end @@ -26,7 +26,7 @@ module NodeBB end def users - user_keys = redis.zrange('users:joindate', 0, -1) + user_keys = redis.zrange("users:joindate", 0, -1) user_keys.map { |user_key| user(user_key) } end @@ -41,13 +41,13 @@ module NodeBB end def categories - category_keys = redis.zrange('categories:cid', 0, -1) + category_keys = redis.zrange("categories:cid", 0, -1) {}.tap do |categories| category_keys.each do |category_key| category = redis.hgetall("category:#{category_key}") - categories[category['cid']] = category + categories[category["cid"]] = category end end end @@ -59,7 +59,7 @@ module NodeBB from = offset to = page_size + offset - topic_keys = redis.zrange('topics:tid', from, to) + topic_keys = redis.zrange("topics:tid", from, to) topic_keys.map { |topic_key| topic(topic_key) } end @@ -75,7 +75,7 @@ module NodeBB end def topic_count - redis.zcard('topics:tid') + redis.zcard("topics:tid") end def posts(offset = 0, page_size = 2000) @@ -85,7 +85,7 @@ module NodeBB from = offset to = page_size + offset - post_keys = redis.zrange('posts:pid', from, to) + post_keys = redis.zrange("posts:pid", from, to) post_keys.map { |post_key| post(post_key) } end @@ -99,7 +99,7 @@ module NodeBB end def post_count - redis.zcard('posts:pid') + redis.zcard("posts:pid") end private diff --git a/script/import_scripts/phorum.rb b/script/import_scripts/phorum.rb index dc2639933e..f03db50ea4 100644 --- a/script/import_scripts/phorum.rb +++ b/script/import_scripts/phorum.rb @@ -5,7 +5,6 @@ require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::Phorum < ImportScripts::Base - PHORUM_DB = "piwik" TABLE_PREFIX = "pw_" BATCH_SIZE = 1000 @@ -13,12 +12,13 @@ class ImportScripts::Phorum < ImportScripts::Base def initialize super - @client = Mysql2::Client.new( - host: "localhost", - username: "root", - password: "pa$$word", - database: PHORUM_DB - ) + @client = + Mysql2::Client.new( + host: "localhost", + username: "root", + password: "pa$$word", + database: PHORUM_DB, + ) end def execute @@ -29,30 +29,34 @@ class ImportScripts::Phorum < ImportScripts::Base end def import_users - puts '', "creating users" + puts "", "creating users" - total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}users;").first['count'] + total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}users;").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query( - "SELECT user_id id, username, TRIM(email) AS email, username name, date_added created_at, + results = + mysql_query( + "SELECT user_id id, username, TRIM(email) AS email, username name, date_added created_at, date_last_active last_seen_at, admin FROM #{TABLE_PREFIX}users WHERE #{TABLE_PREFIX}users.active = 1 LIMIT #{BATCH_SIZE} - OFFSET #{offset};") + OFFSET #{offset};", + ) break if results.size < 1 create_users(results, total: total_count, offset: offset) do |user| - next if user['username'].blank? - { id: user['id'], - email: user['email'], - username: user['username'], - name: user['name'], - created_at: Time.zone.at(user['created_at']), - last_seen_at: Time.zone.at(user['last_seen_at']), - admin: user['admin'] == 1 } + next if user["username"].blank? + { + id: user["id"], + email: user["email"], + username: user["username"], + name: user["name"], + created_at: Time.zone.at(user["created_at"]), + last_seen_at: Time.zone.at(user["last_seen_at"]), + admin: user["admin"] == 1, + } end end end @@ -60,19 +64,18 @@ class ImportScripts::Phorum < ImportScripts::Base def import_categories puts "", "importing categories..." - categories = mysql_query(" + categories = + mysql_query( + " SELECT forum_id id, name, description, active FROM #{TABLE_PREFIX}forums ORDER BY forum_id ASC - ").to_a + ", + ).to_a create_categories(categories) do |category| - next if category['active'] == 0 - { - id: category['id'], - name: category["name"], - description: category["description"] - } + next if category["active"] == 0 + { id: category["id"], name: category["name"], description: category["description"] } end # uncomment below lines to create permalink @@ -87,7 +90,9 @@ class ImportScripts::Phorum < ImportScripts::Base total_count = mysql_query("SELECT count(*) count from #{TABLE_PREFIX}messages").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query(" + results = + mysql_query( + " SELECT m.message_id id, m.parent_id, m.forum_id category_id, @@ -100,7 +105,8 @@ class ImportScripts::Phorum < ImportScripts::Base ORDER BY m.datestamp LIMIT #{BATCH_SIZE} OFFSET #{offset}; - ").to_a + ", + ).to_a break if results.size < 1 @@ -108,20 +114,20 @@ class ImportScripts::Phorum < ImportScripts::Base skip = false mapped = {} - mapped[:id] = m['id'] - mapped[:user_id] = user_id_from_imported_user_id(m['user_id']) || -1 - mapped[:raw] = process_raw_post(m['raw'], m['id']) - mapped[:created_at] = Time.zone.at(m['created_at']) + mapped[:id] = m["id"] + mapped[:user_id] = user_id_from_imported_user_id(m["user_id"]) || -1 + mapped[:raw] = process_raw_post(m["raw"], m["id"]) + mapped[:created_at] = Time.zone.at(m["created_at"]) - if m['parent_id'] == 0 - mapped[:category] = category_id_from_imported_category_id(m['category_id'].to_i) - mapped[:title] = CGI.unescapeHTML(m['title']) + if m["parent_id"] == 0 + mapped[:category] = category_id_from_imported_category_id(m["category_id"].to_i) + mapped[:title] = CGI.unescapeHTML(m["title"]) else - parent = topic_lookup_from_imported_post_id(m['parent_id']) + parent = topic_lookup_from_imported_post_id(m["parent_id"]) if parent mapped[:topic_id] = parent[:topic_id] else - puts "Parent post #{m['parent_id']} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" + puts "Parent post #{m["parent_id"]} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" skip = true end end @@ -137,25 +143,24 @@ class ImportScripts::Phorum < ImportScripts::Base # end # end end - end def process_raw_post(raw, import_id) s = raw.dup # :) is encoded as :) - s.gsub!(/]+) \/>/, '\1') + s.gsub!(%r{]+) />}, '\1') # Some links look like this: http://www.onegameamonth.com - s.gsub!(/(.+)<\/a>/, '[\2](\1)') + s.gsub!(%r{(.+)}, '[\2](\1)') # Many phpbb bbcode tags have a hash attached to them. Examples: # [url=https://google.com:1qh1i7ky]click here[/url:1qh1i7ky] # [quote="cybereality":b0wtlzex]Some text.[/quote:b0wtlzex] - s.gsub!(/:(?:\w{8})\]/, ']') + s.gsub!(/:(?:\w{8})\]/, "]") # Remove mybb video tags. - s.gsub!(/(^\[video=.*?\])|(\[\/video\]$)/, '') + s.gsub!(%r{(^\[video=.*?\])|(\[/video\]$)}, "") s = CGI.unescapeHTML(s) @@ -163,50 +168,54 @@ class ImportScripts::Phorum < ImportScripts::Base # [http://answers.yahoo.com/question/index ... 223AAkkPli](http://answers.yahoo.com/question/index?qid=20070920134223AAkkPli) # # Work around it for now: - s.gsub!(/\[http(s)?:\/\/(www\.)?/, '[') + s.gsub!(%r{\[http(s)?://(www\.)?}, "[") # [QUOTE]...[/QUOTE] - s.gsub!(/\[quote\](.+?)\[\/quote\]/im) { "\n> #{$1}\n" } + s.gsub!(%r{\[quote\](.+?)\[/quote\]}im) { "\n> #{$1}\n" } # [URL=...]...[/URL] - s.gsub!(/\[url="?(.+?)"?\](.+)\[\/url\]/i) { "[#{$2}](#{$1})" } + s.gsub!(%r{\[url="?(.+?)"?\](.+)\[/url\]}i) { "[#{$2}](#{$1})" } # [IMG]...[/IMG] - s.gsub!(/\[\/?img\]/i, "") + s.gsub!(%r{\[/?img\]}i, "") # convert list tags to ul and list=1 tags to ol # (basically, we're only missing list=a here...) - s.gsub!(/\[list\](.*?)\[\/list\]/m, '[ul]\1[/ul]') - s.gsub!(/\[list=1\](.*?)\[\/list\]/m, '[ol]\1[/ol]') + s.gsub!(%r{\[list\](.*?)\[/list\]}m, '[ul]\1[/ul]') + s.gsub!(%r{\[list=1\](.*?)\[/list\]}m, '[ol]\1[/ol]') # convert *-tags to li-tags so bbcode-to-md can do its magic on phpBB's lists: s.gsub!(/\[\*\](.*?)\n/, '[li]\1[/li]') # [CODE]...[/CODE] - s.gsub!(/\[\/?code\]/i, "\n```\n") + s.gsub!(%r{\[/?code\]}i, "\n```\n") # [HIGHLIGHT]...[/HIGHLIGHT] - s.gsub!(/\[\/?highlight\]/i, "\n```\n") + s.gsub!(%r{\[/?highlight\]}i, "\n```\n") # [YOUTUBE][/YOUTUBE] - s.gsub!(/\[youtube\](.+?)\[\/youtube\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } + s.gsub!(%r{\[youtube\](.+?)\[/youtube\]}i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } # [youtube=425,350]id[/youtube] - s.gsub!(/\[youtube="?(.+?)"?\](.+)\[\/youtube\]/i) { "\nhttps://www.youtube.com/watch?v=#{$2}\n" } + s.gsub!(%r{\[youtube="?(.+?)"?\](.+)\[/youtube\]}i) do + "\nhttps://www.youtube.com/watch?v=#{$2}\n" + end # [MEDIA=youtube]id[/MEDIA] - s.gsub!(/\[MEDIA=youtube\](.+?)\[\/MEDIA\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } + s.gsub!(%r{\[MEDIA=youtube\](.+?)\[/MEDIA\]}i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } # [ame="youtube_link"]title[/ame] - s.gsub!(/\[ame="?(.+?)"?\](.+)\[\/ame\]/i) { "\n#{$1}\n" } + s.gsub!(%r{\[ame="?(.+?)"?\](.+)\[/ame\]}i) { "\n#{$1}\n" } # [VIDEO=youtube;]...[/VIDEO] - s.gsub!(/\[video=youtube;([^\]]+)\].*?\[\/video\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } + s.gsub!(%r{\[video=youtube;([^\]]+)\].*?\[/video\]}i) do + "\nhttps://www.youtube.com/watch?v=#{$1}\n" + end # [USER=706]@username[/USER] - s.gsub!(/\[user="?(.+?)"?\](.+)\[\/user\]/i) { $2 } + s.gsub!(%r{\[user="?(.+?)"?\](.+)\[/user\]}i) { $2 } # Remove the color tag s.gsub!(/\[color=[#a-z0-9]+\]/i, "") - s.gsub!(/\[\/color\]/i, "") + s.gsub!(%r{\[/color\]}i, "") s.gsub!(/\[hr\]/i, "


") @@ -221,7 +230,7 @@ class ImportScripts::Phorum < ImportScripts::Base end def import_attachments - puts '', 'importing attachments...' + puts "", "importing attachments..." uploads = mysql_query <<-SQL SELECT message_id, filename, FROM_BASE64(file_data) AS file_data, file_id @@ -234,26 +243,23 @@ class ImportScripts::Phorum < ImportScripts::Base total_count = uploads.count uploads.each do |upload| - # puts "*** processing file #{upload['file_id']}" - post_id = post_id_from_imported_post_id(upload['message_id']) + post_id = post_id_from_imported_post_id(upload["message_id"]) if post_id.nil? - puts "Post #{upload['message_id']} for attachment #{upload['file_id']} not found" + puts "Post #{upload["message_id"]} for attachment #{upload["file_id"]} not found" next end post = Post.find(post_id) - real_filename = upload['filename'] - real_filename.prepend SecureRandom.hex if real_filename[0] == '.' + real_filename = upload["filename"] + real_filename.prepend SecureRandom.hex if real_filename[0] == "." - tmpfile = 'attach_' + upload['file_id'].to_s - filename = File.join('/tmp/', tmpfile) - File.open(filename, 'wb') { |f| - f.write(upload['file_data']) - } + tmpfile = "attach_" + upload["file_id"].to_s + filename = File.join("/tmp/", tmpfile) + File.open(filename, "wb") { |f| f.write(upload["file_data"]) } upl_obj = create_upload(post.user.id, filename, real_filename) @@ -265,16 +271,16 @@ class ImportScripts::Phorum < ImportScripts::Base post.raw += "\n\n#{html}\n\n" post.save! if PostUpload.where(post: post, upload: upl_obj).exists? - puts "skipping creating uploaded for previously uploaded file #{upload['file_id']}" + puts "skipping creating uploaded for previously uploaded file #{upload["file_id"]}" else PostUpload.create!(post: post, upload: upl_obj) end # PostUpload.create!(post: post, upload: upl_obj) unless PostUpload.where(post: post, upload: upl_obj).exists? else - puts "Skipping attachment #{upload['file_id']}" + puts "Skipping attachment #{upload["file_id"]}" end else - puts "Failed to upload attachment #{upload['file_id']}" + puts "Failed to upload attachment #{upload["file_id"]}" exit end @@ -282,7 +288,6 @@ class ImportScripts::Phorum < ImportScripts::Base print_status(current_count, total_count) end end - end ImportScripts::Phorum.new.perform diff --git a/script/import_scripts/phpbb3.rb b/script/import_scripts/phpbb3.rb index fb1807f911..2c5ae75e44 100644 --- a/script/import_scripts/phpbb3.rb +++ b/script/import_scripts/phpbb3.rb @@ -4,32 +4,34 @@ # Documentation: https://meta.discourse.org/t/importing-from-phpbb3/30810 if ARGV.length != 1 || !File.exist?(ARGV[0]) - STDERR.puts '', 'Usage of phpBB3 importer:', 'bundle exec ruby phpbb3.rb ' - STDERR.puts '', "Use the settings file from #{File.expand_path('phpbb3/settings.yml', File.dirname(__FILE__))} as an example." - STDERR.puts '', 'Still having problems? Take a look at https://meta.discourse.org/t/importing-from-phpbb3/30810' + STDERR.puts "", "Usage of phpBB3 importer:", "bundle exec ruby phpbb3.rb " + STDERR.puts "", + "Use the settings file from #{File.expand_path("phpbb3/settings.yml", File.dirname(__FILE__))} as an example." + STDERR.puts "", + "Still having problems? Take a look at https://meta.discourse.org/t/importing-from-phpbb3/30810" exit 1 end module ImportScripts module PhpBB3 - require_relative 'phpbb3/support/settings' - require_relative 'phpbb3/database/database' + require_relative "phpbb3/support/settings" + require_relative "phpbb3/database/database" @settings = Settings.load(ARGV[0]) # We need to load the gem files for ruby-bbcode-to-md and the database adapter # (e.g. mysql2) before bundler gets initialized by the base importer. # Otherwise we get an error since those gems are not always in the Gemfile. - require 'ruby-bbcode-to-md' if @settings.use_bbcode_to_md + require "ruby-bbcode-to-md" if @settings.use_bbcode_to_md begin @database = Database.create(@settings.database) rescue UnsupportedVersionError => error - STDERR.puts '', error.message + STDERR.puts "", error.message exit 1 end - require_relative 'phpbb3/importer' + require_relative "phpbb3/importer" Importer.new(@settings, @database).perform end end diff --git a/script/import_scripts/phpbb3/database/database.rb b/script/import_scripts/phpbb3/database/database.rb index 240003edae..c31b6bb620 100644 --- a/script/import_scripts/phpbb3/database/database.rb +++ b/script/import_scripts/phpbb3/database/database.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'mysql2' +require "mysql2" module ImportScripts::PhpBB3 class Database @@ -19,11 +19,11 @@ module ImportScripts::PhpBB3 def create_database version = get_phpbb_version - if version.start_with?('3.0') - require_relative 'database_3_0' + if version.start_with?("3.0") + require_relative "database_3_0" Database_3_0.new(@database_client, @database_settings) - elsif version.start_with?('3.1') || version.start_with?('3.2') || version.start_with?('3.3') - require_relative 'database_3_1' + elsif version.start_with?("3.1") || version.start_with?("3.2") || version.start_with?("3.3") + require_relative "database_3_1" Database_3_1.new(@database_client, @database_settings) else raise UnsupportedVersionError, <<~TEXT @@ -42,7 +42,7 @@ module ImportScripts::PhpBB3 username: @database_settings.username, password: @database_settings.password, database: @database_settings.schema, - reconnect: true + reconnect: true, ) end diff --git a/script/import_scripts/phpbb3/database/database_3_0.rb b/script/import_scripts/phpbb3/database/database_3_0.rb index 49f042a6e7..f45b38824a 100644 --- a/script/import_scripts/phpbb3/database/database_3_0.rb +++ b/script/import_scripts/phpbb3/database/database_3_0.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative 'database_base' -require_relative '../support/constants' +require_relative "database_base" +require_relative "../support/constants" module ImportScripts::PhpBB3 class Database_3_0 < DatabaseBase diff --git a/script/import_scripts/phpbb3/database/database_3_1.rb b/script/import_scripts/phpbb3/database/database_3_1.rb index ee666bbbc0..3255e484b1 100644 --- a/script/import_scripts/phpbb3/database/database_3_1.rb +++ b/script/import_scripts/phpbb3/database/database_3_1.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative 'database_3_0' -require_relative '../support/constants' +require_relative "database_3_0" +require_relative "../support/constants" module ImportScripts::PhpBB3 class Database_3_1 < Database_3_0 @@ -32,14 +32,15 @@ module ImportScripts::PhpBB3 private def profile_fields_query(profile_fields) - @profile_fields_query ||= begin - if profile_fields.present? - columns = profile_fields.map { |field| "pf_#{field[:phpbb_field_name]}" } - ", #{columns.join(', ')}" - else - "" + @profile_fields_query ||= + begin + if profile_fields.present? + columns = profile_fields.map { |field| "pf_#{field[:phpbb_field_name]}" } + ", #{columns.join(", ")}" + else + "" + end end - end end end end diff --git a/script/import_scripts/phpbb3/database/database_base.rb b/script/import_scripts/phpbb3/database/database_base.rb index a51bcde3a5..4419d4e78c 100644 --- a/script/import_scripts/phpbb3/database/database_base.rb +++ b/script/import_scripts/phpbb3/database/database_base.rb @@ -39,9 +39,7 @@ module ImportScripts::PhpBB3 def find_last_row(rows) last_index = rows.size - 1 - rows.each_with_index do |row, index| - return row if index == last_index - end + rows.each_with_index { |row, index| return row if index == last_index } nil end diff --git a/script/import_scripts/phpbb3/importer.rb b/script/import_scripts/phpbb3/importer.rb index b8b84e29e6..a4f64f82c4 100644 --- a/script/import_scripts/phpbb3/importer.rb +++ b/script/import_scripts/phpbb3/importer.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require_relative '../base' -require_relative 'support/settings' -require_relative 'database/database' -require_relative 'importers/importer_factory' +require_relative "../base" +require_relative "support/settings" +require_relative "database/database" +require_relative "importers/importer_factory" module ImportScripts::PhpBB3 class Importer < ImportScripts::Base @@ -25,7 +25,7 @@ module ImportScripts::PhpBB3 protected def execute - puts '', "importing from phpBB #{@php_config[:phpbb_version]}" + puts "", "importing from phpBB #{@php_config[:phpbb_version]}" SiteSetting.tagging_enabled = true if @settings.tag_mappings.present? @@ -55,8 +55,14 @@ module ImportScripts::PhpBB3 settings[:max_attachment_size_kb] = [max_file_size_kb, SiteSetting.max_attachment_size_kb].max # temporarily disable validation since we want to import all existing images and attachments - SiteSetting.type_supervisor.load_setting(:max_image_size_kb, max: settings[:max_image_size_kb]) - SiteSetting.type_supervisor.load_setting(:max_attachment_size_kb, max: settings[:max_attachment_size_kb]) + SiteSetting.type_supervisor.load_setting( + :max_image_size_kb, + max: settings[:max_image_size_kb], + ) + SiteSetting.type_supervisor.load_setting( + :max_attachment_size_kb, + max: settings[:max_attachment_size_kb], + ) settings end @@ -66,7 +72,7 @@ module ImportScripts::PhpBB3 end def import_users - puts '', 'creating users' + puts "", "creating users" total_count = @database.count_users importer = @importers.user_importer last_user_id = 0 @@ -88,10 +94,10 @@ module ImportScripts::PhpBB3 end def import_anonymous_users - puts '', 'creating anonymous users' + puts "", "creating anonymous users" total_count = @database.count_anonymous_users importer = @importers.user_importer - last_username = '' + last_username = "" batches do |offset| rows, last_username = @database.fetch_anonymous_users(last_username) @@ -109,26 +115,34 @@ module ImportScripts::PhpBB3 end def import_groups - puts '', 'creating groups' + puts "", "creating groups" rows = @database.fetch_groups create_groups(rows) do |row| begin next if row[:group_type] == 3 - group_name = if @settings.site_name.present? - "#{@settings.site_name}_#{row[:group_name]}" - else - row[:group_name] - end[0..19].gsub(/[^a-zA-Z0-9\-_. ]/, '_') + group_name = + if @settings.site_name.present? + "#{@settings.site_name}_#{row[:group_name]}" + else + row[:group_name] + end[ + 0..19 + ].gsub(/[^a-zA-Z0-9\-_. ]/, "_") - bio_raw = @importers.text_processor.process_raw_text(row[:group_desc]) rescue row[:group_desc] + bio_raw = + begin + @importers.text_processor.process_raw_text(row[:group_desc]) + rescue StandardError + row[:group_desc] + end { id: @settings.prefix(row[:group_id]), name: group_name, full_name: row[:group_name], - bio_raw: bio_raw + bio_raw: bio_raw, } rescue => e log_error("Failed to map group with ID #{row[:group_id]}", e) @@ -137,7 +151,7 @@ module ImportScripts::PhpBB3 end def import_user_groups - puts '', 'creating user groups' + puts "", "creating user groups" rows = @database.fetch_group_users rows.each do |row| @@ -147,7 +161,11 @@ module ImportScripts::PhpBB3 user_id = @lookup.user_id_from_imported_user_id(@settings.prefix(row[:user_id])) begin - GroupUser.find_or_create_by(user_id: user_id, group_id: group_id, owner: row[:group_leader]) + GroupUser.find_or_create_by( + user_id: user_id, + group_id: group_id, + owner: row[:group_leader], + ) rescue => e log_error("Failed to add user #{row[:user_id]} to group #{row[:group_id]}", e) end @@ -155,7 +173,7 @@ module ImportScripts::PhpBB3 end def import_new_categories - puts '', 'creating new categories' + puts "", "creating new categories" create_categories(@settings.new_categories) do |row| next if row == "SKIP" @@ -163,13 +181,14 @@ module ImportScripts::PhpBB3 { id: @settings.prefix(row[:forum_id]), name: row[:name], - parent_category_id: @lookup.category_id_from_imported_category_id(@settings.prefix(row[:parent_id])) + parent_category_id: + @lookup.category_id_from_imported_category_id(@settings.prefix(row[:parent_id])), } end end def import_categories - puts '', 'creating categories' + puts "", "creating categories" rows = @database.fetch_categories importer = @importers.category_importer @@ -181,7 +200,7 @@ module ImportScripts::PhpBB3 end def import_posts - puts '', 'creating topics and posts' + puts "", "creating topics and posts" total_count = @database.count_posts importer = @importers.post_importer last_post_id = 0 @@ -202,7 +221,7 @@ module ImportScripts::PhpBB3 end def import_private_messages - puts '', 'creating private messages' + puts "", "creating private messages" total_count = @database.count_messages importer = @importers.message_importer last_msg_id = 0 @@ -223,7 +242,7 @@ module ImportScripts::PhpBB3 end def import_bookmarks - puts '', 'creating bookmarks' + puts "", "creating bookmarks" total_count = @database.count_bookmarks importer = @importers.bookmark_importer last_user_id = last_topic_id = 0 @@ -243,7 +262,7 @@ module ImportScripts::PhpBB3 end def import_likes - puts '', 'importing likes' + puts "", "importing likes" total_count = @database.count_likes last_post_id = last_user_id = 0 @@ -255,7 +274,7 @@ module ImportScripts::PhpBB3 { post_id: @settings.prefix(row[:post_id]), user_id: @settings.prefix(row[:user_id]), - created_at: Time.zone.at(row[:thanks_time]) + created_at: Time.zone.at(row[:thanks_time]), } end end diff --git a/script/import_scripts/phpbb3/importers/avatar_importer.rb b/script/import_scripts/phpbb3/importers/avatar_importer.rb index bb72572c0b..4e6e3b13bb 100644 --- a/script/import_scripts/phpbb3/importers/avatar_importer.rb +++ b/script/import_scripts/phpbb3/importers/avatar_importer.rb @@ -49,12 +49,12 @@ module ImportScripts::PhpBB3 def get_avatar_path(avatar_type, filename) case avatar_type - when Constants::AVATAR_TYPE_UPLOADED, Constants::AVATAR_TYPE_STRING_UPLOADED then - filename.gsub!(/_[0-9]+\./, '.') # we need 1337.jpg, not 1337_2983745.jpg - get_uploaded_path(filename) - when Constants::AVATAR_TYPE_GALLERY, Constants::AVATAR_TYPE_STRING_GALLERY then + when Constants::AVATAR_TYPE_UPLOADED, Constants::AVATAR_TYPE_STRING_UPLOADED + filename.gsub!(/_[0-9]+\./, ".") # we need 1337.jpg, not 1337_2983745.jpg + get_uploaded_path(filename) + when Constants::AVATAR_TYPE_GALLERY, Constants::AVATAR_TYPE_STRING_GALLERY get_gallery_path(filename) - when Constants::AVATAR_TYPE_REMOTE, Constants::AVATAR_TYPE_STRING_REMOTE then + when Constants::AVATAR_TYPE_REMOTE, Constants::AVATAR_TYPE_STRING_REMOTE download_avatar(filename) else puts "Invalid avatar type #{avatar_type}. Skipping..." @@ -67,12 +67,13 @@ module ImportScripts::PhpBB3 max_image_size_kb = SiteSetting.max_image_size_kb.kilobytes begin - avatar_file = FileHelper.download( - url, - max_file_size: max_image_size_kb, - tmp_file_name: 'discourse-avatar', - follow_redirect: true - ) + avatar_file = + FileHelper.download( + url, + max_file_size: max_image_size_kb, + tmp_file_name: "discourse-avatar", + follow_redirect: true, + ) rescue StandardError => err warn "Error downloading avatar: #{err.message}. Skipping..." return nil @@ -100,11 +101,11 @@ module ImportScripts::PhpBB3 def is_allowed_avatar_type?(avatar_type) case avatar_type - when Constants::AVATAR_TYPE_UPLOADED, Constants::AVATAR_TYPE_STRING_UPLOADED then + when Constants::AVATAR_TYPE_UPLOADED, Constants::AVATAR_TYPE_STRING_UPLOADED @settings.import_uploaded_avatars - when Constants::AVATAR_TYPE_REMOTE, Constants::AVATAR_TYPE_STRING_REMOTE then + when Constants::AVATAR_TYPE_REMOTE, Constants::AVATAR_TYPE_STRING_REMOTE @settings.import_remote_avatars - when Constants::AVATAR_TYPE_GALLERY, Constants::AVATAR_TYPE_STRING_GALLERY then + when Constants::AVATAR_TYPE_GALLERY, Constants::AVATAR_TYPE_STRING_GALLERY @settings.import_gallery_avatars else false diff --git a/script/import_scripts/phpbb3/importers/bookmark_importer.rb b/script/import_scripts/phpbb3/importers/bookmark_importer.rb index 784e6c7476..6dd2a793d8 100644 --- a/script/import_scripts/phpbb3/importers/bookmark_importer.rb +++ b/script/import_scripts/phpbb3/importers/bookmark_importer.rb @@ -9,7 +9,7 @@ module ImportScripts::PhpBB3 def map_bookmark(row) { user_id: @settings.prefix(row[:user_id]), - post_id: @settings.prefix(row[:topic_first_post_id]) + post_id: @settings.prefix(row[:topic_first_post_id]), } end end diff --git a/script/import_scripts/phpbb3/importers/category_importer.rb b/script/import_scripts/phpbb3/importers/category_importer.rb index 6e5725b97e..e0b95bc79b 100644 --- a/script/import_scripts/phpbb3/importers/category_importer.rb +++ b/script/import_scripts/phpbb3/importers/category_importer.rb @@ -23,11 +23,13 @@ module ImportScripts::PhpBB3 { id: @settings.prefix(row[:forum_id]), name: CGI.unescapeHTML(row[:forum_name]), - parent_category_id: @lookup.category_id_from_imported_category_id(@settings.prefix(row[:parent_id])), - post_create_action: proc do |category| - update_category_description(category, row) - @permalink_importer.create_for_category(category, row[:forum_id]) # skip @settings.prefix because ID is used in permalink generation - end + parent_category_id: + @lookup.category_id_from_imported_category_id(@settings.prefix(row[:parent_id])), + post_create_action: + proc do |category| + update_category_description(category, row) + @permalink_importer.create_for_category(category, row[:forum_id]) # skip @settings.prefix because ID is used in permalink generation + end, } end @@ -51,7 +53,16 @@ module ImportScripts::PhpBB3 end if row[:forum_desc].present? - changes = { raw: (@text_processor.process_raw_text(row[:forum_desc]) rescue row[:forum_desc]) } + changes = { + raw: + ( + begin + @text_processor.process_raw_text(row[:forum_desc]) + rescue StandardError + row[:forum_desc] + end + ), + } opts = { revised_at: post.created_at, bypass_bump: true } post.revise(Discourse.system_user, changes, opts) end diff --git a/script/import_scripts/phpbb3/importers/importer_factory.rb b/script/import_scripts/phpbb3/importers/importer_factory.rb index b02cb92ff0..d7a3fe9c9f 100644 --- a/script/import_scripts/phpbb3/importers/importer_factory.rb +++ b/script/import_scripts/phpbb3/importers/importer_factory.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true -require_relative 'attachment_importer' -require_relative 'avatar_importer' -require_relative 'bookmark_importer' -require_relative 'category_importer' -require_relative 'message_importer' -require_relative 'poll_importer' -require_relative 'post_importer' -require_relative 'permalink_importer' -require_relative 'user_importer' -require_relative '../support/smiley_processor' -require_relative '../support/text_processor' +require_relative "attachment_importer" +require_relative "avatar_importer" +require_relative "bookmark_importer" +require_relative "category_importer" +require_relative "message_importer" +require_relative "poll_importer" +require_relative "post_importer" +require_relative "permalink_importer" +require_relative "user_importer" +require_relative "../support/smiley_processor" +require_relative "../support/text_processor" module ImportScripts::PhpBB3 class ImporterFactory @@ -36,7 +36,14 @@ module ImportScripts::PhpBB3 end def post_importer - PostImporter.new(@lookup, text_processor, attachment_importer, poll_importer, permalink_importer, @settings) + PostImporter.new( + @lookup, + text_processor, + attachment_importer, + poll_importer, + permalink_importer, + @settings, + ) end def message_importer @@ -64,7 +71,8 @@ module ImportScripts::PhpBB3 end def text_processor - @text_processor ||= TextProcessor.new(@lookup, @database, smiley_processor, @settings, @phpbb_config) + @text_processor ||= + TextProcessor.new(@lookup, @database, smiley_processor, @settings, @phpbb_config) end def smiley_processor diff --git a/script/import_scripts/phpbb3/importers/message_importer.rb b/script/import_scripts/phpbb3/importers/message_importer.rb index 65c5874bda..c71795b328 100644 --- a/script/import_scripts/phpbb3/importers/message_importer.rb +++ b/script/import_scripts/phpbb3/importers/message_importer.rb @@ -20,14 +20,16 @@ module ImportScripts::PhpBB3 end def map_message(row) - user_id = @lookup.user_id_from_imported_user_id(@settings.prefix(row[:author_id])) || Discourse.system_user.id + user_id = + @lookup.user_id_from_imported_user_id(@settings.prefix(row[:author_id])) || + Discourse.system_user.id attachments = import_attachments(row, user_id) mapped = { id: get_import_id(row[:msg_id]), user_id: user_id, created_at: Time.zone.at(row[:message_time]), - raw: @text_processor.process_private_msg(row[:message_text], attachments) + raw: @text_processor.process_private_msg(row[:message_text], attachments), } root_user_ids = sorted_user_ids(row[:root_author_id], row[:root_to_address]) @@ -43,7 +45,7 @@ module ImportScripts::PhpBB3 protected - RE_PREFIX = 're: ' + RE_PREFIX = "re: " def import_attachments(row, user_id) if @settings.import_attachments && row[:attachment_count] > 0 @@ -55,7 +57,7 @@ module ImportScripts::PhpBB3 mapped[:title] = get_topic_title(row) mapped[:archetype] = Archetype.private_message mapped[:target_usernames] = get_recipient_usernames(row) - mapped[:custom_fields] = { import_user_ids: current_user_ids.join(',') } + mapped[:custom_fields] = { import_user_ids: current_user_ids.join(",") } if mapped[:target_usernames].empty? puts "Private message without recipients. Skipping #{row[:msg_id]}: #{row[:message_subject][0..40]}" @@ -75,9 +77,9 @@ module ImportScripts::PhpBB3 # to_address looks like this: "u_91:u_1234:g_200" # If there is a "u_" prefix, the prefix is discarded and the rest is a user_id - user_ids = to_address.split(':') + user_ids = to_address.split(":") user_ids.uniq! - user_ids.map! { |u| u[2..-1].to_i if u[0..1] == 'u_' }.compact + user_ids.map! { |u| u[2..-1].to_i if u[0..1] == "u_" }.compact end def get_recipient_group_ids(to_address) @@ -85,16 +87,19 @@ module ImportScripts::PhpBB3 # to_address looks like this: "u_91:u_1234:g_200" # If there is a "g_" prefix, the prefix is discarded and the rest is a group_id - group_ids = to_address.split(':') + group_ids = to_address.split(":") group_ids.uniq! - group_ids.map! { |g| g[2..-1].to_i if g[0..1] == 'g_' }.compact + group_ids.map! { |g| g[2..-1].to_i if g[0..1] == "g_" }.compact end def get_recipient_usernames(row) import_user_ids = get_recipient_user_ids(row[:to_address]) - usernames = import_user_ids.map do |import_user_id| - @lookup.find_user_by_import_id(@settings.prefix(import_user_id)).try(:username) - end.compact + usernames = + import_user_ids + .map do |import_user_id| + @lookup.find_user_by_import_id(@settings.prefix(import_user_id)).try(:username) + end + .compact import_group_ids = get_recipient_group_ids(row[:to_address]) import_group_ids.each do |import_group_id| @@ -142,13 +147,19 @@ module ImportScripts::PhpBB3 topic_titles = [topic_title] topic_titles << topic_title[RE_PREFIX.length..-1] if topic_title.start_with?(RE_PREFIX) - Post.select(:topic_id) + Post + .select(:topic_id) .joins(:topic) .joins(:_custom_fields) - .where(["LOWER(topics.title) IN (:titles) AND post_custom_fields.name = 'import_user_ids' AND post_custom_fields.value = :user_ids", - { titles: topic_titles, user_ids: current_user_ids.join(',') }]) - .order('topics.created_at DESC') - .first.try(:topic_id) + .where( + [ + "LOWER(topics.title) IN (:titles) AND post_custom_fields.name = 'import_user_ids' AND post_custom_fields.value = :user_ids", + { titles: topic_titles, user_ids: current_user_ids.join(",") }, + ], + ) + .order("topics.created_at DESC") + .first + .try(:topic_id) end end end diff --git a/script/import_scripts/phpbb3/importers/permalink_importer.rb b/script/import_scripts/phpbb3/importers/permalink_importer.rb index 051604ba87..5dcd9ffe60 100644 --- a/script/import_scripts/phpbb3/importers/permalink_importer.rb +++ b/script/import_scripts/phpbb3/importers/permalink_importer.rb @@ -13,13 +13,15 @@ module ImportScripts::PhpBB3 def change_site_settings normalizations = SiteSetting.permalink_normalizations - normalizations = normalizations.blank? ? [] : normalizations.split('|') + normalizations = normalizations.blank? ? [] : normalizations.split("|") - add_normalization(normalizations, CATEGORY_LINK_NORMALIZATION) if @settings.create_category_links + if @settings.create_category_links + add_normalization(normalizations, CATEGORY_LINK_NORMALIZATION) + end add_normalization(normalizations, POST_LINK_NORMALIZATION) if @settings.create_post_links add_normalization(normalizations, TOPIC_LINK_NORMALIZATION) if @settings.create_topic_links - SiteSetting.permalink_normalizations = normalizations.join('|') + SiteSetting.permalink_normalizations = normalizations.join("|") end def create_for_category(category, import_id) @@ -50,8 +52,8 @@ module ImportScripts::PhpBB3 def add_normalization(normalizations, normalization) if @settings.normalization_prefix.present? - prefix = @settings.normalization_prefix[%r|^/?(.*?)/?$|, 1] - normalization = "/#{prefix.gsub('/', '\/')}\\#{normalization}" + prefix = @settings.normalization_prefix[%r{^/?(.*?)/?$}, 1] + normalization = "/#{prefix.gsub("/", '\/')}\\#{normalization}" end normalizations << normalization unless normalizations.include?(normalization) diff --git a/script/import_scripts/phpbb3/importers/poll_importer.rb b/script/import_scripts/phpbb3/importers/poll_importer.rb index 785fbb60b2..df4696201c 100644 --- a/script/import_scripts/phpbb3/importers/poll_importer.rb +++ b/script/import_scripts/phpbb3/importers/poll_importer.rb @@ -49,7 +49,12 @@ module ImportScripts::PhpBB3 end def get_option_text(row) - text = @text_processor.process_raw_text(row[:poll_option_text]) rescue row[:poll_option_text] + text = + begin + @text_processor.process_raw_text(row[:poll_option_text]) + rescue StandardError + row[:poll_option_text] + end text.squish! text.gsub!(/^(\d+)\./, '\1\.') text @@ -57,7 +62,12 @@ module ImportScripts::PhpBB3 # @param poll_data [ImportScripts::PhpBB3::PollData] def get_poll_text(poll_data) - title = @text_processor.process_raw_text(poll_data.title) rescue poll_data.title + title = + begin + @text_processor.process_raw_text(poll_data.title) + rescue StandardError + poll_data.title + end text = +"#{title}\n\n" arguments = ["results=always"] @@ -69,11 +79,9 @@ module ImportScripts::PhpBB3 arguments << "type=regular" end - text << "[poll #{arguments.join(' ')}]" + text << "[poll #{arguments.join(" ")}]" - poll_data.options.each do |option| - text << "\n* #{option[:text]}" - end + poll_data.options.each { |option| text << "\n* #{option[:text]}" } text << "\n[/poll]" end @@ -104,9 +112,7 @@ module ImportScripts::PhpBB3 poll.poll_options.each_with_index do |option, index| imported_option = poll_data.options[index] - imported_option[:ids].each do |imported_id| - option_ids[imported_id] = option.id - end + imported_option[:ids].each { |imported_id| option_ids[imported_id] = option.id } end option_ids diff --git a/script/import_scripts/phpbb3/importers/post_importer.rb b/script/import_scripts/phpbb3/importers/post_importer.rb index 8f41e9ed66..4f66560e34 100644 --- a/script/import_scripts/phpbb3/importers/post_importer.rb +++ b/script/import_scripts/phpbb3/importers/post_importer.rb @@ -8,7 +8,14 @@ module ImportScripts::PhpBB3 # @param poll_importer [ImportScripts::PhpBB3::PollImporter] # @param permalink_importer [ImportScripts::PhpBB3::PermalinkImporter] # @param settings [ImportScripts::PhpBB3::Settings] - def initialize(lookup, text_processor, attachment_importer, poll_importer, permalink_importer, settings) + def initialize( + lookup, + text_processor, + attachment_importer, + poll_importer, + permalink_importer, + settings + ) @lookup = lookup @text_processor = text_processor @attachment_importer = attachment_importer @@ -24,7 +31,8 @@ module ImportScripts::PhpBB3 def map_post(row) return if @settings.category_mappings.dig(row[:forum_id].to_s, :skip) - imported_user_id = @settings.prefix(row[:post_username].blank? ? row[:poster_id] : row[:post_username]) + imported_user_id = + @settings.prefix(row[:post_username].blank? ? row[:poster_id] : row[:post_username]) user_id = @lookup.user_id_from_imported_user_id(imported_user_id) || -1 is_first_post = row[:post_id] == row[:topic_first_post_id] @@ -35,7 +43,7 @@ module ImportScripts::PhpBB3 user_id: user_id, created_at: Time.zone.at(row[:post_time]), raw: @text_processor.process_post(row[:post_text], attachments), - import_topic_id: @settings.prefix(row[:topic_id]) + import_topic_id: @settings.prefix(row[:topic_id]), } if is_first_post @@ -58,7 +66,9 @@ module ImportScripts::PhpBB3 mapped[:category] = if category_mapping = @settings.category_mappings[row[:forum_id].to_s] category_mapping[:discourse_category_id] || - @lookup.category_id_from_imported_category_id(@settings.prefix(category_mapping[:target_category_id])) + @lookup.category_id_from_imported_category_id( + @settings.prefix(category_mapping[:target_category_id]), + ) else @lookup.category_id_from_imported_category_id(@settings.prefix(row[:forum_id])) end @@ -81,7 +91,8 @@ module ImportScripts::PhpBB3 end def map_other_post(row, mapped) - parent = @lookup.topic_lookup_from_imported_post_id(@settings.prefix(row[:topic_first_post_id])) + parent = + @lookup.topic_lookup_from_imported_post_id(@settings.prefix(row[:topic_first_post_id])) if parent.blank? puts "Parent post #{@settings.prefix(row[:topic_first_post_id])} doesn't exist. Skipping #{@settings.prefix(row[:post_id])}: #{row[:topic_title][0..40]}" diff --git a/script/import_scripts/phpbb3/importers/user_importer.rb b/script/import_scripts/phpbb3/importers/user_importer.rb index 3fa61d6e17..6f32223232 100644 --- a/script/import_scripts/phpbb3/importers/user_importer.rb +++ b/script/import_scripts/phpbb3/importers/user_importer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative '../support/constants' +require_relative "../support/constants" module ImportScripts::PhpBB3 class UserImporter @@ -29,8 +29,22 @@ module ImportScripts::PhpBB3 password: @settings.import_passwords ? row[:user_password] : nil, name: @settings.username_as_name ? row[:username] : row[:name].presence, created_at: Time.zone.at(row[:user_regdate]), - last_seen_at: row[:user_lastvisit] == 0 ? Time.zone.at(row[:user_regdate]) : Time.zone.at(row[:user_lastvisit]), - registration_ip_address: (IPAddr.new(row[:user_ip]) rescue nil), + last_seen_at: + ( + if row[:user_lastvisit] == 0 + Time.zone.at(row[:user_regdate]) + else + Time.zone.at(row[:user_lastvisit]) + end + ), + registration_ip_address: + ( + begin + IPAddr.new(row[:user_ip]) + rescue StandardError + nil + end + ), active: is_active_user, trust_level: trust_level, manual_locked_trust_level: manual_locked_trust_level, @@ -43,10 +57,11 @@ module ImportScripts::PhpBB3 location: row[:user_from], date_of_birth: parse_birthdate(row), custom_fields: custom_fields(row), - post_create_action: proc do |user| - suspend_user(user, row) - @avatar_importer.import_avatar(user, row) if row[:user_avatar_type].present? - end + post_create_action: + proc do |user| + suspend_user(user, row) + @avatar_importer.import_avatar(user, row) if row[:user_avatar_type].present? + end, } end @@ -61,18 +76,19 @@ module ImportScripts::PhpBB3 id: @settings.prefix(username), email: "anonymous_#{SecureRandom.hex}@no-email.invalid", username: username, - name: @settings.username_as_name ? username : '', + name: @settings.username_as_name ? username : "", created_at: Time.zone.at(row[:first_post_time]), active: true, trust_level: TrustLevel[0], approved: true, approved_by_id: Discourse.system_user.id, approved_at: Time.now, - post_create_action: proc do |user| - row[:user_inactive_reason] = Constants::INACTIVE_MANUAL - row[:ban_reason] = 'Anonymous user from phpBB3' # TODO i18n - suspend_user(user, row, true) - end + post_create_action: + proc do |user| + row[:user_inactive_reason] = Constants::INACTIVE_MANUAL + row[:ban_reason] = "Anonymous user from phpBB3" # TODO i18n + suspend_user(user, row, true) + end, } end @@ -80,25 +96,32 @@ module ImportScripts::PhpBB3 def parse_birthdate(row) return nil if row[:user_birthday].blank? - birthdate = Date.strptime(row[:user_birthday].delete(' '), '%d-%m-%Y') rescue nil + birthdate = + begin + Date.strptime(row[:user_birthday].delete(" "), "%d-%m-%Y") + rescue StandardError + nil + end birthdate && birthdate.year > 0 ? birthdate : nil end def user_fields - @user_fields ||= begin - Hash[UserField.all.map { |field| [field.name, field] }] - end + @user_fields ||= + begin + Hash[UserField.all.map { |field| [field.name, field] }] + end end def field_mappings - @field_mappings ||= begin - @settings.custom_fields.map do |field| - { - phpbb_field_name: "pf_#{field[:phpbb_field_name]}".to_sym, - discourse_user_field: user_fields[field[:discourse_field_name]] - } + @field_mappings ||= + begin + @settings.custom_fields.map do |field| + { + phpbb_field_name: "pf_#{field[:phpbb_field_name]}".to_sym, + discourse_user_field: user_fields[field[:discourse_field_name]], + } + end end - end end def custom_fields(row) @@ -114,7 +137,8 @@ module ImportScripts::PhpBB3 when "confirm" value = value == 1 ? true : nil when "dropdown" - value = user_field.user_field_options.find { |option| option.value == value } ? value : nil + value = + user_field.user_field_options.find { |option| option.value == value } ? value : nil end custom_fields["user_field_#{user_field.id}"] = value if value.present? @@ -128,7 +152,8 @@ module ImportScripts::PhpBB3 if row[:user_inactive_reason] == Constants::INACTIVE_MANUAL user.suspended_at = Time.now user.suspended_till = 200.years.from_now - ban_reason = row[:ban_reason].blank? ? 'Account deactivated by administrator' : row[:ban_reason] # TODO i18n + ban_reason = + row[:ban_reason].blank? ? "Account deactivated by administrator" : row[:ban_reason] # TODO i18n elsif row[:ban_start].present? user.suspended_at = Time.zone.at(row[:ban_start]) user.suspended_till = row[:ban_end] > 0 ? Time.zone.at(row[:ban_end]) : 200.years.from_now @@ -148,7 +173,9 @@ module ImportScripts::PhpBB3 if user.save StaffActionLogger.new(Discourse.system_user).log_user_suspend(user, ban_reason) else - Rails.logger.error("Failed to suspend user #{user.username}. #{user.errors.try(:full_messages).try(:inspect)}") + Rails.logger.error( + "Failed to suspend user #{user.username}. #{user.errors.try(:full_messages).try(:inspect)}", + ) end end end diff --git a/script/import_scripts/phpbb3/support/bbcode/markdown_node.rb b/script/import_scripts/phpbb3/support/bbcode/markdown_node.rb index 5a42a1bf40..c5e5048a9f 100644 --- a/script/import_scripts/phpbb3/support/bbcode/markdown_node.rb +++ b/script/import_scripts/phpbb3/support/bbcode/markdown_node.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true -module ImportScripts; end -module ImportScripts::PhpBB3; end +module ImportScripts +end +module ImportScripts::PhpBB3 +end module ImportScripts::PhpBB3::BBCode LINEBREAK_AUTO = :auto diff --git a/script/import_scripts/phpbb3/support/bbcode/xml_to_markdown.rb b/script/import_scripts/phpbb3/support/bbcode/xml_to_markdown.rb index 7041c5923f..004601d247 100644 --- a/script/import_scripts/phpbb3/support/bbcode/xml_to_markdown.rb +++ b/script/import_scripts/phpbb3/support/bbcode/xml_to_markdown.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'nokogiri' -require_relative 'markdown_node' +require "nokogiri" +require_relative "markdown_node" module ImportScripts::PhpBB3::BBCode class XmlToMarkdown @@ -14,7 +14,7 @@ module ImportScripts::PhpBB3::BBCode @allow_inline_code = opts.fetch(:allow_inline_code, false) @traditional_linebreaks = opts.fetch(:traditional_linebreaks, false) - @doc = Nokogiri::XML(xml) + @doc = Nokogiri.XML(xml) @list_stack = [] end @@ -28,9 +28,9 @@ module ImportScripts::PhpBB3::BBCode private - IGNORED_ELEMENTS = ["s", "e", "i"] - ELEMENTS_WITHOUT_LEADING_WHITESPACES = ["LIST", "LI"] - ELEMENTS_WITH_HARD_LINEBREAKS = ["B", "I", "U"] + IGNORED_ELEMENTS = %w[s e i] + ELEMENTS_WITHOUT_LEADING_WHITESPACES = %w[LIST LI] + ELEMENTS_WITH_HARD_LINEBREAKS = %w[B I U] EXPLICIT_LINEBREAK_THRESHOLD = 2 def preprocess_xml @@ -65,9 +65,7 @@ module ImportScripts::PhpBB3::BBCode xml_node.children.each { |xml_child| visit(xml_child, md_node || md_parent) } after_hook = "after_#{xml_node.name}" - if respond_to?(after_hook, include_all: true) - send(after_hook, xml_node, md_node) - end + send(after_hook, xml_node, md_node) if respond_to?(after_hook, include_all: true) end def create_node(xml_node, md_parent) @@ -84,19 +82,15 @@ module ImportScripts::PhpBB3::BBCode end def visit_B(xml_node, md_node) - if xml_node.parent&.name != 'B' - md_node.enclosed_with = "**" - end + md_node.enclosed_with = "**" if xml_node.parent&.name != "B" end def visit_I(xml_node, md_node) - if xml_node.parent&.name != 'I' - md_node.enclosed_with = "_" - end + md_node.enclosed_with = "_" if xml_node.parent&.name != "I" end def visit_U(xml_node, md_node) - if xml_node.parent&.name != 'U' + if xml_node.parent&.name != "U" md_node.prefix = "[u]" md_node.postfix = "[/u]" end @@ -122,10 +116,7 @@ module ImportScripts::PhpBB3::BBCode md_node.prefix_linebreaks = md_node.postfix_linebreaks = @list_stack.size == 0 ? 2 : 1 md_node.prefix_linebreak_type = LINEBREAK_HTML if @list_stack.size == 0 - @list_stack << { - unordered: xml_node.attribute('type').nil?, - item_count: 0 - } + @list_stack << { unordered: xml_node.attribute("type").nil?, item_count: 0 } end def after_LIST(xml_node, md_node) @@ -138,21 +129,21 @@ module ImportScripts::PhpBB3::BBCode list[:item_count] += 1 - indentation = ' ' * 2 * depth - symbol = list[:unordered] ? '*' : "#{list[:item_count]}." + indentation = " " * 2 * depth + symbol = list[:unordered] ? "*" : "#{list[:item_count]}." md_node.prefix = "#{indentation}#{symbol} " md_node.postfix_linebreaks = 1 end def visit_IMG(xml_node, md_node) - md_node.text = +"![](#{xml_node.attribute('src')})" + md_node.text = +"![](#{xml_node.attribute("src")})" md_node.prefix_linebreaks = md_node.postfix_linebreaks = 2 md_node.skip_children end def visit_URL(xml_node, md_node) - original_url = xml_node.attribute('url').to_s + original_url = xml_node.attribute("url").to_s url = CGI.unescapeHTML(original_url) url = @url_replacement.call(url) if @url_replacement @@ -173,7 +164,8 @@ module ImportScripts::PhpBB3::BBCode def visit_br(xml_node, md_node) md_node.postfix_linebreaks += 1 - if md_node.postfix_linebreaks > 1 && ELEMENTS_WITH_HARD_LINEBREAKS.include?(xml_node.parent&.name) + if md_node.postfix_linebreaks > 1 && + ELEMENTS_WITH_HARD_LINEBREAKS.include?(xml_node.parent&.name) md_node.postfix_linebreak_type = LINEBREAK_HARD end end @@ -194,7 +186,8 @@ module ImportScripts::PhpBB3::BBCode def visit_QUOTE(xml_node, md_node) if post = quoted_post(xml_node) - md_node.prefix = %Q{[quote="#{post[:username]}, post:#{post[:post_number]}, topic:#{post[:topic_id]}"]\n} + md_node.prefix = + %Q{[quote="#{post[:username]}, post:#{post[:post_number]}, topic:#{post[:topic_id]}"]\n} md_node.postfix = "\n[/quote]" elsif username = quoted_username(xml_node) md_node.prefix = %Q{[quote="#{username}"]\n} @@ -242,11 +235,11 @@ module ImportScripts::PhpBB3::BBCode return if size.nil? if size.between?(1, 99) - md_node.prefix = '' - md_node.postfix = '' + md_node.prefix = "" + md_node.postfix = "" elsif size.between?(101, 200) - md_node.prefix = '' - md_node.postfix = '' + md_node.prefix = "" + md_node.postfix = "" end end @@ -267,7 +260,8 @@ module ImportScripts::PhpBB3::BBCode parent_prefix = prefix_from_parent(md_parent) - if parent_prefix && md_node.xml_node_name != "br" && (md_parent.prefix_children || !markdown.empty?) + if parent_prefix && md_node.xml_node_name != "br" && + (md_parent.prefix_children || !markdown.empty?) prefix = "#{parent_prefix}#{prefix}" end @@ -275,11 +269,21 @@ module ImportScripts::PhpBB3::BBCode text, prefix, postfix = hoist_whitespaces!(markdown, text, prefix, postfix) end - add_linebreaks!(markdown, md_node.prefix_linebreaks, md_node.prefix_linebreak_type, parent_prefix) + add_linebreaks!( + markdown, + md_node.prefix_linebreaks, + md_node.prefix_linebreak_type, + parent_prefix, + ) markdown << prefix markdown << text markdown << postfix - add_linebreaks!(markdown, md_node.postfix_linebreaks, md_node.postfix_linebreak_type, parent_prefix) + add_linebreaks!( + markdown, + md_node.postfix_linebreaks, + md_node.postfix_linebreak_type, + parent_prefix, + ) end markdown @@ -296,9 +300,7 @@ module ImportScripts::PhpBB3::BBCode end unless postfix.empty? - if ends_with_whitespace?(text) - postfix = "#{postfix}#{text[-1]}" - end + postfix = "#{postfix}#{text[-1]}" if ends_with_whitespace?(text) text = text.rstrip end @@ -319,16 +321,24 @@ module ImportScripts::PhpBB3::BBCode if linebreak_type == LINEBREAK_HTML max_linebreak_count = [existing_linebreak_count, required_linebreak_count - 1].max + 1 - required_linebreak_count = max_linebreak_count if max_linebreak_count > EXPLICIT_LINEBREAK_THRESHOLD + required_linebreak_count = max_linebreak_count if max_linebreak_count > + EXPLICIT_LINEBREAK_THRESHOLD end return if existing_linebreak_count >= required_linebreak_count rstrip!(markdown) - alternative_linebreak_start_index = required_linebreak_count > EXPLICIT_LINEBREAK_THRESHOLD ? 1 : 2 + alternative_linebreak_start_index = + required_linebreak_count > EXPLICIT_LINEBREAK_THRESHOLD ? 1 : 2 required_linebreak_count.times do |index| - linebreak = linebreak(linebreak_type, index, alternative_linebreak_start_index, required_linebreak_count) + linebreak = + linebreak( + linebreak_type, + index, + alternative_linebreak_start_index, + required_linebreak_count, + ) markdown << (linebreak == "\n" ? prefix.rstrip : prefix) if prefix && index > 0 markdown << linebreak @@ -336,18 +346,25 @@ module ImportScripts::PhpBB3::BBCode end def rstrip!(markdown) - markdown.gsub!(/\s*(?:\\?\n|
\n)*\z/, '') + markdown.gsub!(/\s*(?:\\?\n|
\n)*\z/, "") end - def linebreak(linebreak_type, linebreak_index, alternative_linebreak_start_index, required_linebreak_count) + def linebreak( + linebreak_type, + linebreak_index, + alternative_linebreak_start_index, + required_linebreak_count + ) use_alternative_linebreak = linebreak_index >= alternative_linebreak_start_index is_last_linebreak = linebreak_index + 1 == required_linebreak_count - return "
\n" if linebreak_type == LINEBREAK_HTML && - use_alternative_linebreak && is_last_linebreak + if linebreak_type == LINEBREAK_HTML && use_alternative_linebreak && is_last_linebreak + return "
\n" + end - return "\\\n" if linebreak_type == LINEBREAK_HARD || - @traditional_linebreaks || use_alternative_linebreak + if linebreak_type == LINEBREAK_HARD || @traditional_linebreaks || use_alternative_linebreak + return "\\\n" + end "\n" end diff --git a/script/import_scripts/phpbb3/support/constants.rb b/script/import_scripts/phpbb3/support/constants.rb index af8d62dc43..c832cfee8f 100644 --- a/script/import_scripts/phpbb3/support/constants.rb +++ b/script/import_scripts/phpbb3/support/constants.rb @@ -8,8 +8,8 @@ module ImportScripts::PhpBB3 INACTIVE_MANUAL = 3 # Account deactivated by administrator INACTIVE_REMIND = 4 # Forced user account reactivation - GROUP_ADMINISTRATORS = 'ADMINISTRATORS' - GROUP_MODERATORS = 'GLOBAL_MODERATORS' + GROUP_ADMINISTRATORS = "ADMINISTRATORS" + GROUP_MODERATORS = "GLOBAL_MODERATORS" # https://wiki.phpbb.com/Table.phpbb_users USER_TYPE_NORMAL = 0 @@ -21,9 +21,9 @@ module ImportScripts::PhpBB3 AVATAR_TYPE_REMOTE = 2 AVATAR_TYPE_GALLERY = 3 - AVATAR_TYPE_STRING_UPLOADED = 'avatar.driver.upload' - AVATAR_TYPE_STRING_REMOTE = 'avatar.driver.remote' - AVATAR_TYPE_STRING_GALLERY = 'avatar.driver.local' + AVATAR_TYPE_STRING_UPLOADED = "avatar.driver.upload" + AVATAR_TYPE_STRING_REMOTE = "avatar.driver.remote" + AVATAR_TYPE_STRING_GALLERY = "avatar.driver.local" FORUM_TYPE_CATEGORY = 0 FORUM_TYPE_POST = 1 diff --git a/script/import_scripts/phpbb3/support/settings.rb b/script/import_scripts/phpbb3/support/settings.rb index b259821ab3..e308e322cf 100644 --- a/script/import_scripts/phpbb3/support/settings.rb +++ b/script/import_scripts/phpbb3/support/settings.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require 'csv' -require 'yaml' -require_relative '../../base' +require "csv" +require "yaml" +require_relative "../../base" module ImportScripts::PhpBB3 class Settings def self.load(filename) - yaml = YAML::load_file(filename) + yaml = YAML.load_file(filename) Settings.new(yaml.deep_stringify_keys.with_indifferent_access) end @@ -44,40 +44,41 @@ module ImportScripts::PhpBB3 attr_reader :database def initialize(yaml) - import_settings = yaml['import'] + import_settings = yaml["import"] - @site_name = import_settings['site_name'] + @site_name = import_settings["site_name"] - @new_categories = import_settings['new_categories'] - @category_mappings = import_settings.fetch('category_mappings', []).to_h { |m| [m[:source_category_id].to_s, m] } - @tag_mappings = import_settings['tag_mappings'] - @rank_mapping = import_settings['rank_mapping'] + @new_categories = import_settings["new_categories"] + @category_mappings = + import_settings.fetch("category_mappings", []).to_h { |m| [m[:source_category_id].to_s, m] } + @tag_mappings = import_settings["tag_mappings"] + @rank_mapping = import_settings["rank_mapping"] - @import_anonymous_users = import_settings['anonymous_users'] - @import_attachments = import_settings['attachments'] - @import_private_messages = import_settings['private_messages'] - @import_polls = import_settings['polls'] - @import_bookmarks = import_settings['bookmarks'] - @import_passwords = import_settings['passwords'] - @import_likes = import_settings['likes'] + @import_anonymous_users = import_settings["anonymous_users"] + @import_attachments = import_settings["attachments"] + @import_private_messages = import_settings["private_messages"] + @import_polls = import_settings["polls"] + @import_bookmarks = import_settings["bookmarks"] + @import_passwords = import_settings["passwords"] + @import_likes = import_settings["likes"] - avatar_settings = import_settings['avatars'] - @import_uploaded_avatars = avatar_settings['uploaded'] - @import_remote_avatars = avatar_settings['remote'] - @import_gallery_avatars = avatar_settings['gallery'] + avatar_settings = import_settings["avatars"] + @import_uploaded_avatars = avatar_settings["uploaded"] + @import_remote_avatars = avatar_settings["remote"] + @import_gallery_avatars = avatar_settings["gallery"] - @use_bbcode_to_md = import_settings['use_bbcode_to_md'] + @use_bbcode_to_md = import_settings["use_bbcode_to_md"] - @original_site_prefix = import_settings['site_prefix']['original'] - @new_site_prefix = import_settings['site_prefix']['new'] - @base_dir = import_settings['phpbb_base_dir'] - @permalinks = PermalinkSettings.new(import_settings['permalinks']) + @original_site_prefix = import_settings["site_prefix"]["original"] + @new_site_prefix = import_settings["site_prefix"]["new"] + @base_dir = import_settings["phpbb_base_dir"] + @permalinks = PermalinkSettings.new(import_settings["permalinks"]) - @username_as_name = import_settings['username_as_name'] - @emojis = import_settings.fetch('emojis', []) - @custom_fields = import_settings.fetch('custom_fields', []) + @username_as_name = import_settings["username_as_name"] + @emojis = import_settings.fetch("emojis", []) + @custom_fields = import_settings.fetch("custom_fields", []) - @database = DatabaseSettings.new(yaml['database']) + @database = DatabaseSettings.new(yaml["database"]) end def prefix(val) @@ -87,7 +88,7 @@ module ImportScripts::PhpBB3 def trust_level_for_posts(rank, trust_level: 0) if @rank_mapping.present? @rank_mapping.each do |key, value| - trust_level = [trust_level, key.gsub('trust_level_', '').to_i].max if rank >= value + trust_level = [trust_level, key.gsub("trust_level_", "").to_i].max if rank >= value end end @@ -106,14 +107,14 @@ module ImportScripts::PhpBB3 attr_reader :batch_size def initialize(yaml) - @type = yaml['type'] - @host = yaml['host'] - @port = yaml['port'] - @username = yaml['username'] - @password = yaml['password'] - @schema = yaml['schema'] - @table_prefix = yaml['table_prefix'] - @batch_size = yaml['batch_size'] + @type = yaml["type"] + @host = yaml["host"] + @port = yaml["port"] + @username = yaml["username"] + @password = yaml["password"] + @schema = yaml["schema"] + @table_prefix = yaml["table_prefix"] + @batch_size = yaml["batch_size"] end end @@ -124,10 +125,10 @@ module ImportScripts::PhpBB3 attr_reader :normalization_prefix def initialize(yaml) - @create_category_links = yaml['categories'] - @create_topic_links = yaml['topics'] - @create_post_links = yaml['posts'] - @normalization_prefix = yaml['prefix'] + @create_category_links = yaml["categories"] + @create_topic_links = yaml["topics"] + @create_post_links = yaml["posts"] + @normalization_prefix = yaml["prefix"] end end end diff --git a/script/import_scripts/phpbb3/support/smiley_processor.rb b/script/import_scripts/phpbb3/support/smiley_processor.rb index 618f99ddd2..4a861fc4c1 100644 --- a/script/import_scripts/phpbb3/support/smiley_processor.rb +++ b/script/import_scripts/phpbb3/support/smiley_processor.rb @@ -18,15 +18,16 @@ module ImportScripts::PhpBB3 def replace_smilies(text) # :) is encoded as :) - text.gsub!(/.*?/) do - emoji($1) - end + text.gsub!( + /.*?/, + ) { emoji($1) } end def emoji(smiley_code) @smiley_map.fetch(smiley_code) do smiley = @database.get_smiley(smiley_code) - emoji = upload_smiley(smiley_code, smiley[:smiley_url], smiley_code, smiley[:emotion]) if smiley + emoji = + upload_smiley(smiley_code, smiley[:smiley_url], smiley_code, smiley[:emotion]) if smiley emoji || smiley_as_text(smiley_code) end end @@ -35,37 +36,34 @@ module ImportScripts::PhpBB3 def add_default_smilies { - [':D', ':-D', ':grin:'] => ':smiley:', - [':)', ':-)', ':smile:'] => ':slight_smile:', - [';)', ';-)', ':wink:'] => ':wink:', - [':(', ':-(', ':sad:'] => ':frowning:', - [':o', ':-o', ':eek:'] => ':astonished:', - [':shock:'] => ':open_mouth:', - [':?', ':-?', ':???:'] => ':confused:', - ['8)', '8-)', ':cool:'] => ':sunglasses:', - [':lol:'] => ':laughing:', - [':x', ':-x', ':mad:'] => ':angry:', - [':P', ':-P', ':razz:'] => ':stuck_out_tongue:', - [':oops:'] => ':blush:', - [':cry:'] => ':cry:', - [':evil:'] => ':imp:', - [':twisted:'] => ':smiling_imp:', - [':roll:'] => ':unamused:', - [':!:'] => ':exclamation:', - [':?:'] => ':question:', - [':idea:'] => ':bulb:', - [':arrow:'] => ':arrow_right:', - [':|', ':-|'] => ':neutral_face:', - [':geek:'] => ':nerd:' - }.each do |smilies, emoji| - smilies.each { |smiley| @smiley_map[smiley] = emoji } - end + %w[:D :-D :grin:] => ":smiley:", + %w[:) :-) :smile:] => ":slight_smile:", + %w[;) ;-) :wink:] => ":wink:", + %w[:( :-( :sad:] => ":frowning:", + %w[:o :-o :eek:] => ":astonished:", + [":shock:"] => ":open_mouth:", + %w[:? :-? :???:] => ":confused:", + %w[8) 8-) :cool:] => ":sunglasses:", + [":lol:"] => ":laughing:", + %w[:x :-x :mad:] => ":angry:", + %w[:P :-P :razz:] => ":stuck_out_tongue:", + [":oops:"] => ":blush:", + [":cry:"] => ":cry:", + [":evil:"] => ":imp:", + [":twisted:"] => ":smiling_imp:", + [":roll:"] => ":unamused:", + [":!:"] => ":exclamation:", + [":?:"] => ":question:", + [":idea:"] => ":bulb:", + [":arrow:"] => ":arrow_right:", + %w[:| :-|] => ":neutral_face:", + [":geek:"] => ":nerd:", + }.each { |smilies, emoji| smilies.each { |smiley| @smiley_map[smiley] = emoji } } end def add_configured_smilies(emojis) emojis.each do |emoji, smilies| - Array.wrap(smilies) - .each { |smiley| @smiley_map[smiley] = ":#{emoji}:" } + Array.wrap(smilies).each { |smiley| @smiley_map[smiley] = ":#{emoji}:" } end end diff --git a/script/import_scripts/phpbb3/support/text_processor.rb b/script/import_scripts/phpbb3/support/text_processor.rb index 62547b4f15..fb788bf537 100644 --- a/script/import_scripts/phpbb3/support/text_processor.rb +++ b/script/import_scripts/phpbb3/support/text_processor.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'bbcode/xml_to_markdown' +require_relative "bbcode/xml_to_markdown" module ImportScripts::PhpBB3 class TextProcessor @@ -14,7 +14,9 @@ module ImportScripts::PhpBB3 @database = database @smiley_processor = smiley_processor @he = HTMLEntities.new - @use_xml_to_markdown = phpbb_config[:phpbb_version].start_with?('3.2') || phpbb_config[:phpbb_version].start_with?('3.3') + @use_xml_to_markdown = + phpbb_config[:phpbb_version].start_with?("3.2") || + phpbb_config[:phpbb_version].start_with?("3.3") @settings = settings @new_site_prefix = settings.new_site_prefix @@ -25,24 +27,27 @@ module ImportScripts::PhpBB3 if @use_xml_to_markdown unreferenced_attachments = attachments&.dup - converter = BBCode::XmlToMarkdown.new( - raw, - username_from_user_id: lambda { |user_id| @lookup.find_username_by_import_id(user_id) }, - smilie_to_emoji: lambda { |smilie| @smiley_processor.emoji(smilie).dup }, - quoted_post_from_post_id: lambda { |post_id| @lookup.topic_lookup_from_imported_post_id(post_id) }, - upload_md_from_file: (lambda do |filename, index| - unreferenced_attachments[index] = nil - attachments.fetch(index, filename).dup - end if attachments), - url_replacement: nil, - allow_inline_code: false - ) + converter = + BBCode::XmlToMarkdown.new( + raw, + username_from_user_id: lambda { |user_id| @lookup.find_username_by_import_id(user_id) }, + smilie_to_emoji: lambda { |smilie| @smiley_processor.emoji(smilie).dup }, + quoted_post_from_post_id: + lambda { |post_id| @lookup.topic_lookup_from_imported_post_id(post_id) }, + upload_md_from_file: + ( + lambda do |filename, index| + unreferenced_attachments[index] = nil + attachments.fetch(index, filename).dup + end if attachments + ), + url_replacement: nil, + allow_inline_code: false, + ) text = converter.convert - text.gsub!(@short_internal_link_regexp) do |link| - replace_internal_link(link, $1, $2) - end + text.gsub!(@short_internal_link_regexp) { |link| replace_internal_link(link, $1, $2) } add_unreferenced_attachments(text, unreferenced_attachments) else @@ -50,9 +55,7 @@ module ImportScripts::PhpBB3 text = CGI.unescapeHTML(text) clean_bbcodes(text) - if @settings.use_bbcode_to_md - text = bbcode_to_md(text) - end + text = bbcode_to_md(text) if @settings.use_bbcode_to_md process_smilies(text) process_links(text) process_lists(text) @@ -65,11 +68,19 @@ module ImportScripts::PhpBB3 end def process_post(raw, attachments) - process_raw_text(raw, attachments) rescue raw + begin + process_raw_text(raw, attachments) + rescue StandardError + raw + end end def process_private_msg(raw, attachments) - process_raw_text(raw, attachments) rescue raw + begin + process_raw_text(raw, attachments) + rescue StandardError + raw + end end protected @@ -78,10 +89,10 @@ module ImportScripts::PhpBB3 # Many phpbb bbcode tags have a hash attached to them. Examples: # [url=https://google.com:1qh1i7ky]click here[/url:1qh1i7ky] # [quote="cybereality":b0wtlzex]Some text.[/quote:b0wtlzex] - text.gsub!(/:(?:\w{5,8})\]/, ']') + text.gsub!(/:(?:\w{5,8})\]/, "]") # remove color tags - text.gsub!(/\[\/?color(=#?[a-z0-9]*)?\]/i, "") + text.gsub!(%r{\[/?color(=#?[a-z0-9]*)?\]}i, "") end def bbcode_to_md(text) @@ -101,23 +112,19 @@ module ImportScripts::PhpBB3 # Internal forum links can have this forms: # for topics: viewtopic.php?f=26&t=3412 # for posts: viewtopic.php?p=1732#p1732 - text.gsub!(@long_internal_link_regexp) do |link| - replace_internal_link(link, $1, $2) - end + text.gsub!(@long_internal_link_regexp) { |link| replace_internal_link(link, $1, $2) } # Some links look like this: http://www.onegameamonth.com - text.gsub!(/(.+)<\/a>/i, '[\2](\1)') + text.gsub!(%r{(.+)}i, '[\2](\1)') # Replace internal forum links that aren't in the format - text.gsub!(@short_internal_link_regexp) do |link| - replace_internal_link(link, $1, $2) - end + text.gsub!(@short_internal_link_regexp) { |link| replace_internal_link(link, $1, $2) } # phpBB shortens link text like this, which breaks our markdown processing: # [http://answers.yahoo.com/question/index ... 223AAkkPli](http://answers.yahoo.com/question/index?qid=20070920134223AAkkPli) # # Work around it for now: - text.gsub!(/\[http(s)?:\/\/(www\.)?/i, '[') + text.gsub!(%r{\[http(s)?://(www\.)?}i, "[") end def replace_internal_link(link, import_topic_id, import_post_id) @@ -144,19 +151,20 @@ module ImportScripts::PhpBB3 # convert list tags to ul and list=1 tags to ol # list=a is not supported, so handle it like list=1 # list=9 and list=x have the same result as list=1 and list=a - text.gsub!(/\[list\](.*?)\[\/list:u\]/mi) do - $1.gsub(/\[\*\](.*?)\[\/\*:m\]\n*/mi) { "* #{$1}\n" } + text.gsub!(%r{\[list\](.*?)\[/list:u\]}mi) do + $1.gsub(%r{\[\*\](.*?)\[/\*:m\]\n*}mi) { "* #{$1}\n" } end - text.gsub!(/\[list=.*?\](.*?)\[\/list:o\]/mi) do - $1.gsub(/\[\*\](.*?)\[\/\*:m\]\n*/mi) { "1. #{$1}\n" } + text.gsub!(%r{\[list=.*?\](.*?)\[/list:o\]}mi) do + $1.gsub(%r{\[\*\](.*?)\[/\*:m\]\n*}mi) { "1. #{$1}\n" } end end # This replaces existing [attachment] BBCodes with the corresponding HTML tags for Discourse. # All attachments that haven't been referenced in the text are appended to the end of the text. def process_attachments(text, attachments) - attachment_regexp = /\[attachment=([\d])+\]([^<]+)\[\/attachment\]?/i + attachment_regexp = + %r{\[attachment=([\d])+\]([^<]+)\[/attachment\]?}i unreferenced_attachments = attachments.dup text.gsub!(attachment_regexp) do @@ -178,29 +186,34 @@ module ImportScripts::PhpBB3 end def create_internal_link_regexps(original_site_prefix) - host = original_site_prefix.gsub('.', '\.') - link_regex = "http(?:s)?://#{host}/viewtopic\\.php\\?(?:\\S*)(?:t=(\\d+)|p=(\\d+)(?:#p\\d+)?)(?:[^\\s\\)\\]]*)" + host = original_site_prefix.gsub(".", '\.') + link_regex = + "http(?:s)?://#{host}/viewtopic\\.php\\?(?:\\S*)(?:t=(\\d+)|p=(\\d+)(?:#p\\d+)?)(?:[^\\s\\)\\]]*)" - @long_internal_link_regexp = Regexp.new(%Q||, Regexp::IGNORECASE) + @long_internal_link_regexp = + Regexp.new( + %Q||, + Regexp::IGNORECASE, + ) @short_internal_link_regexp = Regexp.new(link_regex, Regexp::IGNORECASE) end def process_code(text) - text.gsub!(//, "\n") + text.gsub!(%r{}, "\n") text end def fix_markdown(text) - text.gsub!(/(\n*\[\/?quote.*?\]\n*)/mi) { |q| "\n#{q.strip}\n" } + text.gsub!(%r{(\n*\[/?quote.*?\]\n*)}mi) { |q| "\n#{q.strip}\n" } text.gsub!(/^!\[[^\]]*\]\([^\]]*\)$/i) { |img| "\n#{img.strip}\n" } # space out images single on line text end def process_videos(text) # [YOUTUBE][/YOUTUBE] - text.gsub(/\[youtube\](.+?)\[\/youtube\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } + text.gsub(%r{\[youtube\](.+?)\[/youtube\]}i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } text end end diff --git a/script/import_scripts/punbb.rb b/script/import_scripts/punbb.rb index 64cce9bbcf..b73e4f7128 100644 --- a/script/import_scripts/punbb.rb +++ b/script/import_scripts/punbb.rb @@ -7,19 +7,19 @@ require File.expand_path(File.dirname(__FILE__) + "/base.rb") # Call it like this: # RAILS_ENV=production bundle exec ruby script/import_scripts/punbb.rb class ImportScripts::PunBB < ImportScripts::Base - PUNBB_DB = "punbb_db" BATCH_SIZE = 1000 def initialize super - @client = Mysql2::Client.new( - host: "localhost", - username: "root", - password: "pa$$word", - database: PUNBB_DB - ) + @client = + Mysql2::Client.new( + host: "localhost", + username: "root", + password: "pa$$word", + database: PUNBB_DB, + ) end def execute @@ -30,36 +30,41 @@ class ImportScripts::PunBB < ImportScripts::Base end def import_users - puts '', "creating users" + puts "", "creating users" - total_count = mysql_query("SELECT count(*) count FROM users;").first['count'] + total_count = mysql_query("SELECT count(*) count FROM users;").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query( - "SELECT id, username, realname name, url website, email email, registered created_at, + results = + mysql_query( + "SELECT id, username, realname name, url website, email email, registered created_at, registration_ip registration_ip_address, last_visit last_visit_time, last_email_sent last_emailed_at, last_email_sent last_emailed_at, location, group_id FROM users LIMIT #{BATCH_SIZE} - OFFSET #{offset};") + OFFSET #{offset};", + ) break if results.size < 1 next if all_records_exist? :users, results.map { |u| u["id"].to_i } create_users(results, total: total_count, offset: offset) do |user| - { id: user['id'], - email: user['email'], - username: user['username'], - name: user['name'], - created_at: Time.zone.at(user['created_at']), - website: user['website'], - registration_ip_address: user['registration_ip_address'], - last_seen_at: Time.zone.at(user['last_visit_time']), - last_emailed_at: user['last_emailed_at'] == nil ? 0 : Time.zone.at(user['last_emailed_at']), - location: user['location'], - moderator: user['group_id'] == 4, - admin: user['group_id'] == 1 } + { + id: user["id"], + email: user["email"], + username: user["username"], + name: user["name"], + created_at: Time.zone.at(user["created_at"]), + website: user["website"], + registration_ip_address: user["registration_ip_address"], + last_seen_at: Time.zone.at(user["last_visit_time"]), + last_emailed_at: + user["last_emailed_at"] == nil ? 0 : Time.zone.at(user["last_emailed_at"]), + location: user["location"], + moderator: user["group_id"] == 4, + admin: user["group_id"] == 1, + } end end end @@ -67,33 +72,34 @@ class ImportScripts::PunBB < ImportScripts::Base def import_categories puts "", "importing top level categories..." - categories = mysql_query(" + categories = + mysql_query( + " SELECT id, cat_name name, disp_position position FROM categories ORDER BY id ASC - ").to_a + ", + ).to_a - create_categories(categories) do |category| - { - id: category["id"], - name: category["name"] - } - end + create_categories(categories) { |category| { id: category["id"], name: category["name"] } } puts "", "importing children categories..." - children_categories = mysql_query(" + children_categories = + mysql_query( + " SELECT id, forum_name name, forum_desc description, disp_position position, cat_id parent_category_id FROM forums ORDER BY id - ").to_a + ", + ).to_a create_categories(children_categories) do |category| { - id: "child##{category['id']}", + id: "child##{category["id"]}", name: category["name"], description: category["description"], - parent_category_id: category_id_from_imported_category_id(category["parent_category_id"]) + parent_category_id: category_id_from_imported_category_id(category["parent_category_id"]), } end end @@ -104,7 +110,9 @@ class ImportScripts::PunBB < ImportScripts::Base total_count = mysql_query("SELECT count(*) count from posts").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query(" + results = + mysql_query( + " SELECT p.id id, t.id topic_id, t.forum_id category_id, @@ -119,29 +127,30 @@ class ImportScripts::PunBB < ImportScripts::Base ORDER BY p.posted LIMIT #{BATCH_SIZE} OFFSET #{offset}; - ").to_a + ", + ).to_a break if results.size < 1 - next if all_records_exist? :posts, results.map { |m| m['id'].to_i } + next if all_records_exist? :posts, results.map { |m| m["id"].to_i } create_posts(results, total: total_count, offset: offset) do |m| skip = false mapped = {} - mapped[:id] = m['id'] - mapped[:user_id] = user_id_from_imported_user_id(m['user_id']) || -1 - mapped[:raw] = process_punbb_post(m['raw'], m['id']) - mapped[:created_at] = Time.zone.at(m['created_at']) + mapped[:id] = m["id"] + mapped[:user_id] = user_id_from_imported_user_id(m["user_id"]) || -1 + mapped[:raw] = process_punbb_post(m["raw"], m["id"]) + mapped[:created_at] = Time.zone.at(m["created_at"]) - if m['id'] == m['first_post_id'] - mapped[:category] = category_id_from_imported_category_id("child##{m['category_id']}") - mapped[:title] = CGI.unescapeHTML(m['title']) + if m["id"] == m["first_post_id"] + mapped[:category] = category_id_from_imported_category_id("child##{m["category_id"]}") + mapped[:title] = CGI.unescapeHTML(m["title"]) else - parent = topic_lookup_from_imported_post_id(m['first_post_id']) + parent = topic_lookup_from_imported_post_id(m["first_post_id"]) if parent mapped[:topic_id] = parent[:topic_id] else - puts "Parent post #{m['first_post_id']} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" + puts "Parent post #{m["first_post_id"]} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" skip = true end end @@ -152,16 +161,16 @@ class ImportScripts::PunBB < ImportScripts::Base end def suspend_users - puts '', "updating banned users" + puts "", "updating banned users" banned = 0 failed = 0 - total = mysql_query("SELECT count(*) count FROM bans").first['count'] + total = mysql_query("SELECT count(*) count FROM bans").first["count"] system_user = Discourse.system_user mysql_query("SELECT username, email FROM bans").each do |b| - user = User.find_by_email(b['email']) + user = User.find_by_email(b["email"]) if user user.suspended_at = Time.now user.suspended_till = 200.years.from_now @@ -174,7 +183,7 @@ class ImportScripts::PunBB < ImportScripts::Base failed += 1 end else - puts "Not found: #{b['email']}" + puts "Not found: #{b["email"]}" failed += 1 end @@ -189,15 +198,15 @@ class ImportScripts::PunBB < ImportScripts::Base s.gsub!(/(?:.*)/, '\1') # Some links look like this: http://www.onegameamonth.com - s.gsub!(/(.+)<\/a>/, '[\2](\1)') + s.gsub!(%r{(.+)}, '[\2](\1)') # Many phpbb bbcode tags have a hash attached to them. Examples: # [url=https://google.com:1qh1i7ky]click here[/url:1qh1i7ky] # [quote="cybereality":b0wtlzex]Some text.[/quote:b0wtlzex] - s.gsub!(/:(?:\w{8})\]/, ']') + s.gsub!(/:(?:\w{8})\]/, "]") # Remove mybb video tags. - s.gsub!(/(^\[video=.*?\])|(\[\/video\]$)/, '') + s.gsub!(%r{(^\[video=.*?\])|(\[/video\]$)}, "") s = CGI.unescapeHTML(s) @@ -205,7 +214,7 @@ class ImportScripts::PunBB < ImportScripts::Base # [http://answers.yahoo.com/question/index ... 223AAkkPli](http://answers.yahoo.com/question/index?qid=20070920134223AAkkPli) # # Work around it for now: - s.gsub!(/\[http(s)?:\/\/(www\.)?/, '[') + s.gsub!(%r{\[http(s)?://(www\.)?}, "[") s end diff --git a/script/import_scripts/quandora/export.rb b/script/import_scripts/quandora/export.rb index e1f87b7ec3..fbbe146ca6 100644 --- a/script/import_scripts/quandora/export.rb +++ b/script/import_scripts/quandora/export.rb @@ -1,25 +1,25 @@ # frozen_string_literal: true -require 'yaml' -require_relative 'quandora_api' +require "yaml" +require_relative "quandora_api" def load_config(file) - config = YAML::load_file(File.join(__dir__, file)) - @domain = config['domain'] - @username = config['username'] - @password = config['password'] + config = YAML.load_file(File.join(__dir__, file)) + @domain = config["domain"] + @username = config["username"] + @password = config["password"] end def export api = QuandoraApi.new @domain, @username, @password bases = api.list_bases bases.each do |base| - question_list = api.list_questions base['objectId'], 1000 + question_list = api.list_questions base["objectId"], 1000 question_list.each do |q| - question_id = q['uid'] + question_id = q["uid"] question = api.get_question question_id - File.open("output/#{question_id}.json", 'w') do |f| - puts question['title'] + File.open("output/#{question_id}.json", "w") do |f| + puts question["title"] f.write question.to_json f.close end diff --git a/script/import_scripts/quandora/import.rb b/script/import_scripts/quandora/import.rb index 7df8be302c..a3dc5dfe29 100644 --- a/script/import_scripts/quandora/import.rb +++ b/script/import_scripts/quandora/import.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true -require_relative './quandora_question.rb' +require_relative "./quandora_question.rb" require File.expand_path(File.dirname(__FILE__) + "/../base.rb") class ImportScripts::Quandora < ImportScripts::Base - JSON_FILES_DIR = "output" def initialize @@ -12,8 +11,8 @@ class ImportScripts::Quandora < ImportScripts::Base @system_user = Discourse.system_user @questions = [] Dir.foreach(JSON_FILES_DIR) do |filename| - next if filename == ('.') || filename == ('..') - question = File.read JSON_FILES_DIR + '/' + filename + next if filename == (".") || filename == ("..") + question = File.read JSON_FILES_DIR + "/" + filename @questions << question end end @@ -33,9 +32,7 @@ class ImportScripts::Quandora < ImportScripts::Base q = QuandoraQuestion.new question import_users q.users created_topic = import_topic q.topic - if created_topic - import_posts q.replies, created_topic.topic_id - end + import_posts q.replies, created_topic.topic_id if created_topic topics += 1 print_status topics, total end @@ -43,9 +40,7 @@ class ImportScripts::Quandora < ImportScripts::Base end def import_users(users) - users.each do |user| - create_user user, user[:id] - end + users.each { |user| create_user user, user[:id] } end def import_topic(topic) @@ -54,7 +49,7 @@ class ImportScripts::Quandora < ImportScripts::Base post = Post.find(post_id) # already imported this topic else topic[:user_id] = user_id_from_imported_user_id(topic[:author_id]) || -1 - topic[:category] = 'quandora-import' + topic[:category] = "quandora-import" post = create_post(topic, topic[:id]) @@ -68,9 +63,7 @@ class ImportScripts::Quandora < ImportScripts::Base end def import_posts(posts, topic_id) - posts.each do |post| - import_post post, topic_id - end + posts.each { |post| import_post post, topic_id } end def import_post(post, topic_id) @@ -91,6 +84,4 @@ class ImportScripts::Quandora < ImportScripts::Base end end -if __FILE__ == $0 - ImportScripts::Quandora.new.perform -end +ImportScripts::Quandora.new.perform if __FILE__ == $0 diff --git a/script/import_scripts/quandora/quandora_api.rb b/script/import_scripts/quandora/quandora_api.rb index 747473bb79..9a74308772 100644 --- a/script/import_scripts/quandora/quandora_api.rb +++ b/script/import_scripts/quandora/quandora_api.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true -require 'base64' -require 'json' +require "base64" +require "json" class QuandoraApi - attr_accessor :domain, :username, :password def initialize(domain, username, password) @@ -38,18 +37,18 @@ class QuandoraApi def list_bases response = request list_bases_url - response['data'] + response["data"] end def list_questions(kb_id, limit = nil) url = list_questions_url(kb_id, limit) response = request url - response['data']['result'] + response["data"]["result"] end def get_question(question_id) url = "#{base_url @domain}/q/#{question_id}" response = request url - response['data'] + response["data"] end end diff --git a/script/import_scripts/quandora/quandora_question.rb b/script/import_scripts/quandora/quandora_question.rb index abbaaeeda6..767dad16fc 100644 --- a/script/import_scripts/quandora/quandora_question.rb +++ b/script/import_scripts/quandora/quandora_question.rb @@ -1,28 +1,27 @@ # frozen_string_literal: true -require 'json' -require 'cgi' -require 'time' +require "json" +require "cgi" +require "time" class QuandoraQuestion - def initialize(question_json) @question = JSON.parse question_json end def topic topic = {} - topic[:id] = @question['uid'] - topic[:author_id] = @question['author']['uid'] - topic[:title] = unescape @question['title'] - topic[:raw] = unescape @question['content'] - topic[:created_at] = Time.parse @question['created'] + topic[:id] = @question["uid"] + topic[:author_id] = @question["author"]["uid"] + topic[:title] = unescape @question["title"] + topic[:raw] = unescape @question["content"] + topic[:created_at] = Time.parse @question["created"] topic end def users users = {} - user = user_from_author @question['author'] + user = user_from_author @question["author"] users[user[:id]] = user replies.each do |reply| user = user_from_author reply[:author] @@ -32,12 +31,12 @@ class QuandoraQuestion end def user_from_author(author) - email = author['email'] - email = "#{author['uid']}@noemail.com" unless email + email = author["email"] + email = "#{author["uid"]}@noemail.com" unless email user = {} - user[:id] = author['uid'] - user[:name] = "#{author['firstName']} #{author['lastName']}" + user[:id] = author["uid"] + user[:name] = "#{author["firstName"]} #{author["lastName"]}" user[:email] = email user[:staged] = true user @@ -45,26 +44,20 @@ class QuandoraQuestion def replies posts = [] - answers = @question['answersList'] - comments = @question['comments'] - comments.each_with_index do |comment, i| - posts << post_from_comment(comment, i, @question) - end + answers = @question["answersList"] + comments = @question["comments"] + comments.each_with_index { |comment, i| posts << post_from_comment(comment, i, @question) } answers.each do |answer| posts << post_from_answer(answer) - comments = answer['comments'] - comments.each_with_index do |comment, i| - posts << post_from_comment(comment, i, answer) - end + comments = answer["comments"] + comments.each_with_index { |comment, i| posts << post_from_comment(comment, i, answer) } end order_replies posts end def order_replies(posts) posts = posts.sort_by { |p| p[:created_at] } - posts.each_with_index do |p, i| - p[:post_number] = i + 2 - end + posts.each_with_index { |p, i| p[:post_number] = i + 2 } posts.each do |p| parent = posts.select { |pp| pp[:id] == p[:parent_id] } p[:reply_to_post_number] = parent[0][:post_number] if parent.size > 0 @@ -74,35 +67,35 @@ class QuandoraQuestion def post_from_answer(answer) post = {} - post[:id] = answer['uid'] - post[:parent_id] = @question['uid'] - post[:author] = answer['author'] - post[:author_id] = answer['author']['uid'] - post[:raw] = unescape answer['content'] - post[:created_at] = Time.parse answer['created'] + post[:id] = answer["uid"] + post[:parent_id] = @question["uid"] + post[:author] = answer["author"] + post[:author_id] = answer["author"]["uid"] + post[:raw] = unescape answer["content"] + post[:created_at] = Time.parse answer["created"] post end def post_from_comment(comment, index, parent) - if comment['created'] - created_at = Time.parse comment['created'] + if comment["created"] + created_at = Time.parse comment["created"] else - created_at = Time.parse parent['created'] + created_at = Time.parse parent["created"] end - parent_id = parent['uid'] - parent_id = "#{parent['uid']}-#{index - 1}" if index > 0 + parent_id = parent["uid"] + parent_id = "#{parent["uid"]}-#{index - 1}" if index > 0 post = {} - id = "#{parent['uid']}-#{index}" + id = "#{parent["uid"]}-#{index}" post[:id] = id post[:parent_id] = parent_id - post[:author] = comment['author'] - post[:author_id] = comment['author']['uid'] - post[:raw] = unescape comment['text'] + post[:author] = comment["author"] + post[:author_id] = comment["author"]["uid"] + post[:raw] = unescape comment["text"] post[:created_at] = created_at post end - private + private def unescape(html) return nil unless html diff --git a/script/import_scripts/quandora/test/test_data.rb b/script/import_scripts/quandora/test/test_data.rb index 3166d6c44d..172e753e31 100644 --- a/script/import_scripts/quandora/test/test_data.rb +++ b/script/import_scripts/quandora/test/test_data.rb @@ -1,5 +1,6 @@ - # frozen_string_literal: true - BASES = '{ +# frozen_string_literal: true +BASES = + '{ "type" : "kbase", "data" : [ { "objectId" : "90b1ccf3-35aa-4d6f-848e-e7c122d92c58", @@ -9,7 +10,8 @@ } ] }' - QUESTIONS = '{ +QUESTIONS = + '{ "type": "question-search-result", "data": { "totalSize": 445, @@ -50,7 +52,8 @@ } }' - QUESTION = '{ +QUESTION = + '{ "type" : "question", "data" : { "uid" : "de20ed0a-5fe5-48a5-9c14-d854f9af99f1", diff --git a/script/import_scripts/quandora/test/test_quandora_api.rb b/script/import_scripts/quandora/test/test_quandora_api.rb index 784ba4fb85..a167ca9ad7 100644 --- a/script/import_scripts/quandora/test/test_quandora_api.rb +++ b/script/import_scripts/quandora/test/test_quandora_api.rb @@ -1,21 +1,20 @@ # frozen_string_literal: true -require 'minitest/autorun' -require 'yaml' -require_relative '../quandora_api.rb' -require_relative './test_data.rb' +require "minitest/autorun" +require "yaml" +require_relative "../quandora_api.rb" +require_relative "./test_data.rb" class TestQuandoraApi < Minitest::Test - DEBUG = false def initialize(args) - config = YAML::load_file(File.join(__dir__, 'config.yml')) - @domain = config['domain'] - @username = config['username'] - @password = config['password'] - @kb_id = config['kb_id'] - @question_id = config['question_id'] + config = YAML.load_file(File.join(__dir__, "config.yml")) + @domain = config["domain"] + @username = config["username"] + @password = config["password"] + @kb_id = config["kb_id"] + @question_id = config["question_id"] super args end @@ -30,19 +29,19 @@ class TestQuandoraApi < Minitest::Test end def test_base_url - assert_equal 'https://mydomain.quandora.com/m/json', @quandora.base_url('mydomain') + assert_equal "https://mydomain.quandora.com/m/json", @quandora.base_url("mydomain") end def test_auth_header - user = 'Aladdin' - password = 'open sesame' + user = "Aladdin" + password = "open sesame" auth_header = @quandora.auth_header user, password - assert_equal 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==', auth_header[:Authorization] + assert_equal "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", auth_header[:Authorization] end def test_list_bases_element_has_expected_structure element = @quandora.list_bases[0] - expected = JSON.parse(BASES)['data'][0] + expected = JSON.parse(BASES)["data"][0] debug element check_keys expected, element end @@ -50,24 +49,24 @@ class TestQuandoraApi < Minitest::Test def test_list_questions_has_expected_structure response = @quandora.list_questions @kb_id, 1 debug response - check_keys JSON.parse(QUESTIONS)['data']['result'][0], response[0] + check_keys JSON.parse(QUESTIONS)["data"]["result"][0], response[0] end def test_get_question_has_expected_structure question = @quandora.get_question @question_id - expected = JSON.parse(QUESTION)['data'] + expected = JSON.parse(QUESTION)["data"] check_keys expected, question - expected_comment = expected['comments'][0] - actual_comment = question['comments'][0] + expected_comment = expected["comments"][0] + actual_comment = question["comments"][0] check_keys expected_comment, actual_comment - expected_answer = expected['answersList'][1] - actual_answer = question['answersList'][0] + expected_answer = expected["answersList"][1] + actual_answer = question["answersList"][0] check_keys expected_answer, actual_answer - expected_answer_comment = expected_answer['comments'][0] - actual_answer_comment = actual_answer['comments'][0] + expected_answer_comment = expected_answer["comments"][0] + actual_answer_comment = actual_answer["comments"][0] check_keys expected_answer_comment, actual_answer_comment end @@ -75,18 +74,16 @@ class TestQuandoraApi < Minitest::Test def check_keys(expected, actual) msg = "### caller[0]:\nKey not found in actual keys: #{actual.keys}\n" - expected.keys.each do |k| - assert (actual.keys.include? k), "#{k}" - end + expected.keys.each { |k| assert (actual.keys.include? k), "#{k}" } end def debug(message, show = false) if show || DEBUG - puts '### ' + caller[0] - puts '' + puts "### " + caller[0] + puts "" puts message - puts '' - puts '' + puts "" + puts "" end end end diff --git a/script/import_scripts/quandora/test/test_quandora_question.rb b/script/import_scripts/quandora/test/test_quandora_question.rb index 28b5dd9885..6044951c5b 100644 --- a/script/import_scripts/quandora/test/test_quandora_question.rb +++ b/script/import_scripts/quandora/test/test_quandora_question.rb @@ -1,47 +1,46 @@ # frozen_string_literal: true -require 'minitest/autorun' -require 'cgi' -require 'time' -require_relative '../quandora_question.rb' -require_relative './test_data.rb' +require "minitest/autorun" +require "cgi" +require "time" +require_relative "../quandora_question.rb" +require_relative "./test_data.rb" class TestQuandoraQuestion < Minitest::Test - def setup - @data = JSON.parse(QUESTION)['data'] + @data = JSON.parse(QUESTION)["data"] @question = QuandoraQuestion.new @data.to_json end def test_topic topic = @question.topic - assert_equal @data['uid'], topic[:id] - assert_equal @data['author']['uid'], topic[:author_id] - assert_equal unescape(@data['title']), topic[:title] - assert_equal unescape(@data['content']), topic[:raw] - assert_equal Time.parse(@data['created']), topic[:created_at] + assert_equal @data["uid"], topic[:id] + assert_equal @data["author"]["uid"], topic[:author_id] + assert_equal unescape(@data["title"]), topic[:title] + assert_equal unescape(@data["content"]), topic[:raw] + assert_equal Time.parse(@data["created"]), topic[:created_at] end def test_user_from_author author = {} - author['uid'] = 'uid' - author['firstName'] = 'Joe' - author['lastName'] = 'Schmoe' - author['email'] = 'joe.schmoe@mydomain.com' + author["uid"] = "uid" + author["firstName"] = "Joe" + author["lastName"] = "Schmoe" + author["email"] = "joe.schmoe@mydomain.com" user = @question.user_from_author author - assert_equal 'uid', user[:id] - assert_equal 'Joe Schmoe', user[:name] - assert_equal 'joe.schmoe@mydomain.com', user[:email] + assert_equal "uid", user[:id] + assert_equal "Joe Schmoe", user[:name] + assert_equal "joe.schmoe@mydomain.com", user[:email] assert_equal true, user[:staged] end def test_user_from_author_with_no_email author = {} - author['uid'] = 'foo' + author["uid"] = "foo" user = @question.user_from_author author - assert_equal 'foo@noemail.com', user[:email] + assert_equal "foo@noemail.com", user[:email] end def test_replies @@ -57,77 +56,77 @@ class TestQuandoraQuestion < Minitest::Test assert_equal nil, replies[2][:reply_to_post_number] assert_equal 4, replies[3][:reply_to_post_number] assert_equal 3, replies[4][:reply_to_post_number] - assert_equal '2013-01-07 04:59:56 UTC', replies[0][:created_at].to_s - assert_equal '2013-01-08 16:49:32 UTC', replies[1][:created_at].to_s - assert_equal '2016-01-20 15:38:55 UTC', replies[2][:created_at].to_s - assert_equal '2016-01-21 15:38:55 UTC', replies[3][:created_at].to_s - assert_equal '2016-01-22 15:38:55 UTC', replies[4][:created_at].to_s + assert_equal "2013-01-07 04:59:56 UTC", replies[0][:created_at].to_s + assert_equal "2013-01-08 16:49:32 UTC", replies[1][:created_at].to_s + assert_equal "2016-01-20 15:38:55 UTC", replies[2][:created_at].to_s + assert_equal "2016-01-21 15:38:55 UTC", replies[3][:created_at].to_s + assert_equal "2016-01-22 15:38:55 UTC", replies[4][:created_at].to_s end def test_post_from_answer answer = {} - answer['uid'] = 'uid' - answer['content'] = 'content' - answer['created'] = '2013-01-06T18:24:54.62Z' - answer['author'] = { 'uid' => 'auid' } + answer["uid"] = "uid" + answer["content"] = "content" + answer["created"] = "2013-01-06T18:24:54.62Z" + answer["author"] = { "uid" => "auid" } post = @question.post_from_answer answer - assert_equal 'uid', post[:id] + assert_equal "uid", post[:id] assert_equal @question.topic[:id], post[:parent_id] - assert_equal answer['author'], post[:author] - assert_equal 'auid', post[:author_id] - assert_equal 'content', post[:raw] - assert_equal Time.parse('2013-01-06T18:24:54.62Z'), post[:created_at] + assert_equal answer["author"], post[:author] + assert_equal "auid", post[:author_id] + assert_equal "content", post[:raw] + assert_equal Time.parse("2013-01-06T18:24:54.62Z"), post[:created_at] end def test_post_from_comment comment = {} - comment['text'] = 'text' - comment['created'] = '2013-01-06T18:24:54.62Z' - comment['author'] = { 'uid' => 'auid' } - parent = { 'uid' => 'parent-uid' } + comment["text"] = "text" + comment["created"] = "2013-01-06T18:24:54.62Z" + comment["author"] = { "uid" => "auid" } + parent = { "uid" => "parent-uid" } post = @question.post_from_comment comment, 0, parent - assert_equal 'parent-uid-0', post[:id] - assert_equal 'parent-uid', post[:parent_id] - assert_equal comment['author'], post[:author] - assert_equal 'auid', post[:author_id] - assert_equal 'text', post[:raw] - assert_equal Time.parse('2013-01-06T18:24:54.62Z'), post[:created_at] + assert_equal "parent-uid-0", post[:id] + assert_equal "parent-uid", post[:parent_id] + assert_equal comment["author"], post[:author] + assert_equal "auid", post[:author_id] + assert_equal "text", post[:raw] + assert_equal Time.parse("2013-01-06T18:24:54.62Z"), post[:created_at] end def test_post_from_comment_uses_parent_created_if_necessary comment = {} - comment['author'] = { 'uid' => 'auid' } - parent = { 'created' => '2013-01-06T18:24:54.62Z' } + comment["author"] = { "uid" => "auid" } + parent = { "created" => "2013-01-06T18:24:54.62Z" } post = @question.post_from_comment comment, 0, parent - assert_equal Time.parse('2013-01-06T18:24:54.62Z'), post[:created_at] + assert_equal Time.parse("2013-01-06T18:24:54.62Z"), post[:created_at] end def test_post_from_comment_uses_previous_comment_as_parent comment = {} - comment['author'] = { 'uid' => 'auid' } - parent = { 'uid' => 'parent-uid', 'created' => '2013-01-06T18:24:54.62Z' } + comment["author"] = { "uid" => "auid" } + parent = { "uid" => "parent-uid", "created" => "2013-01-06T18:24:54.62Z" } post = @question.post_from_comment comment, 1, parent - assert_equal 'parent-uid-1', post[:id] - assert_equal 'parent-uid-0', post[:parent_id] - assert_equal Time.parse('2013-01-06T18:24:54.62Z'), post[:created_at] + assert_equal "parent-uid-1", post[:id] + assert_equal "parent-uid-0", post[:parent_id] + assert_equal Time.parse("2013-01-06T18:24:54.62Z"), post[:created_at] end def test_users users = @question.users assert_equal 5, users.size - assert_equal 'Ida Inquisitive', users[0][:name] - assert_equal 'Harry Helpful', users[1][:name] - assert_equal 'Sam Smarty-Pants', users[2][:name] - assert_equal 'Greta Greatful', users[3][:name] - assert_equal 'Eddy Excited', users[4][:name] + assert_equal "Ida Inquisitive", users[0][:name] + assert_equal "Harry Helpful", users[1][:name] + assert_equal "Sam Smarty-Pants", users[2][:name] + assert_equal "Greta Greatful", users[3][:name] + assert_equal "Eddy Excited", users[4][:name] end private diff --git a/script/import_scripts/question2answer.rb b/script/import_scripts/question2answer.rb index acd5b70beb..3820b8050b 100644 --- a/script/import_scripts/question2answer.rb +++ b/script/import_scripts/question2answer.rb @@ -1,21 +1,21 @@ # frozen_string_literal: true -require 'mysql2' +require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require 'htmlentities' -require 'php_serialize' # https://github.com/jqr/php-serialize +require "htmlentities" +require "php_serialize" # https://github.com/jqr/php-serialize class ImportScripts::Question2Answer < ImportScripts::Base BATCH_SIZE = 1000 # CHANGE THESE BEFORE RUNNING THE IMPORTER - DB_HOST ||= ENV['DB_HOST'] || "localhost" - DB_NAME ||= ENV['DB_NAME'] || "qa_db" - DB_PW ||= ENV['DB_PW'] || "" - DB_USER ||= ENV['DB_USER'] || "root" - TIMEZONE ||= ENV['TIMEZONE'] || "America/Los_Angeles" - TABLE_PREFIX ||= ENV['TABLE_PREFIX'] || "qa_" + DB_HOST ||= ENV["DB_HOST"] || "localhost" + DB_NAME ||= ENV["DB_NAME"] || "qa_db" + DB_PW ||= ENV["DB_PW"] || "" + DB_USER ||= ENV["DB_USER"] || "root" + TIMEZONE ||= ENV["TIMEZONE"] || "America/Los_Angeles" + TABLE_PREFIX ||= ENV["TABLE_PREFIX"] || "qa_" def initialize super @@ -26,12 +26,8 @@ class ImportScripts::Question2Answer < ImportScripts::Base @htmlentities = HTMLEntities.new - @client = Mysql2::Client.new( - host: DB_HOST, - username: DB_USER, - password: DB_PW, - database: DB_NAME - ) + @client = + Mysql2::Client.new(host: DB_HOST, username: DB_USER, password: DB_PW, database: DB_NAME) end def execute @@ -51,11 +47,16 @@ class ImportScripts::Question2Answer < ImportScripts::Base # only import users that have posted or voted on Q2A # if you want to import all users, just leave out the WHERE and everything after it (and remove line 95 as well) - user_count = mysql_query("SELECT COUNT(userid) count FROM #{TABLE_PREFIX}users u WHERE EXISTS (SELECT 1 FROM #{TABLE_PREFIX}posts p WHERE p.userid=u.userid) or EXISTS (SELECT 1 FROM #{TABLE_PREFIX}uservotes uv WHERE u.userid=uv.userid)").first["count"] + user_count = + mysql_query( + "SELECT COUNT(userid) count FROM #{TABLE_PREFIX}users u WHERE EXISTS (SELECT 1 FROM #{TABLE_PREFIX}posts p WHERE p.userid=u.userid) or EXISTS (SELECT 1 FROM #{TABLE_PREFIX}uservotes uv WHERE u.userid=uv.userid)", + ).first[ + "count" + ] last_user_id = -1 batches(BATCH_SIZE) do |offset| - users = mysql_query(<<-SQL + users = mysql_query(<<-SQL).to_a SELECT u.userid AS id, u.email, u.handle AS username, u.created AS created_at, u.loggedin AS last_sign_in_at, u.avatarblobid FROM #{TABLE_PREFIX}users u WHERE u.userid > #{last_user_id} @@ -63,7 +64,6 @@ class ImportScripts::Question2Answer < ImportScripts::Base ORDER BY u.userid LIMIT #{BATCH_SIZE} SQL - ).to_a break if users.empty? last_user_id = users[-1]["id"] @@ -73,18 +73,17 @@ class ImportScripts::Question2Answer < ImportScripts::Base email = user["email"].presence username = @htmlentities.decode(user["email"]).strip.split("@").first - avatar_url = "https://your_image_bucket/#{user['cdn_slug']}" if user['cdn_slug'] + avatar_url = "https://your_image_bucket/#{user["cdn_slug"]}" if user["cdn_slug"] { id: user["id"], - name: "#{user['username']}", - username: "#{user['username']}", - password: user['password'], + name: "#{user["username"]}", + username: "#{user["username"]}", + password: user["password"], email: email, created_at: user["created_at"], last_seen_at: user["last_sign_in_at"], - post_create_action: proc do |u| - @old_username_to_new_usernames[user["username"]] = u.username - end + post_create_action: + proc { |u| @old_username_to_new_usernames[user["username"]] = u.username }, } end end @@ -93,7 +92,10 @@ class ImportScripts::Question2Answer < ImportScripts::Base def import_categories puts "", "importing top level categories..." - categories = mysql_query("SELECT categoryid, parentid, title, position FROM #{TABLE_PREFIX}categories ORDER BY categoryid").to_a + categories = + mysql_query( + "SELECT categoryid, parentid, title, position FROM #{TABLE_PREFIX}categories ORDER BY categoryid", + ).to_a top_level_categories = categories.select { |c| c["parentid"].nil? } @@ -101,7 +103,7 @@ class ImportScripts::Question2Answer < ImportScripts::Base { id: category["categoryid"], name: @htmlentities.decode(category["title"]).strip, - position: category["position"] + position: category["position"], } end @@ -122,7 +124,7 @@ class ImportScripts::Question2Answer < ImportScripts::Base id: category["categoryid"], name: @htmlentities.decode(category["title"]).strip, position: category["position"], - parent_category_id: category_id_from_imported_category_id(category["parentid"]) + parent_category_id: category_id_from_imported_category_id(category["parentid"]), } end end @@ -130,12 +132,15 @@ class ImportScripts::Question2Answer < ImportScripts::Base def import_topics puts "", "importing topics..." - topic_count = mysql_query("SELECT COUNT(postid) count FROM #{TABLE_PREFIX}posts WHERE type = 'Q'").first["count"] + topic_count = + mysql_query("SELECT COUNT(postid) count FROM #{TABLE_PREFIX}posts WHERE type = 'Q'").first[ + "count" + ] last_topic_id = -1 batches(BATCH_SIZE) do |offset| - topics = mysql_query(<<-SQL + topics = mysql_query(<<-SQL).to_a SELECT p.postid, p.type, p.categoryid, p.closedbyid, p.userid postuserid, p.views, p.created, p.title, p.content raw FROM #{TABLE_PREFIX}posts p WHERE type = 'Q' @@ -143,7 +148,6 @@ class ImportScripts::Question2Answer < ImportScripts::Base ORDER BY p.postid LIMIT #{BATCH_SIZE} SQL - ).to_a break if topics.empty? @@ -179,20 +183,19 @@ class ImportScripts::Question2Answer < ImportScripts::Base if topic.present? title_slugified = slugify(thread["title"], false, 50) if thread["title"].present? url_slug = "qa/#{thread["postid"]}/#{title_slugified}" if thread["title"].present? - Permalink.create(url: url_slug, topic_id: topic[:topic_id].to_i) if url_slug.present? && topic[:topic_id].present? + if url_slug.present? && topic[:topic_id].present? + Permalink.create(url: url_slug, topic_id: topic[:topic_id].to_i) + end end end - end end def slugify(title, ascii_only, max_length) - words = title.downcase.gsub(/[^a-zA-Z0-9\s]/, '').split(" ") + words = title.downcase.gsub(/[^a-zA-Z0-9\s]/, "").split(" ") word_lengths = {} - words.each_with_index do |word, idx| - word_lengths[idx] = word.length - end + words.each_with_index { |word, idx| word_lengths[idx] = word.length } remaining = max_length if word_lengths.inject(0) { |sum, (_, v)| sum + v } > remaining @@ -211,17 +214,16 @@ class ImportScripts::Question2Answer < ImportScripts::Base def import_posts puts "", "importing posts..." - post_count = mysql_query(<<-SQL + post_count = mysql_query(<<-SQL).first["count"] SELECT COUNT(postid) count FROM #{TABLE_PREFIX}posts p WHERE p.parentid IS NOT NULL SQL - ).first["count"] last_post_id = -1 batches(BATCH_SIZE) do |offset| - posts = mysql_query(<<-SQL + posts = mysql_query(<<-SQL).to_a SELECT p.postid, p.type, p.parentid, p.categoryid, p.closedbyid, p.userid, p.views, p.created, p.title, p.content, parent.type AS parenttype, parent.parentid AS qid FROM #{TABLE_PREFIX}posts p @@ -233,7 +235,6 @@ class ImportScripts::Question2Answer < ImportScripts::Base ORDER BY p.postid LIMIT #{BATCH_SIZE} SQL - ).to_a break if posts.empty? last_post_id = posts[-1]["postid"] @@ -250,11 +251,11 @@ class ImportScripts::Question2Answer < ImportScripts::Base # this works as long as comments can not have a comment as parent # it's always Q-A Q-C or A-C - if post['type'] == 'A' # for answers the question/topic is always the parent + if post["type"] == "A" # for answers the question/topic is always the parent topic = topic_lookup_from_imported_post_id("thread-#{post["parentid"]}") next if topic.nil? else - if post['parenttype'] == 'Q' # for comments to questions, the question/topic is the parent as well + if post["parenttype"] == "Q" # for comments to questions, the question/topic is the parent as well topic = topic_lookup_from_imported_post_id("thread-#{post["parentid"]}") next if topic.nil? else # for comments to answers, the question/topic is the parent of the parent @@ -284,7 +285,7 @@ class ImportScripts::Question2Answer < ImportScripts::Base ans = mysql_query("select postid, selchildid from qa_posts where selchildid is not null").to_a ans.each do |answer| begin - post = Post.find_by(id: post_id_from_imported_post_id("#{answer['selchildid']}")) + post = Post.find_by(id: post_id_from_imported_post_id("#{answer["selchildid"]}")) post.custom_fields["is_accepted_answer"] = "true" post.save topic = Topic.find(post.topic_id) @@ -293,20 +294,18 @@ class ImportScripts::Question2Answer < ImportScripts::Base rescue => e puts "error acting on post #{e}" end - end end def import_likes puts "", "importing likes..." - likes = mysql_query(<<-SQL + likes = mysql_query(<<-SQL).to_a SELECT postid, userid FROM #{TABLE_PREFIX}uservotes u WHERE u.vote=1 SQL - ).to_a likes.each do |like| - post = Post.find_by(id: post_id_from_imported_post_id("thread-#{like['postid']}")) + post = Post.find_by(id: post_id_from_imported_post_id("thread-#{like["postid"]}")) user = User.find_by(id: user_id_from_imported_user_id(like["userid"])) begin PostActionCreator.like(user, post) if user && post @@ -340,10 +339,10 @@ class ImportScripts::Question2Answer < ImportScripts::Base def preprocess_post_raw(raw) return "" if raw.blank? - raw.gsub!(/(.+)<\/a>/i, '[\2](\1)') - raw.gsub!(/

(.+?)<\/p>/im) { "#{$1}\n\n" } - raw.gsub!('
', "\n") - raw.gsub!(/(.*?)<\/strong>/im, '[b]\1[/b]') + raw.gsub!(%r{(.+)}i, '[\2](\1)') + raw.gsub!(%r{

(.+?)

}im) { "#{$1}\n\n" } + raw.gsub!("
", "\n") + raw.gsub!(%r{(.*?)}im, '[b]\1[/b]') # decode HTML entities raw = @htmlentities.decode(raw) @@ -355,22 +354,22 @@ class ImportScripts::Question2Answer < ImportScripts::Base # [HTML]...[/HTML] raw.gsub!(/\[html\]/i, "\n```html\n") - raw.gsub!(/\[\/html\]/i, "\n```\n") + raw.gsub!(%r{\[/html\]}i, "\n```\n") # [PHP]...[/PHP] raw.gsub!(/\[php\]/i, "\n```php\n") - raw.gsub!(/\[\/php\]/i, "\n```\n") + raw.gsub!(%r{\[/php\]}i, "\n```\n") # [HIGHLIGHT="..."] raw.gsub!(/\[highlight="?(\w+)"?\]/i) { "\n```#{$1.downcase}\n" } # [CODE]...[/CODE] # [HIGHLIGHT]...[/HIGHLIGHT] - raw.gsub!(/\[\/?code\]/i, "\n```\n") - raw.gsub!(/\[\/?highlight\]/i, "\n```\n") + raw.gsub!(%r{\[/?code\]}i, "\n```\n") + raw.gsub!(%r{\[/?highlight\]}i, "\n```\n") # [SAMP]...[/SAMP] - raw.gsub!(/\[\/?samp\]/i, "`") + raw.gsub!(%r{\[/?samp\]}i, "`") # replace all chevrons with HTML entities # NOTE: must be done @@ -385,16 +384,16 @@ class ImportScripts::Question2Answer < ImportScripts::Base raw.gsub!("\u2603", ">") # [URL=...]...[/URL] - raw.gsub!(/\[url="?([^"]+?)"?\](.*?)\[\/url\]/im) { "[#{$2.strip}](#{$1})" } - raw.gsub!(/\[url="?(.+?)"?\](.+)\[\/url\]/im) { "[#{$2.strip}](#{$1})" } + raw.gsub!(%r{\[url="?([^"]+?)"?\](.*?)\[/url\]}im) { "[#{$2.strip}](#{$1})" } + raw.gsub!(%r{\[url="?(.+?)"?\](.+)\[/url\]}im) { "[#{$2.strip}](#{$1})" } # [URL]...[/URL] # [MP3]...[/MP3] - raw.gsub!(/\[\/?url\]/i, "") - raw.gsub!(/\[\/?mp3\]/i, "") + raw.gsub!(%r{\[/?url\]}i, "") + raw.gsub!(%r{\[/?mp3\]}i, "") # [MENTION][/MENTION] - raw.gsub!(/\[mention\](.+?)\[\/mention\]/i) do + raw.gsub!(%r{\[mention\](.+?)\[/mention\]}i) do old_username = $1 if @old_username_to_new_usernames.has_key?(old_username) old_username = @old_username_to_new_usernames[old_username] @@ -403,31 +402,31 @@ class ImportScripts::Question2Answer < ImportScripts::Base end # [FONT=blah] and [COLOR=blah] - raw.gsub!(/\[FONT=.*?\](.*?)\[\/FONT\]/im, '\1') - raw.gsub!(/\[COLOR=.*?\](.*?)\[\/COLOR\]/im, '\1') - raw.gsub!(/\[COLOR=#.*?\](.*?)\[\/COLOR\]/im, '\1') + raw.gsub!(%r{\[FONT=.*?\](.*?)\[/FONT\]}im, '\1') + raw.gsub!(%r{\[COLOR=.*?\](.*?)\[/COLOR\]}im, '\1') + raw.gsub!(%r{\[COLOR=#.*?\](.*?)\[/COLOR\]}im, '\1') - raw.gsub!(/\[SIZE=.*?\](.*?)\[\/SIZE\]/im, '\1') - raw.gsub!(/\[h=.*?\](.*?)\[\/h\]/im, '\1') + raw.gsub!(%r{\[SIZE=.*?\](.*?)\[/SIZE\]}im, '\1') + raw.gsub!(%r{\[h=.*?\](.*?)\[/h\]}im, '\1') # [CENTER]...[/CENTER] - raw.gsub!(/\[CENTER\](.*?)\[\/CENTER\]/im, '\1') + raw.gsub!(%r{\[CENTER\](.*?)\[/CENTER\]}im, '\1') # [INDENT]...[/INDENT] - raw.gsub!(/\[INDENT\](.*?)\[\/INDENT\]/im, '\1') - raw.gsub!(/\[TABLE\](.*?)\[\/TABLE\]/im, '\1') - raw.gsub!(/\[TR\](.*?)\[\/TR\]/im, '\1') - raw.gsub!(/\[TD\](.*?)\[\/TD\]/im, '\1') - raw.gsub!(/\[TD="?.*?"?\](.*?)\[\/TD\]/im, '\1') + raw.gsub!(%r{\[INDENT\](.*?)\[/INDENT\]}im, '\1') + raw.gsub!(%r{\[TABLE\](.*?)\[/TABLE\]}im, '\1') + raw.gsub!(%r{\[TR\](.*?)\[/TR\]}im, '\1') + raw.gsub!(%r{\[TD\](.*?)\[/TD\]}im, '\1') + raw.gsub!(%r{\[TD="?.*?"?\](.*?)\[/TD\]}im, '\1') # [QUOTE]...[/QUOTE] - raw.gsub!(/\[quote\](.+?)\[\/quote\]/im) { |quote| - quote.gsub!(/\[quote\](.+?)\[\/quote\]/im) { "\n#{$1}\n" } + raw.gsub!(%r{\[quote\](.+?)\[/quote\]}im) do |quote| + quote.gsub!(%r{\[quote\](.+?)\[/quote\]}im) { "\n#{$1}\n" } quote.gsub!(/\n(.+?)/) { "\n> #{$1}" } - } + end # [QUOTE=]...[/QUOTE] - raw.gsub!(/\[quote=([^;\]]+)\](.+?)\[\/quote\]/im) do + raw.gsub!(%r{\[quote=([^;\]]+)\](.+?)\[/quote\]}im) do old_username, quote = $1, $2 if @old_username_to_new_usernames.has_key?(old_username) old_username = @old_username_to_new_usernames[old_username] @@ -436,31 +435,33 @@ class ImportScripts::Question2Answer < ImportScripts::Base end # [YOUTUBE][/YOUTUBE] - raw.gsub!(/\[youtube\](.+?)\[\/youtube\]/i) { "\n//youtu.be/#{$1}\n" } + raw.gsub!(%r{\[youtube\](.+?)\[/youtube\]}i) { "\n//youtu.be/#{$1}\n" } # [VIDEO=youtube;]...[/VIDEO] - raw.gsub!(/\[video=youtube;([^\]]+)\].*?\[\/video\]/i) { "\n//youtu.be/#{$1}\n" } + raw.gsub!(%r{\[video=youtube;([^\]]+)\].*?\[/video\]}i) { "\n//youtu.be/#{$1}\n" } # More Additions .... # [spoiler=Some hidden stuff]SPOILER HERE!![/spoiler] - raw.gsub!(/\[spoiler="?(.+?)"?\](.+?)\[\/spoiler\]/im) { "\n#{$1}\n[spoiler]#{$2}[/spoiler]\n" } + raw.gsub!(%r{\[spoiler="?(.+?)"?\](.+?)\[/spoiler\]}im) do + "\n#{$1}\n[spoiler]#{$2}[/spoiler]\n" + end # [IMG][IMG]http://i63.tinypic.com/akga3r.jpg[/IMG][/IMG] - raw.gsub!(/\[IMG\]\[IMG\](.+?)\[\/IMG\]\[\/IMG\]/i) { "[IMG]#{$1}[/IMG]" } + raw.gsub!(%r{\[IMG\]\[IMG\](.+?)\[/IMG\]\[/IMG\]}i) { "[IMG]#{$1}[/IMG]" } # convert list tags to ul and list=1 tags to ol # (basically, we're only missing list=a here...) # (https://meta.discourse.org/t/phpbb-3-importer-old/17397) - raw.gsub!(/\[list\](.*?)\[\/list\]/im, '[ul]\1[/ul]') - raw.gsub!(/\[list=1\](.*?)\[\/list\]/im, '[ol]\1[/ol]') - raw.gsub!(/\[list\](.*?)\[\/list:u\]/im, '[ul]\1[/ul]') - raw.gsub!(/\[list=1\](.*?)\[\/list:o\]/im, '[ol]\1[/ol]') + raw.gsub!(%r{\[list\](.*?)\[/list\]}im, '[ul]\1[/ul]') + raw.gsub!(%r{\[list=1\](.*?)\[/list\]}im, '[ol]\1[/ol]') + raw.gsub!(%r{\[list\](.*?)\[/list:u\]}im, '[ul]\1[/ul]') + raw.gsub!(%r{\[list=1\](.*?)\[/list:o\]}im, '[ol]\1[/ol]') # convert *-tags to li-tags so bbcode-to-md can do its magic on phpBB's lists: - raw.gsub!(/\[\*\]\n/, '') - raw.gsub!(/\[\*\](.*?)\[\/\*:m\]/, '[li]\1[/li]') + raw.gsub!(/\[\*\]\n/, "") + raw.gsub!(%r{\[\*\](.*?)\[/\*:m\]}, '[li]\1[/li]') raw.gsub!(/\[\*\](.*?)\n/, '[li]\1[/li]') - raw.gsub!(/\[\*=1\]/, '') + raw.gsub!(/\[\*=1\]/, "") raw.strip! raw @@ -468,7 +469,7 @@ class ImportScripts::Question2Answer < ImportScripts::Base def postprocess_post_raw(raw) # [QUOTE=;]...[/QUOTE] - raw.gsub!(/\[quote=([^;]+);(\d+)\](.+?)\[\/quote\]/im) do + raw.gsub!(%r{\[quote=([^;]+);(\d+)\](.+?)\[/quote\]}im) do old_username, post_id, quote = $1, $2, $3 if @old_username_to_new_usernames.has_key?(old_username) @@ -477,7 +478,7 @@ class ImportScripts::Question2Answer < ImportScripts::Base if topic_lookup = topic_lookup_from_imported_post_id(post_id) post_number = topic_lookup[:post_number] - topic_id = topic_lookup[:topic_id] + topic_id = topic_lookup[:topic_id] "\n[quote=\"#{old_username},post:#{post_number},topic:#{topic_id}\"]\n#{quote}\n[/quote]\n" else "\n[quote=\"#{old_username}\"]\n#{quote}\n[/quote]\n" @@ -485,11 +486,11 @@ class ImportScripts::Question2Answer < ImportScripts::Base end # remove attachments - raw.gsub!(/\[attach[^\]]*\]\d+\[\/attach\]/i, "") + raw.gsub!(%r{\[attach[^\]]*\]\d+\[/attach\]}i, "") # [THREAD][/THREAD] # ==> http://my.discourse.org/t/slug/ - raw.gsub!(/\[thread\](\d+)\[\/thread\]/i) do + raw.gsub!(%r{\[thread\](\d+)\[/thread\]}i) do thread_id = $1 if topic_lookup = topic_lookup_from_imported_post_id("thread-#{thread_id}") topic_lookup[:url] @@ -500,7 +501,7 @@ class ImportScripts::Question2Answer < ImportScripts::Base # [THREAD=]...[/THREAD] # ==> [...](http://my.discourse.org/t/slug/) - raw.gsub!(/\[thread=(\d+)\](.+?)\[\/thread\]/i) do + raw.gsub!(%r{\[thread=(\d+)\](.+?)\[/thread\]}i) do thread_id, link = $1, $2 if topic_lookup = topic_lookup_from_imported_post_id("thread-#{thread_id}") url = topic_lookup[:url] @@ -512,7 +513,7 @@ class ImportScripts::Question2Answer < ImportScripts::Base # [POST][/POST] # ==> http://my.discourse.org/t/slug// - raw.gsub!(/\[post\](\d+)\[\/post\]/i) do + raw.gsub!(%r{\[post\](\d+)\[/post\]}i) do post_id = $1 if topic_lookup = topic_lookup_from_imported_post_id(post_id) topic_lookup[:url] @@ -523,7 +524,7 @@ class ImportScripts::Question2Answer < ImportScripts::Base # [POST=]...[/POST] # ==> [...](http://my.discourse.org/t///) - raw.gsub!(/\[post=(\d+)\](.+?)\[\/post\]/i) do + raw.gsub!(%r{\[post=(\d+)\](.+?)\[/post\]}i) do post_id, link = $1, $2 if topic_lookup = topic_lookup_from_imported_post_id(post_id) url = topic_lookup[:url] @@ -537,7 +538,7 @@ class ImportScripts::Question2Answer < ImportScripts::Base end def create_permalinks - puts '', 'Creating permalinks...' + puts "", "Creating permalinks..." # topics Topic.find_each do |topic| @@ -546,7 +547,11 @@ class ImportScripts::Question2Answer < ImportScripts::Base if tcf && tcf["import_id"] question_id = tcf["import_id"][/thread-(\d)/, 0] url = "#{question_id}" - Permalink.create(url: url, topic_id: topic.id) rescue nil + begin + Permalink.create(url: url, topic_id: topic.id) + rescue StandardError + nil + end end end @@ -555,11 +560,21 @@ class ImportScripts::Question2Answer < ImportScripts::Base ccf = category.custom_fields if ccf && ccf["import_id"] - url = category.parent_category ? "#{category.parent_category.slug}/#{category.slug}" : category.slug - Permalink.create(url: url, category_id: category.id) rescue nil + url = + ( + if category.parent_category + "#{category.parent_category.slug}/#{category.slug}" + else + category.slug + end + ) + begin + Permalink.create(url: url, category_id: category.id) + rescue StandardError + nil + end end end - end def parse_timestamp(timestamp) @@ -569,7 +584,6 @@ class ImportScripts::Question2Answer < ImportScripts::Base def mysql_query(sql) @client.query(sql, cache_rows: true) end - end ImportScripts::Question2Answer.new.perform diff --git a/script/import_scripts/sfn.rb b/script/import_scripts/sfn.rb index e9270813d7..e10c9c1b0a 100644 --- a/script/import_scripts/sfn.rb +++ b/script/import_scripts/sfn.rb @@ -8,7 +8,6 @@ require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::Sfn < ImportScripts::Base - BATCH_SIZE = 100_000 MIN_CREATED_AT = "2003-11-01" @@ -96,22 +95,27 @@ class ImportScripts::Sfn < ImportScripts::Base username: email.split("@")[0], bio_raw: bio, created_at: user["created_at"], - post_create_action: proc do |newuser| - next if user["avatar"].blank? + post_create_action: + proc do |newuser| + next if user["avatar"].blank? - avatar = Tempfile.new("sfn-avatar") - avatar.write(user["avatar"].encode("ASCII-8BIT").force_encoding("UTF-8")) - avatar.rewind + avatar = Tempfile.new("sfn-avatar") + avatar.write(user["avatar"].encode("ASCII-8BIT").force_encoding("UTF-8")) + avatar.rewind - upload = UploadCreator.new(avatar, "avatar.jpg").create_for(newuser.id) - if upload.persisted? - newuser.create_user_avatar - newuser.user_avatar.update(custom_upload_id: upload.id) - newuser.update(uploaded_avatar_id: upload.id) - end + upload = UploadCreator.new(avatar, "avatar.jpg").create_for(newuser.id) + if upload.persisted? + newuser.create_user_avatar + newuser.user_avatar.update(custom_upload_id: upload.id) + newuser.update(uploaded_avatar_id: upload.id) + end - avatar.try(:close!) rescue nil - end + begin + avatar.try(:close!) + rescue StandardError + nil + end + end, } end end @@ -198,9 +202,7 @@ class ImportScripts::Sfn < ImportScripts::Base def import_categories puts "", "importing categories..." - create_categories(NEW_CATEGORIES) do |category| - { id: category, name: category } - end + create_categories(NEW_CATEGORIES) { |category| { id: category, name: category } } end def import_topics @@ -234,7 +236,7 @@ class ImportScripts::Sfn < ImportScripts::Base SQL break if topics.size < 1 - next if all_records_exist? :posts, topics.map { |t| t['id'].to_i } + next if all_records_exist? :posts, topics.map { |t| t["id"].to_i } create_posts(topics, total: topic_count, offset: offset) do |topic| next unless category_id = CATEGORY_MAPPING[topic["category_id"]] @@ -286,7 +288,7 @@ class ImportScripts::Sfn < ImportScripts::Base break if posts.size < 1 - next if all_records_exist? :posts, posts.map { |p| p['id'].to_i } + next if all_records_exist? :posts, posts.map { |p| p["id"].to_i } create_posts(posts, total: posts_count, offset: offset) do |post| next unless parent = topic_lookup_from_imported_post_id(post["topic_id"]) @@ -307,7 +309,7 @@ class ImportScripts::Sfn < ImportScripts::Base def cleanup_raw(raw) # fix some html - raw.gsub!(//i, "\n") + raw.gsub!(%r{}i, "\n") # remove "This message has been cross posted to the following eGroups: ..." raw.gsub!(/^This message has been cross posted to the following eGroups: .+\n-{3,}/i, "") # remove signatures @@ -320,7 +322,6 @@ class ImportScripts::Sfn < ImportScripts::Base @client ||= Mysql2::Client.new(username: "root", database: "sfn") @client.query(sql) end - end ImportScripts::Sfn.new.perform diff --git a/script/import_scripts/simplepress.rb b/script/import_scripts/simplepress.rb index 3375258790..b0aa7f5f11 100644 --- a/script/import_scripts/simplepress.rb +++ b/script/import_scripts/simplepress.rb @@ -1,22 +1,17 @@ # frozen_string_literal: true -require 'mysql2' +require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::SimplePress < ImportScripts::Base - - SIMPLE_PRESS_DB ||= ENV['SIMPLEPRESS_DB'] || "simplepress" + SIMPLE_PRESS_DB ||= ENV["SIMPLEPRESS_DB"] || "simplepress" TABLE_PREFIX = "wp_sf" BATCH_SIZE ||= 1000 def initialize super - @client = Mysql2::Client.new( - host: "localhost", - username: "root", - database: SIMPLE_PRESS_DB, - ) + @client = Mysql2::Client.new(host: "localhost", username: "root", database: SIMPLE_PRESS_DB) SiteSetting.max_username_length = 50 end @@ -32,10 +27,11 @@ class ImportScripts::SimplePress < ImportScripts::Base puts "", "importing users..." last_user_id = -1 - total_users = mysql_query("SELECT COUNT(*) count FROM wp_users WHERE user_email LIKE '%@%'").first["count"] + total_users = + mysql_query("SELECT COUNT(*) count FROM wp_users WHERE user_email LIKE '%@%'").first["count"] batches(BATCH_SIZE) do |offset| - users = mysql_query(<<-SQL + users = mysql_query(<<-SQL).to_a SELECT ID id, user_nicename, display_name, user_email, user_registered, user_url FROM wp_users WHERE user_email LIKE '%@%' @@ -43,7 +39,6 @@ class ImportScripts::SimplePress < ImportScripts::Base ORDER BY id LIMIT #{BATCH_SIZE} SQL - ).to_a break if users.empty? @@ -55,13 +50,12 @@ class ImportScripts::SimplePress < ImportScripts::Base user_ids_sql = user_ids.join(",") users_description = {} - mysql_query(<<-SQL + mysql_query(<<-SQL).each { |um| users_description[um["user_id"]] = um["description"] } SELECT user_id, meta_value description FROM wp_usermeta WHERE user_id IN (#{user_ids_sql}) AND meta_key = 'description' SQL - ).each { |um| users_description[um["user_id"]] = um["description"] } create_users(users, total: total_users, offset: offset) do |u| { @@ -71,7 +65,7 @@ class ImportScripts::SimplePress < ImportScripts::Base name: u["display_name"], created_at: u["user_registered"], website: u["user_url"], - bio_raw: users_description[u["id"]] + bio_raw: users_description[u["id"]], } end end @@ -80,16 +74,20 @@ class ImportScripts::SimplePress < ImportScripts::Base def import_categories puts "", "importing categories..." - categories = mysql_query(<<-SQL + categories = mysql_query(<<-SQL) SELECT forum_id, forum_name, forum_seq, forum_desc, parent FROM #{TABLE_PREFIX}forums ORDER BY forum_id SQL - ) create_categories(categories) do |c| - category = { id: c['forum_id'], name: CGI.unescapeHTML(c['forum_name']), description: CGI.unescapeHTML(c['forum_desc']), position: c['forum_seq'] } - if (parent_id = c['parent'].to_i) > 0 + category = { + id: c["forum_id"], + name: CGI.unescapeHTML(c["forum_name"]), + description: CGI.unescapeHTML(c["forum_desc"]), + position: c["forum_seq"], + } + if (parent_id = c["parent"].to_i) > 0 category[:parent_category_id] = category_id_from_imported_category_id(parent_id) end category @@ -99,10 +97,15 @@ class ImportScripts::SimplePress < ImportScripts::Base def import_topics puts "", "creating topics" - total_count = mysql_query("SELECT COUNT(*) count FROM #{TABLE_PREFIX}posts WHERE post_index = 1").first["count"] + total_count = + mysql_query("SELECT COUNT(*) count FROM #{TABLE_PREFIX}posts WHERE post_index = 1").first[ + "count" + ] batches(BATCH_SIZE) do |offset| - results = mysql_query(" + results = + mysql_query( + " SELECT p.post_id id, p.topic_id topic_id, t.forum_id category_id, @@ -119,23 +122,24 @@ class ImportScripts::SimplePress < ImportScripts::Base ORDER BY p.post_id LIMIT #{BATCH_SIZE} OFFSET #{offset}; - ") + ", + ) break if results.size < 1 - next if all_records_exist? :posts, results.map { |m| m['id'].to_i } + next if all_records_exist? :posts, results.map { |m| m["id"].to_i } create_posts(results, total: total_count, offset: offset) do |m| - created_at = Time.zone.at(m['post_time']) + created_at = Time.zone.at(m["post_time"]) { - id: m['id'], - user_id: user_id_from_imported_user_id(m['user_id']) || -1, - raw: process_simplepress_post(m['raw'], m['id']), + id: m["id"], + user_id: user_id_from_imported_user_id(m["user_id"]) || -1, + raw: process_simplepress_post(m["raw"], m["id"]), created_at: created_at, - category: category_id_from_imported_category_id(m['category_id']), - title: CGI.unescapeHTML(m['title']), - views: m['views'], - pinned_at: m['pinned'] == 1 ? created_at : nil, + category: category_id_from_imported_category_id(m["category_id"]), + title: CGI.unescapeHTML(m["title"]), + views: m["views"], + pinned_at: m["pinned"] == 1 ? created_at : nil, } end end @@ -146,17 +150,24 @@ class ImportScripts::SimplePress < ImportScripts::Base topic_first_post_id = {} - mysql_query(" + mysql_query( + " SELECT t.topic_id, p.post_id FROM #{TABLE_PREFIX}topics t JOIN #{TABLE_PREFIX}posts p ON p.topic_id = t.topic_id WHERE p.post_index = 1 - ").each { |r| topic_first_post_id[r["topic_id"]] = r["post_id"] } + ", + ).each { |r| topic_first_post_id[r["topic_id"]] = r["post_id"] } - total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}posts WHERE post_index <> 1").first["count"] + total_count = + mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}posts WHERE post_index <> 1").first[ + "count" + ] batches(BATCH_SIZE) do |offset| - results = mysql_query(" + results = + mysql_query( + " SELECT p.post_id id, p.topic_id topic_id, p.user_id user_id, @@ -169,23 +180,24 @@ class ImportScripts::SimplePress < ImportScripts::Base ORDER BY p.post_id LIMIT #{BATCH_SIZE} OFFSET #{offset}; - ") + ", + ) break if results.size < 1 - next if all_records_exist? :posts, results.map { |m| m['id'].to_i } + next if all_records_exist? :posts, results.map { |m| m["id"].to_i } create_posts(results, total: total_count, offset: offset) do |m| - if parent = topic_lookup_from_imported_post_id(topic_first_post_id[m['topic_id']]) + if parent = topic_lookup_from_imported_post_id(topic_first_post_id[m["topic_id"]]) { - id: m['id'], - user_id: user_id_from_imported_user_id(m['user_id']) || -1, + id: m["id"], + user_id: user_id_from_imported_user_id(m["user_id"]) || -1, topic_id: parent[:topic_id], - raw: process_simplepress_post(m['raw'], m['id']), - created_at: Time.zone.at(m['post_time']), + raw: process_simplepress_post(m["raw"], m["id"]), + created_at: Time.zone.at(m["post_time"]), } else - puts "Parent post #{m['topic_id']} doesn't exist. Skipping #{m["id"]}" + puts "Parent post #{m["topic_id"]} doesn't exist. Skipping #{m["id"]}" nil end end @@ -196,28 +208,27 @@ class ImportScripts::SimplePress < ImportScripts::Base s = raw.dup # fix invalid byte sequence in UTF-8 (ArgumentError) - unless s.valid_encoding? - s.force_encoding("UTF-8") - end + s.force_encoding("UTF-8") unless s.valid_encoding? # convert the quote line - s.gsub!(/\[quote='([^']+)'.*?pid='(\d+).*?\]/) { - "[quote=\"#{convert_username($1, import_id)}, " + post_id_to_post_num_and_topic($2, import_id) + '"]' - } + s.gsub!(/\[quote='([^']+)'.*?pid='(\d+).*?\]/) do + "[quote=\"#{convert_username($1, import_id)}, " + + post_id_to_post_num_and_topic($2, import_id) + '"]' + end # :) is encoded as :) s.gsub!(/(?:.*)/, '\1') # Some links look like this: http://www.onegameamonth.com - s.gsub!(/(.+)<\/a>/, '[\2](\1)') + s.gsub!(%r{(.+)}, '[\2](\1)') # Many phpbb bbcode tags have a hash attached to them. Examples: # [url=https://google.com:1qh1i7ky]click here[/url:1qh1i7ky] # [quote="cybereality":b0wtlzex]Some text.[/quote:b0wtlzex] - s.gsub!(/:(?:\w{8})\]/, ']') + s.gsub!(/:(?:\w{8})\]/, "]") # Remove mybb video tags. - s.gsub!(/(^\[video=.*?\])|(\[\/video\]$)/, '') + s.gsub!(%r{(^\[video=.*?\])|(\[/video\]$)}, "") s = CGI.unescapeHTML(s) @@ -225,7 +236,7 @@ class ImportScripts::SimplePress < ImportScripts::Base # [http://answers.yahoo.com/question/index ... 223AAkkPli](http://answers.yahoo.com/question/index?qid=20070920134223AAkkPli) # # Work around it for now: - s.gsub!(/\[http(s)?:\/\/(www\.)?/, '[') + s.gsub!(%r{\[http(s)?://(www\.)?}, "[") s end @@ -233,7 +244,6 @@ class ImportScripts::SimplePress < ImportScripts::Base def mysql_query(sql) @client.query(sql, cache_rows: false) end - end ImportScripts::SimplePress.new.perform diff --git a/script/import_scripts/smf1.rb b/script/import_scripts/smf1.rb index 90ec314ea9..5d601f2fa2 100644 --- a/script/import_scripts/smf1.rb +++ b/script/import_scripts/smf1.rb @@ -5,21 +5,21 @@ require "htmlentities" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::Smf1 < ImportScripts::Base - - BATCH_SIZE ||= 5000 + BATCH_SIZE ||= 5000 UPLOADS_DIR ||= ENV["UPLOADS_DIR"].presence - FORUM_URL ||= ENV["FORUM_URL"].presence + FORUM_URL ||= ENV["FORUM_URL"].presence def initialize - fail "UPLOADS_DIR env variable is required (example: '/path/to/attachments')" unless UPLOADS_DIR + fail "UPLOADS_DIR env variable is required (example: '/path/to/attachments')" unless UPLOADS_DIR fail "FORUM_URL env variable is required (example: 'https://domain.com/forum')" unless FORUM_URL - @client = Mysql2::Client.new( - host: ENV["DB_HOST"] || "localhost", - username: ENV["DB_USER"] || "root", - password: ENV["DB_PW"], - database: ENV["DB_NAME"], - ) + @client = + Mysql2::Client.new( + host: ENV["DB_HOST"] || "localhost", + username: ENV["DB_USER"] || "root", + password: ENV["DB_PW"], + database: ENV["DB_NAME"], + ) check_version! @@ -29,7 +29,12 @@ class ImportScripts::Smf1 < ImportScripts::Base puts "Loading existing usernames..." - @old_to_new_usernames = UserCustomField.joins(:user).where(name: "import_username").pluck("value", "users.username").to_h + @old_to_new_usernames = + UserCustomField + .joins(:user) + .where(name: "import_username") + .pluck("value", "users.username") + .to_h puts "Loading pm mapping..." @@ -41,13 +46,14 @@ class ImportScripts::Smf1 < ImportScripts::Base .where("title NOT ILIKE 'Re: %'") .group(:id) .order(:id) - .pluck("string_agg(topic_allowed_users.user_id::text, ',' ORDER BY topic_allowed_users.user_id), title, topics.id") + .pluck( + "string_agg(topic_allowed_users.user_id::text, ',' ORDER BY topic_allowed_users.user_id), title, topics.id", + ) .each do |users, title, topic_id| - @pm_mapping[users] ||= {} - @pm_mapping[users][title] ||= [] - @pm_mapping[users][title] << topic_id - end - + @pm_mapping[users] ||= {} + @pm_mapping[users][title] ||= [] + @pm_mapping[users][title] << topic_id + end end def execute @@ -71,7 +77,10 @@ class ImportScripts::Smf1 < ImportScripts::Base end def check_version! - version = mysql_query("SELECT value FROM smf_settings WHERE variable = 'smfVersion' LIMIT 1").first["value"] + version = + mysql_query("SELECT value FROM smf_settings WHERE variable = 'smfVersion' LIMIT 1").first[ + "value" + ] fail "Incompatible version (#{version})" unless version&.start_with?("1.") end @@ -84,10 +93,7 @@ class ImportScripts::Smf1 < ImportScripts::Base create_groups(groups) do |g| next if g["groupName"].blank? - { - id: g["id_group"], - full_name: g["groupName"], - } + { id: g["id_group"], full_name: g["groupName"] } end end @@ -98,7 +104,7 @@ class ImportScripts::Smf1 < ImportScripts::Base total = mysql_query("SELECT COUNT(*) count FROM smf_members").first["count"] batches(BATCH_SIZE) do |offset| - users = mysql_query(<<~SQL + users = mysql_query(<<~SQL).to_a SELECT m.id_member , memberName , dateRegistered @@ -125,7 +131,6 @@ class ImportScripts::Smf1 < ImportScripts::Base ORDER BY m.id_member LIMIT #{BATCH_SIZE} SQL - ).to_a break if users.empty? @@ -158,38 +163,45 @@ class ImportScripts::Smf1 < ImportScripts::Base ip_address: u["memberIP2"], active: u["is_activated"] == 1, approved: u["is_activated"] == 1, - post_create_action: proc do |user| - # usernames - @old_to_new_usernames[u["memberName"]] = user.username + post_create_action: + proc do |user| + # usernames + @old_to_new_usernames[u["memberName"]] = user.username - # groups - GroupUser.transaction do - group_ids.each do |gid| - (group_id = group_id_from_imported_group_id(gid)) && GroupUser.find_or_create_by(user: user, group_id: group_id) + # groups + GroupUser.transaction do + group_ids.each do |gid| + (group_id = group_id_from_imported_group_id(gid)) && + GroupUser.find_or_create_by(user: user, group_id: group_id) + end end - end - # avatar - avatar_url = nil + # avatar + avatar_url = nil - if u["avatar"].present? - if u["avatar"].start_with?("http") - avatar_url = u["avatar"] - elsif u["avatar"].start_with?("avatar_") - avatar_url = "#{FORUM_URL}/avatar-members/#{u["avatar"]}" + if u["avatar"].present? + if u["avatar"].start_with?("http") + avatar_url = u["avatar"] + elsif u["avatar"].start_with?("avatar_") + avatar_url = "#{FORUM_URL}/avatar-members/#{u["avatar"]}" + end end - end - avatar_url ||= if u["attachmentType"] == 0 && u["id_attach"].present? - "#{FORUM_URL}/index.php?action=dlattach;attach=#{u["id_attach"]};type=avatar" - elsif u["attachmentType"] == 1 && u["filename"].present? - "#{FORUM_URL}/avatar-members/#{u["filename"]}" - end + avatar_url ||= + if u["attachmentType"] == 0 && u["id_attach"].present? + "#{FORUM_URL}/index.php?action=dlattach;attach=#{u["id_attach"]};type=avatar" + elsif u["attachmentType"] == 1 && u["filename"].present? + "#{FORUM_URL}/avatar-members/#{u["filename"]}" + end - if avatar_url.present? - UserAvatar.import_url_for_user(avatar_url, user) rescue nil - end - end + if avatar_url.present? + begin + UserAvatar.import_url_for_user(avatar_url, user) + rescue StandardError + nil + end + end + end, } end end @@ -198,7 +210,7 @@ class ImportScripts::Smf1 < ImportScripts::Base def import_categories puts "", "Importing categories..." - categories = mysql_query(<<~SQL + categories = mysql_query(<<~SQL).to_a SELECT id_board , id_parent , boardOrder @@ -207,7 +219,6 @@ class ImportScripts::Smf1 < ImportScripts::Base FROM smf_boards ORDER BY id_parent, id_board SQL - ).to_a parent_categories = categories.select { |c| c["id_parent"] == 0 } children_categories = categories.select { |c| c["id_parent"] != 0 } @@ -218,9 +229,13 @@ class ImportScripts::Smf1 < ImportScripts::Base name: c["name"], description: pre_process_raw(c["description"].presence), position: c["boardOrder"], - post_create_action: proc do |category| - Permalink.find_or_create_by(url: "forums/index.php/board,#{c["id_board"]}.0.html", category_id: category.id) - end, + post_create_action: + proc do |category| + Permalink.find_or_create_by( + url: "forums/index.php/board,#{c["id_board"]}.0.html", + category_id: category.id, + ) + end, } end @@ -231,9 +246,13 @@ class ImportScripts::Smf1 < ImportScripts::Base name: c["name"], description: pre_process_raw(c["description"].presence), position: c["boardOrder"], - post_create_action: proc do |category| - Permalink.find_or_create_by(url: "forums/index.php/board,#{c["id_board"]}.0.html", category_id: category.id) - end, + post_create_action: + proc do |category| + Permalink.find_or_create_by( + url: "forums/index.php/board,#{c["id_board"]}.0.html", + category_id: category.id, + ) + end, } end end @@ -245,7 +264,7 @@ class ImportScripts::Smf1 < ImportScripts::Base total = mysql_query("SELECT COUNT(*) count FROM smf_messages").first["count"] batches(BATCH_SIZE) do |offset| - posts = mysql_query(<<~SQL + posts = mysql_query(<<~SQL).to_a SELECT m.id_msg , m.id_topic , m.id_board @@ -262,7 +281,6 @@ class ImportScripts::Smf1 < ImportScripts::Base ORDER BY m.id_msg LIMIT #{BATCH_SIZE} SQL - ).to_a break if posts.empty? @@ -287,12 +305,18 @@ class ImportScripts::Smf1 < ImportScripts::Base post[:views] = p["numViews"] post[:pinned_at] = created_at if p["isSticky"] == 1 post[:post_create_action] = proc do |pp| - Permalink.find_or_create_by(url: "forums/index.php/topic,#{p["id_topic"]}.0.html", topic_id: pp.topic_id) + Permalink.find_or_create_by( + url: "forums/index.php/topic,#{p["id_topic"]}.0.html", + topic_id: pp.topic_id, + ) end elsif parent = topic_lookup_from_imported_post_id(p["id_first_msg"]) post[:topic_id] = parent[:topic_id] post[:post_create_action] = proc do |pp| - Permalink.find_or_create_by(url: "forums/index.php/topic,#{p["id_topic"]}.msg#{p["id_msg"]}.html", post_id: pp.id) + Permalink.find_or_create_by( + url: "forums/index.php/topic,#{p["id_topic"]}.msg#{p["id_msg"]}.html", + post_id: pp.id, + ) end else next @@ -307,10 +331,15 @@ class ImportScripts::Smf1 < ImportScripts::Base puts "", "Importing personal posts..." last_post_id = -1 - total = mysql_query("SELECT COUNT(*) count FROM smf_personal_messages WHERE deletedBySender = 0").first["count"] + total = + mysql_query( + "SELECT COUNT(*) count FROM smf_personal_messages WHERE deletedBySender = 0", + ).first[ + "count" + ] batches(BATCH_SIZE) do |offset| - posts = mysql_query(<<~SQL + posts = mysql_query(<<~SQL).to_a SELECT id_pm , id_member_from , msgtime @@ -323,7 +352,6 @@ class ImportScripts::Smf1 < ImportScripts::Base ORDER BY id_pm LIMIT #{BATCH_SIZE} SQL - ).to_a break if posts.empty? @@ -335,7 +363,8 @@ class ImportScripts::Smf1 < ImportScripts::Base create_posts(posts, total: total, offset: offset) do |p| next unless user_id = user_id_from_imported_user_id(p["id_member_from"]) next if p["recipients"].blank? - recipients = p["recipients"].split(",").map { |id| user_id_from_imported_user_id(id) }.compact.uniq + recipients = + p["recipients"].split(",").map { |id| user_id_from_imported_user_id(id) }.compact.uniq next if recipients.empty? id = "pm-#{p["id_pm"]}" @@ -385,10 +414,13 @@ class ImportScripts::Smf1 < ImportScripts::Base count = 0 last_upload_id = -1 - total = mysql_query("SELECT COUNT(*) count FROM smf_attachments WHERE id_msg IS NOT NULL").first["count"] + total = + mysql_query("SELECT COUNT(*) count FROM smf_attachments WHERE id_msg IS NOT NULL").first[ + "count" + ] batches(BATCH_SIZE) do |offset| - uploads = mysql_query(<<~SQL + uploads = mysql_query(<<~SQL).to_a SELECT id_attach , id_msg , filename @@ -399,7 +431,6 @@ class ImportScripts::Smf1 < ImportScripts::Base ORDER BY id_attach LIMIT #{BATCH_SIZE} SQL - ).to_a break if uploads.empty? @@ -408,7 +439,13 @@ class ImportScripts::Smf1 < ImportScripts::Base uploads.each do |u| count += 1 - next unless post = PostCustomField.joins(:post).find_by(name: "import_id", value: u["id_msg"].to_s)&.post + unless post = + PostCustomField + .joins(:post) + .find_by(name: "import_id", value: u["id_msg"].to_s) + &.post + next + end path = File.join(UPLOADS_DIR, "#{u["id_attach"]}_#{u["file_hash"]}") next unless File.exist?(path) && File.size(path) > 0 @@ -433,15 +470,25 @@ class ImportScripts::Smf1 < ImportScripts::Base puts "", "Importing likes..." count = 0 - total = mysql_query("SELECT COUNT(*) count FROM smf_thank_you_post WHERE thx_time > 0").first["count"] + total = + mysql_query("SELECT COUNT(*) count FROM smf_thank_you_post WHERE thx_time > 0").first["count"] like = PostActionType.types[:like] - mysql_query("SELECT id_msg, id_member, thx_time FROM smf_thank_you_post WHERE thx_time > 0 ORDER BY id_thx_post").each do |l| + mysql_query( + "SELECT id_msg, id_member, thx_time FROM smf_thank_you_post WHERE thx_time > 0 ORDER BY id_thx_post", + ).each do |l| print_status(count += 1, total, get_start_time("likes")) next unless post_id = post_id_from_imported_post_id(l["id_msg"]) next unless user_id = user_id_from_imported_user_id(l["id_member"]) - next if PostAction.where(post_action_type_id: like, post_id: post_id, user_id: user_id).exists? - PostAction.create(post_action_type_id: like, post_id: post_id, user_id: user_id, created_at: Time.at(l["thx_time"])) + if PostAction.where(post_action_type_id: like, post_id: post_id, user_id: user_id).exists? + next + end + PostAction.create( + post_action_type_id: like, + post_id: post_id, + user_id: user_id, + created_at: Time.at(l["thx_time"]), + ) end end @@ -457,7 +504,7 @@ class ImportScripts::Smf1 < ImportScripts::Base count = 0 total = mysql_query("SELECT COUNT(*) count FROM smf_feedback WHERE approved").first["count"] - mysql_query(<<~SQL + mysql_query(<<~SQL).each do |f| SELECT feedbackid , id_member , feedbackmember_id @@ -470,7 +517,6 @@ class ImportScripts::Smf1 < ImportScripts::Base WHERE approved ORDER BY feedbackid SQL - ).each do |f| print_status(count += 1, total, get_start_time("feedbacks")) next unless user_id_from = user_id_from_imported_user_id(f["feedbackmember_id"]) next unless user_id_to = user_id_from_imported_user_id(f["id_member"]) @@ -498,7 +544,10 @@ class ImportScripts::Smf1 < ImportScripts::Base puts "", "Importing banned email domains..." blocklist = SiteSetting.blocked_email_domains.split("|") - banned_domains = mysql_query("SELECT SUBSTRING(email_address, 3) domain FROM smf_ban_items WHERE email_address RLIKE '^%@[^%]+$' GROUP BY email_address").map { |r| r["domain"] } + banned_domains = + mysql_query( + "SELECT SUBSTRING(email_address, 3) domain FROM smf_ban_items WHERE email_address RLIKE '^%@[^%]+$' GROUP BY email_address", + ).map { |r| r["domain"] } SiteSetting.blocked_email_domains = (blocklist + banned_domains).uniq.sort.join("|") end @@ -508,7 +557,10 @@ class ImportScripts::Smf1 < ImportScripts::Base count = 0 - banned_emails = mysql_query("SELECT email_address FROM smf_ban_items WHERE email_address RLIKE '^[^%]+@[^%]+$' GROUP BY email_address").map { |r| r["email_address"] } + banned_emails = + mysql_query( + "SELECT email_address FROM smf_ban_items WHERE email_address RLIKE '^[^%]+@[^%]+$' GROUP BY email_address", + ).map { |r| r["email_address"] } banned_emails.each do |email| print_status(count += 1, banned_emails.size, get_start_time("banned_emails")) ScreenedEmail.find_or_create_by(email: email) @@ -520,7 +572,7 @@ class ImportScripts::Smf1 < ImportScripts::Base count = 0 - banned_ips = mysql_query(<<~SQL + banned_ips = mysql_query(<<~SQL).to_a SELECT CONCAT_WS('.', ip_low1, ip_low2, ip_low3, ip_low4) low , CONCAT_WS('.', ip_high1, ip_high2, ip_high3, ip_high4) high , hits @@ -528,7 +580,6 @@ class ImportScripts::Smf1 < ImportScripts::Base WHERE (ip_low1 + ip_low2 + ip_low3 + ip_low4 + ip_high1 + ip_high2 + ip_high3 + ip_high4) > 0 GROUP BY low, high, hits; SQL - ).to_a banned_ips.each do |r| print_status(count += 1, banned_ips.size, get_start_time("banned_ips")) @@ -537,15 +588,15 @@ class ImportScripts::Smf1 < ImportScripts::Base ScreenedIpAddress.create(ip_address: r["low"], match_count: r["hits"]) end else - low_values = r["low"].split(".").map(&:to_i) + low_values = r["low"].split(".").map(&:to_i) high_values = r["high"].split(".").map(&:to_i) - first_diff = low_values.zip(high_values).count { |a, b| a == b } + first_diff = low_values.zip(high_values).count { |a, b| a == b } first_diff -= 1 if low_values[first_diff] == 0 && high_values[first_diff] == 255 - prefix = low_values[0...first_diff] - suffix = [0] * (3 - first_diff) - mask = 8 * (first_diff + 1) - values = (low_values[first_diff]..high_values[first_diff]) - hits = (r["hits"] / [1, values.count].max).floor + prefix = low_values[0...first_diff] + suffix = [0] * (3 - first_diff) + mask = 8 * (first_diff + 1) + values = (low_values[first_diff]..high_values[first_diff]) + hits = (r["hits"] / [1, values.count].max).floor values.each do |v| range_values = prefix + [v] + suffix ip_address = "#{range_values.join(".")}/#{mask}" @@ -562,10 +613,28 @@ class ImportScripts::Smf1 < ImportScripts::Base ScreenedIpAddress.roll_up end - IGNORED_BBCODE ||= %w{ - black blue center color email flash font glow green iurl left list move red - right shadown size table time white - } + IGNORED_BBCODE ||= %w[ + black + blue + center + color + email + flash + font + glow + green + iurl + left + list + move + red + right + shadown + size + table + time + white + ] def pre_process_raw(raw) return "" if raw.blank? @@ -573,59 +642,59 @@ class ImportScripts::Smf1 < ImportScripts::Base raw = @htmlentities.decode(raw) # [acronym] - raw.gsub!(/\[acronym=([^\]]+)\](.*?)\[\/acronym\]/im) { %{#{$2}} } + raw.gsub!(%r{\[acronym=([^\]]+)\](.*?)\[/acronym\]}im) { %{#{$2}} } # [br] raw.gsub!(/\[br\]/i, "\n") - raw.gsub!(//i, "\n") + raw.gsub!(%r{}i, "\n") # [hr] raw.gsub!(/\[hr\]/i, "
") # [sub] - raw.gsub!(/\[sub\](.*?)\[\/sub\]/im) { "#{$1}" } + raw.gsub!(%r{\[sub\](.*?)\[/sub\]}im) { "#{$1}" } # [sup] - raw.gsub!(/\[sup\](.*?)\[\/sup\]/im) { "#{$1}" } + raw.gsub!(%r{\[sup\](.*?)\[/sup\]}im) { "#{$1}" } # [html] raw.gsub!(/\[html\]/i, "\n```html\n") - raw.gsub!(/\[\/html\]/i, "\n```\n") + raw.gsub!(%r{\[/html\]}i, "\n```\n") # [php] raw.gsub!(/\[php\]/i, "\n```php\n") - raw.gsub!(/\[\/php\]/i, "\n```\n") + raw.gsub!(%r{\[/php\]}i, "\n```\n") # [code] - raw.gsub!(/\[\/?code\]/i, "\n```\n") + raw.gsub!(%r{\[/?code\]}i, "\n```\n") # [pre] - raw.gsub!(/\[\/?pre\]/i, "\n```\n") + raw.gsub!(%r{\[/?pre\]}i, "\n```\n") # [tt] - raw.gsub!(/\[\/?tt\]/i, "`") + raw.gsub!(%r{\[/?tt\]}i, "`") # [ftp] raw.gsub!(/\[ftp/i, "[url") - raw.gsub!(/\[\/ftp\]/i, "[/url]") + raw.gsub!(%r{\[/ftp\]}i, "[/url]") # [me] - raw.gsub!(/\[me=([^\]]*)\](.*?)\[\/me\]/im) { "_\\* #{$1} #{$2}_" } + raw.gsub!(%r{\[me=([^\]]*)\](.*?)\[/me\]}im) { "_\\* #{$1} #{$2}_" } # [li] - raw.gsub!(/\[li\](.*?)\[\/li\]/im) { "- #{$1}" } + raw.gsub!(%r{\[li\](.*?)\[/li\]}im) { "- #{$1}" } # puts [img] on their own line - raw.gsub!(/\[img[^\]]*\](.*?)\[\/img\]/im) { "\n#{$1}\n" } + raw.gsub!(%r{\[img[^\]]*\](.*?)\[/img\]}im) { "\n#{$1}\n" } # puts [youtube] on their own line - raw.gsub!(/\[youtube\](.*?)\[\/youtube\]/im) { "\n#{$1}\n" } + raw.gsub!(%r{\[youtube\](.*?)\[/youtube\]}im) { "\n#{$1}\n" } - IGNORED_BBCODE.each { |code| raw.gsub!(/\[#{code}[^\]]*\](.*?)\[\/#{code}\]/im, '\1') } + IGNORED_BBCODE.each { |code| raw.gsub!(%r{\[#{code}[^\]]*\](.*?)\[/#{code}\]}im, '\1') } # ensure [/quote] are on their own line - raw.gsub!(/\s*\[\/quote\]\s*/im, "\n[/quote]\n") + raw.gsub!(%r{\s*\[/quote\]\s*}im, "\n[/quote]\n") # [quote] - raw.gsub!(/\s*\[quote (.+?)\]\s/im) { + raw.gsub!(/\s*\[quote (.+?)\]\s/im) do params = $1 post_id = params[/msg(\d+)/, 1] username = params[/author=(.+) link=/, 1] @@ -636,14 +705,14 @@ class ImportScripts::Smf1 < ImportScripts::Base else %{\n[quote="#{username}"]\n} end - } + end # remove tapatalk mess - raw.gsub!(/Sent from .+? using \[url=.*?\].+?\[\/url\]/i, "") + raw.gsub!(%r{Sent from .+? using \[url=.*?\].+?\[/url\]}i, "") raw.gsub!(/Sent from .+? using .+?\z/i, "") # clean URLs - raw.gsub!(/\[url=(.+?)\]\1\[\/url\]/i, '\1') + raw.gsub!(%r{\[url=(.+?)\]\1\[/url\]}i, '\1') raw end @@ -651,7 +720,6 @@ class ImportScripts::Smf1 < ImportScripts::Base def mysql_query(sql) @client.query(sql) end - end ImportScripts::Smf1.new.perform diff --git a/script/import_scripts/smf2.rb b/script/import_scripts/smf2.rb index a70faff4cb..97eb20a92b 100644 --- a/script/import_scripts/smf2.rb +++ b/script/import_scripts/smf2.rb @@ -1,18 +1,17 @@ # coding: utf-8 # frozen_string_literal: true -require 'mysql2' -require File.expand_path(File.dirname(__FILE__) + '/base.rb') +require "mysql2" +require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require 'htmlentities' -require 'tsort' -require 'set' -require 'optparse' -require 'etc' -require 'open3' +require "htmlentities" +require "tsort" +require "set" +require "optparse" +require "etc" +require "open3" class ImportScripts::Smf2 < ImportScripts::Base - def self.run options = Options.new begin @@ -54,9 +53,9 @@ class ImportScripts::Smf2 < ImportScripts::Base exit 1 end if options.password == :ask - require 'highline' + require "highline" $stderr.print "Enter password for MySQL database `#{options.database}`: " - options.password = HighLine.new.ask('') { |q| q.echo = false } + options.password = HighLine.new.ask("") { |q| q.echo = false } end @default_db_connection = create_db_connection @@ -68,11 +67,11 @@ class ImportScripts::Smf2 < ImportScripts::Base import_categories import_posts postprocess_posts - make_prettyurl_permalinks('/forum') + make_prettyurl_permalinks("/forum") end def import_groups - puts '', 'creating groups' + puts "", "creating groups" total = query(<<-SQL, as: :single) SELECT COUNT(*) FROM {prefix}membergroups @@ -92,7 +91,7 @@ class ImportScripts::Smf2 < ImportScripts::Base MODERATORS_GROUP = 2 def import_users - puts '', 'creating users' + puts "", "creating users" total = query("SELECT COUNT(*) FROM {prefix}members", as: :single) create_users(query(<<-SQL), total: total) do |member| @@ -103,10 +102,25 @@ class ImportScripts::Smf2 < ImportScripts::Base FROM {prefix}members AS a LEFT JOIN {prefix}attachments AS b ON a.id_member = b.id_member SQL - group_ids = [ member[:id_group], *member[:additional_groups].split(',').map(&:to_i) ] - create_time = Time.zone.at(member[:date_registered]) rescue Time.now - last_seen_time = Time.zone.at(member[:last_login]) rescue nil - ip_addr = IPAddr.new(member[:member_ip]) rescue nil + group_ids = [member[:id_group], *member[:additional_groups].split(",").map(&:to_i)] + create_time = + begin + Time.zone.at(member[:date_registered]) + rescue StandardError + Time.now + end + last_seen_time = + begin + Time.zone.at(member[:last_login]) + rescue StandardError + nil + end + ip_addr = + begin + IPAddr.new(member[:member_ip]) + rescue StandardError + nil + end { id: member[:id_member], username: member[:member_name], @@ -121,27 +135,33 @@ class ImportScripts::Smf2 < ImportScripts::Base ip_address: ip_addr, admin: group_ids.include?(ADMIN_GROUP), moderator: group_ids.include?(MODERATORS_GROUP), - - post_create_action: proc do |user| - user.update(created_at: create_time) if create_time < user.created_at - user.save - GroupUser.transaction do - group_ids.each do |gid| - (group_id = group_id_from_imported_group_id(gid)) && - GroupUser.find_or_create_by(user: user, group_id: group_id) - end - end - if options.smfroot && member[:id_attach].present? && user.uploaded_avatar_id.blank? - (path = find_smf_attachment_path(member[:id_attach], member[:file_hash], member[:filename])) && begin - upload = create_upload(user.id, path, member[:filename]) - if upload.persisted? - user.update(uploaded_avatar_id: upload.id) + post_create_action: + proc do |user| + user.update(created_at: create_time) if create_time < user.created_at + user.save + GroupUser.transaction do + group_ids.each do |gid| + (group_id = group_id_from_imported_group_id(gid)) && + GroupUser.find_or_create_by(user: user, group_id: group_id) end - rescue SystemCallError => err - puts "Could not import avatar: #{err.message}" end - end - end + if options.smfroot && member[:id_attach].present? && user.uploaded_avatar_id.blank? + ( + path = + find_smf_attachment_path( + member[:id_attach], + member[:file_hash], + member[:filename], + ) + ) && + begin + upload = create_upload(user.id, path, member[:filename]) + user.update(uploaded_avatar_id: upload.id) if upload.persisted? + rescue SystemCallError => err + puts "Could not import avatar: #{err.message}" + end + end + end, } end end @@ -155,38 +175,39 @@ class ImportScripts::Smf2 < ImportScripts::Base parent_id = category_id_from_imported_category_id(board[:id_parent]) if board[:id_parent] > 0 groups = (board[:member_groups] || "").split(/,/).map(&:to_i) restricted = !groups.include?(GUEST_GROUP) && !groups.include?(MEMBER_GROUP) - if Category.find_by_name(board[:name]) - board[:name] += board[:id_board].to_s - end + board[:name] += board[:id_board].to_s if Category.find_by_name(board[:name]) { id: board[:id_board], name: board[:name], description: board[:description], parent_category_id: parent_id, - post_create_action: restricted && proc do |category| - category.update(read_restricted: true) - groups.each do |imported_group_id| - (group_id = group_id_from_imported_group_id(imported_group_id)) && - CategoryGroup.find_or_create_by(category: category, group_id: group_id) do |cg| - cg.permission_type = CategoryGroup.permission_types[:full] - end - end - end, + post_create_action: + restricted && + proc do |category| + category.update(read_restricted: true) + groups.each do |imported_group_id| + (group_id = group_id_from_imported_group_id(imported_group_id)) && + CategoryGroup.find_or_create_by(category: category, group_id: group_id) do |cg| + cg.permission_type = CategoryGroup.permission_types[:full] + end + end + end, } end end def import_posts - puts '', 'creating posts' - spinner = %w(/ - \\ |).cycle + puts "", "creating posts" + spinner = %w[/ - \\ |].cycle total = query("SELECT COUNT(*) FROM {prefix}messages", as: :single) PostCreator.class_eval do def guardian - @guardian ||= if opts[:import_mode] - @@system_guardian ||= Guardian.new(Discourse.system_user) - else - Guardian.new(@user) - end + @guardian ||= + if opts[:import_mode] + @@system_guardian ||= Guardian.new(Discourse.system_user) + else + Guardian.new(@user) + end end end @@ -208,10 +229,12 @@ class ImportScripts::Smf2 < ImportScripts::Base id: message[:id_msg], user_id: user_id_from_imported_user_id(message[:id_member]) || -1, created_at: Time.zone.at(message[:poster_time]), - post_create_action: ignore_quotes && proc do |p| - p.custom_fields['import_rebake'] = 't' - p.save - end + post_create_action: + ignore_quotes && + proc do |p| + p.custom_fields["import_rebake"] = "t" + p.save + end, } if message[:id_msg] == message[:id_first_msg] @@ -228,31 +251,48 @@ class ImportScripts::Smf2 < ImportScripts::Base end next nil if skip - attachments = message[:attachment_count] == 0 ? [] : query(<<-SQL, connection: db2, as: :array) + attachments = + message[:attachment_count] == 0 ? [] : query(<<-SQL, connection: db2, as: :array) SELECT id_attach, file_hash, filename FROM {prefix}attachments WHERE attachment_type = 0 AND id_msg = #{message[:id_msg]} ORDER BY id_attach ASC SQL - attachments.map! { |a| import_attachment(post, a) rescue (puts $! ; nil) } + attachments.map! do |a| + begin + import_attachment(post, a) + rescue StandardError + ( + puts $! + nil + ) + end + end post[:raw] = convert_message_body(message[:body], attachments, ignore_quotes: ignore_quotes) next post end end def import_attachment(post, attachment) - path = find_smf_attachment_path(attachment[:id_attach], attachment[:file_hash], attachment[:filename]) + path = + find_smf_attachment_path( + attachment[:id_attach], + attachment[:file_hash], + attachment[:filename], + ) raise "Attachment for post #{post[:id]} failed: #{attachment[:filename]}" unless path.present? upload = create_upload(post[:user_id], path, attachment[:filename]) - raise "Attachment for post #{post[:id]} failed: #{upload.errors.full_messages.join(', ')}" unless upload.persisted? + unless upload.persisted? + raise "Attachment for post #{post[:id]} failed: #{upload.errors.full_messages.join(", ")}" + end upload rescue SystemCallError => err raise "Attachment for post #{post[:id]} failed: #{err.message}" end def postprocess_posts - puts '', 'rebaking posts' + puts "", "rebaking posts" - tags = PostCustomField.where(name: 'import_rebake', value: 't') + tags = PostCustomField.where(name: "import_rebake", value: "t") tags_total = tags.count tags_done = 0 @@ -271,38 +311,47 @@ class ImportScripts::Smf2 < ImportScripts::Base private def create_db_connection - Mysql2::Client.new(host: options.host, username: options.username, - password: options.password, database: options.database) + Mysql2::Client.new( + host: options.host, + username: options.username, + password: options.password, + database: options.database, + ) end def query(sql, **opts, &block) db = opts[:connection] || @default_db_connection - return __query(db, sql).to_a if opts[:as] == :array - return __query(db, sql, as: :array).first[0] if opts[:as] == :single + return __query(db, sql).to_a if opts[:as] == :array + return __query(db, sql, as: :array).first[0] if opts[:as] == :single return __query(db, sql, stream: true).each(&block) if block_given? __query(db, sql, stream: true) end def __query(db, sql, **opts) - db.query(sql.gsub('{prefix}', options.prefix), - { symbolize_keys: true, cache_rows: false }.merge(opts)) + db.query( + sql.gsub("{prefix}", options.prefix), + { symbolize_keys: true, cache_rows: false }.merge(opts), + ) end - TRTR_TABLE = begin - from = "ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ" - to = "SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy" - from.chars.zip(to.chars) - end + TRTR_TABLE = + begin + from = "ŠŽšžŸÀÁÂÃÄÅÇÈÉÊËÌÍÎÏÑÒÓÔÕÖØÙÚÛÜÝàáâãäåçèéêëìíîïñòóôõöøùúûüýÿ" + to = "SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy" + from.chars.zip(to.chars) + end def find_smf_attachment_path(attachment_id, file_hash, filename) cleaned_name = filename.dup TRTR_TABLE.each { |from, to| cleaned_name.gsub!(from, to) } - cleaned_name.gsub!(/\s/, '_') - cleaned_name.gsub!(/[^\w_\.\-]/, '') - legacy_name = "#{attachment_id}_#{cleaned_name.gsub('.', '_')}#{Digest::MD5.hexdigest(cleaned_name)}" + cleaned_name.gsub!(/\s/, "_") + cleaned_name.gsub!(/[^\w_\.\-]/, "") + legacy_name = + "#{attachment_id}_#{cleaned_name.gsub(".", "_")}#{Digest::MD5.hexdigest(cleaned_name)}" - [ filename, "#{attachment_id}_#{file_hash}", legacy_name ] - .map { |name| File.join(options.smfroot, 'attachments', name) } + [filename, "#{attachment_id}_#{file_hash}", legacy_name].map do |name| + File.join(options.smfroot, "attachments", name) + end .detect { |file| File.exist?(file) } end @@ -311,16 +360,16 @@ class ImportScripts::Smf2 < ImportScripts::Base end def convert_message_body(body, attachments = [], **opts) - body = decode_entities(body.gsub(//, "\n")) + body = decode_entities(body.gsub(%r{}, "\n")) body.gsub!(ColorPattern, '\k') body.gsub!(ListPattern) do |s| params = parse_tag_params($~[:params]) - tag = params['type'] == 'decimal' ? 'ol' : 'ul' + tag = params["type"] == "decimal" ? "ol" : "ul" "\n[#{tag}]#{$~[:inner].strip}[/#{tag}]\n" end body.gsub!(XListPattern) do |s| r = +"\n[ul]" - s.lines.each { |l| "#{r}[li]#{l.strip.sub(/^\[x\]\s*/, '')}[/li]" } + s.lines.each { |l| "#{r}[li]#{l.strip.sub(/^\[x\]\s*/, "")}[/li]" } "#{r}[/ul]\n" end @@ -338,9 +387,7 @@ class ImportScripts::Smf2 < ImportScripts::Base if use_count.keys.length < attachments.select(&:present?).length body = "#{body}\n\n---" attachments.each_with_index do |upload, num| - if upload.present? && use_count[num] == (0) - "#{body}\n\n#{get_upload_markdown(upload)}" - end + "#{body}\n\n#{get_upload_markdown(upload)}" if upload.present? && use_count[num] == (0) end end end @@ -353,26 +400,46 @@ class ImportScripts::Smf2 < ImportScripts::Base end def convert_quotes(body) - body.to_s.gsub(QuotePattern) do |s| - inner = $~[:inner].strip - params = parse_tag_params($~[:params]) - if params['author'].present? - quote = +"\n[quote=\"#{params['author']}" - if QuoteParamsPattern =~ params['link'] - tl = topic_lookup_from_imported_post_id($~[:msg].to_i) - quote = "#{quote} post:#{tl[:post_number]}, topic:#{tl[:topic_id]}" if tl + body + .to_s + .gsub(QuotePattern) do |s| + inner = $~[:inner].strip + params = parse_tag_params($~[:params]) + if params["author"].present? + quote = +"\n[quote=\"#{params["author"]}" + if QuoteParamsPattern =~ params["link"] + tl = topic_lookup_from_imported_post_id($~[:msg].to_i) + quote = "#{quote} post:#{tl[:post_number]}, topic:#{tl[:topic_id]}" if tl + end + quote = "#{quote}\"]\n#{convert_quotes(inner)}\n[/quote]" + else + "
#{convert_quotes(inner)}
" end - quote = "#{quote}\"]\n#{convert_quotes(inner)}\n[/quote]" - else - "
#{convert_quotes(inner)}
" end - end end - IGNORED_BBCODE ||= %w{ - black blue center color email flash font glow green iurl left list move red - right shadown size table time white - } + IGNORED_BBCODE ||= %w[ + black + blue + center + color + email + flash + font + glow + green + iurl + left + list + move + red + right + shadown + size + table + time + white + ] def convert_bbcode(raw) return "" if raw.blank? @@ -380,67 +447,67 @@ class ImportScripts::Smf2 < ImportScripts::Base raw = convert_quotes(raw) # [acronym] - raw.gsub!(/\[acronym=([^\]]+)\](.*?)\[\/acronym\]/im) { %{#{$2}} } + raw.gsub!(%r{\[acronym=([^\]]+)\](.*?)\[/acronym\]}im) { %{#{$2}} } # [br] raw.gsub!(/\[br\]/i, "\n") - raw.gsub!(//i, "\n") + raw.gsub!(%r{}i, "\n") # [hr] raw.gsub!(/\[hr\]/i, "
") # [sub] - raw.gsub!(/\[sub\](.*?)\[\/sub\]/im) { "#{$1}" } + raw.gsub!(%r{\[sub\](.*?)\[/sub\]}im) { "#{$1}" } # [sup] - raw.gsub!(/\[sup\](.*?)\[\/sup\]/im) { "#{$1}" } + raw.gsub!(%r{\[sup\](.*?)\[/sup\]}im) { "#{$1}" } # [html] raw.gsub!(/\[html\]/i, "\n```html\n") - raw.gsub!(/\[\/html\]/i, "\n```\n") + raw.gsub!(%r{\[/html\]}i, "\n```\n") # [php] raw.gsub!(/\[php\]/i, "\n```php\n") - raw.gsub!(/\[\/php\]/i, "\n```\n") + raw.gsub!(%r{\[/php\]}i, "\n```\n") # [code] - raw.gsub!(/\[\/?code\]/i, "\n```\n") + raw.gsub!(%r{\[/?code\]}i, "\n```\n") # [pre] - raw.gsub!(/\[\/?pre\]/i, "\n```\n") + raw.gsub!(%r{\[/?pre\]}i, "\n```\n") # [tt] - raw.gsub!(/\[\/?tt\]/i, "`") + raw.gsub!(%r{\[/?tt\]}i, "`") # [ftp] raw.gsub!(/\[ftp/i, "[url") - raw.gsub!(/\[\/ftp\]/i, "[/url]") + raw.gsub!(%r{\[/ftp\]}i, "[/url]") # [me] - raw.gsub!(/\[me=([^\]]*)\](.*?)\[\/me\]/im) { "_\\* #{$1} #{$2}_" } + raw.gsub!(%r{\[me=([^\]]*)\](.*?)\[/me\]}im) { "_\\* #{$1} #{$2}_" } # [ul] raw.gsub!(/\[ul\]/i, "") - raw.gsub!(/\[\/ul\]/i, "") + raw.gsub!(%r{\[/ul\]}i, "") # [li] - raw.gsub!(/\[li\](.*?)\[\/li\]/im) { "- #{$1}" } + raw.gsub!(%r{\[li\](.*?)\[/li\]}im) { "- #{$1}" } # puts [img] on their own line - raw.gsub!(/\[img[^\]]*\](.*?)\[\/img\]/im) { "\n#{$1}\n" } + raw.gsub!(%r{\[img[^\]]*\](.*?)\[/img\]}im) { "\n#{$1}\n" } # puts [youtube] on their own line - raw.gsub!(/\[youtube\](.*?)\[\/youtube\]/im) { "\n#{$1}\n" } + raw.gsub!(%r{\[youtube\](.*?)\[/youtube\]}im) { "\n#{$1}\n" } - IGNORED_BBCODE.each { |code| raw.gsub!(/\[#{code}[^\]]*\](.*?)\[\/#{code}\]/im, '\1') } + IGNORED_BBCODE.each { |code| raw.gsub!(%r{\[#{code}[^\]]*\](.*?)\[/#{code}\]}im, '\1') } # ensure [/quote] are on their own line - raw.gsub!(/\s*\[\/quote\]\s*/im, "\n[/quote]\n") + raw.gsub!(%r{\s*\[/quote\]\s*}im, "\n[/quote]\n") # remove tapatalk mess - raw.gsub!(/Sent from .+? using \[url=.*?\].+?\[\/url\]/i, "") + raw.gsub!(%r{Sent from .+? using \[url=.*?\].+?\[/url\]}i, "") raw.gsub!(/Sent from .+? using .+?\z/i, "") # clean URLs - raw.gsub!(/\[url=(.+?)\]\1\[\/url\]/i, '\1') + raw.gsub!(%r{\[url=(.+?)\]\1\[/url\]}i, '\1') raw end @@ -460,8 +527,14 @@ class ImportScripts::Smf2 < ImportScripts::Base # param1=value1=still1 value1 param2=value2 ... # => {'param1' => 'value1=still1 value1', 'param2' => 'value2 ...'} def parse_tag_params(params) - params.to_s.strip.scan(/(?\w+)=(?(?:(?>\S+)|\s+(?!\w+=))*)/). - inject({}) { |h, e| h[e[0]] = e[1]; h } + params + .to_s + .strip + .scan(/(?\w+)=(?(?:(?>\S+)|\s+(?!\w+=))*)/) + .inject({}) do |h, e| + h[e[0]] = e[1] + h + end end class << self @@ -474,8 +547,8 @@ class ImportScripts::Smf2 < ImportScripts::Base # => match[:params] == 'param=value param2=value2' # match[:inner] == "\n text\n [tag nested=true]text[/tag]\n" def build_nested_tag_regex(ltag, rtag = nil) - rtag ||= '/' + ltag - %r{ + rtag ||= "/" + ltag + / \[#{ltag}(?-x:[ =](?[^\]]*))?\] # consume open tag, followed by... (?(?: (?> [^\[]+ ) # non-tags, or... @@ -495,40 +568,41 @@ class ImportScripts::Smf2 < ImportScripts::Base ) )*) \[#{rtag}\] - }x + /x end end QuoteParamsPattern = /^topic=(?\d+).msg(?\d+)#msg\k$/ XListPattern = /(?(?>^\[x\]\s*(?.*)$\n?)+)/ - QuotePattern = build_nested_tag_regex('quote') - ColorPattern = build_nested_tag_regex('color') - ListPattern = build_nested_tag_regex('list') + QuotePattern = build_nested_tag_regex("quote") + ColorPattern = build_nested_tag_regex("color") + ListPattern = build_nested_tag_regex("list") AttachmentPatterns = [ [/^\[attach(?:|img|url|mini)=(?\d+)\]$/, ->(u) { "\n" + get_upload_markdown(u) + "\n" }], - [/\[attach(?:|img|url|mini)=(?\d+)\]/, ->(u) { get_upload_markdown(u) }] + [/\[attach(?:|img|url|mini)=(?\d+)\]/, ->(u) { get_upload_markdown(u) }], ] # Provides command line options and parses the SMF settings file. class Options - - class Error < StandardError ; end - class SettingsError < Error ; end + class Error < StandardError + end + class SettingsError < Error + end def parse!(args = ARGV) - raise Error, 'not enough arguments' if ARGV.empty? + raise Error, "not enough arguments" if ARGV.empty? begin parser.parse!(args) rescue OptionParser::ParseError => err raise Error, err.message end - raise Error, 'too many arguments' if args.length > 1 + raise Error, "too many arguments" if args.length > 1 self.smfroot = args.first read_smf_settings if self.smfroot - self.host ||= 'localhost' + self.host ||= "localhost" self.username ||= Etc.getlogin - self.prefix ||= 'smf_' + self.prefix ||= "smf_" self.timezone ||= get_php_timezone end @@ -547,44 +621,63 @@ class ImportScripts::Smf2 < ImportScripts::Base private def get_php_timezone - phpinfo, status = Open3.capture2('php', '-i') + phpinfo, status = Open3.capture2("php", "-i") phpinfo.lines.each do |line| - key, *vals = line.split(' => ').map(&:strip) - break vals[0] if key == 'Default timezone' + key, *vals = line.split(" => ").map(&:strip) + break vals[0] if key == "Default timezone" end rescue Errno::ENOENT $stderr.puts "Error: PHP CLI executable not found" end def read_smf_settings - settings = File.join(self.smfroot, 'Settings.php') - File.readlines(settings).each do |line| - next unless m = /\$([a-z_]+)\s*=\s*['"](.+?)['"]\s*;\s*((#|\/\/).*)?$/.match(line) - case m[1] - when 'db_server' then self.host ||= m[2] - when 'db_user' then self.username ||= m[2] - when 'db_passwd' then self.password ||= m[2] - when 'db_name' then self.database ||= m[2] - when 'db_prefix' then self.prefix ||= m[2] + settings = File.join(self.smfroot, "Settings.php") + File + .readlines(settings) + .each do |line| + next unless m = %r{\$([a-z_]+)\s*=\s*['"](.+?)['"]\s*;\s*((#|//).*)?$}.match(line) + case m[1] + when "db_server" + self.host ||= m[2] + when "db_user" + self.username ||= m[2] + when "db_passwd" + self.password ||= m[2] + when "db_name" + self.database ||= m[2] + when "db_prefix" + self.prefix ||= m[2] + end end - end rescue => err raise SettingsError, err.message unless self.database end def parser - @parser ||= OptionParser.new(nil, 12) do |o| - o.banner = "Usage:\t#{File.basename($0)} [options]\n" - o.banner = "${o.banner}\t#{File.basename($0)} -d [options]" - o.on('-h HOST', :REQUIRED, "MySQL server hostname [\"#{self.host}\"]") { |s| self.host = s } - o.on('-u USER', :REQUIRED, "MySQL username [\"#{self.username}\"]") { |s| self.username = s } - o.on('-p [PASS]', :OPTIONAL, 'MySQL password. Without argument, reads password from STDIN.') { |s| self.password = s || :ask } - o.on('-d DBNAME', :REQUIRED, 'Name of SMF database') { |s| self.database = s } - o.on('-f PREFIX', :REQUIRED, "Table names prefix [\"#{self.prefix}\"]") { |s| self.prefix = s } - o.on('-t TIMEZONE', :REQUIRED, 'Timezone used by SMF2 [auto-detected from PHP]') { |s| self.timezone = s } - end + @parser ||= + OptionParser.new(nil, 12) do |o| + o.banner = "Usage:\t#{File.basename($0)} [options]\n" + o.banner = "${o.banner}\t#{File.basename($0)} -d [options]" + o.on("-h HOST", :REQUIRED, "MySQL server hostname [\"#{self.host}\"]") do |s| + self.host = s + end + o.on("-u USER", :REQUIRED, "MySQL username [\"#{self.username}\"]") do |s| + self.username = s + end + o.on( + "-p [PASS]", + :OPTIONAL, + "MySQL password. Without argument, reads password from STDIN.", + ) { |s| self.password = s || :ask } + o.on("-d DBNAME", :REQUIRED, "Name of SMF database") { |s| self.database = s } + o.on("-f PREFIX", :REQUIRED, "Table names prefix [\"#{self.prefix}\"]") do |s| + self.prefix = s + end + o.on("-t TIMEZONE", :REQUIRED, "Timezone used by SMF2 [auto-detected from PHP]") do |s| + self.timezone = s + end + end end - end #Options # Framework around TSort, used to build a dependency graph over messages @@ -644,10 +737,14 @@ class ImportScripts::Smf2 < ImportScripts::Base end def dependencies - @dependencies ||= Set.new.tap do |deps| - deps.merge(quoted) unless ignore_quotes? - deps << prev if prev.present? - end.to_a + @dependencies ||= + Set + .new + .tap do |deps| + deps.merge(quoted) unless ignore_quotes? + deps << prev if prev.present? + end + .to_a end def hash @@ -659,7 +756,7 @@ class ImportScripts::Smf2 < ImportScripts::Base end def inspect - "#<#{self.class.name}: id=#{id.inspect}, prev=#{safe_id(@prev)}, quoted=[#{@quoted.map(&method(:safe_id)).join(', ')}]>" + "#<#{self.class.name}: id=#{id.inspect}, prev=#{safe_id(@prev)}, quoted=[#{@quoted.map(&method(:safe_id)).join(", ")}]>" end private @@ -668,11 +765,10 @@ class ImportScripts::Smf2 < ImportScripts::Base @graph[id].present? ? @graph[id].id.inspect : "(#{id})" end end #Node - end #MessageDependencyGraph def make_prettyurl_permalinks(prefix) - puts 'creating permalinks for prettyurl plugin' + puts "creating permalinks for prettyurl plugin" begin serialized = query(<<-SQL, as: :single) SELECT value FROM {prefix}settings @@ -680,9 +776,7 @@ class ImportScripts::Smf2 < ImportScripts::Base SQL board_slugs = Array.new ser = /\{(.*)\}/.match(serialized)[1] - ser.scan(/i:(\d+);s:\d+:\"(.*?)\";/).each do |nv| - board_slugs[nv[0].to_i] = nv[1] - end + ser.scan(/i:(\d+);s:\d+:\"(.*?)\";/).each { |nv| board_slugs[nv[0].to_i] = nv[1] } topic_urls = query(<<-SQL, as: :array) SELECT t.id_first_msg, t.id_board,u.pretty_url FROM smf_topics t @@ -690,12 +784,14 @@ class ImportScripts::Smf2 < ImportScripts::Base SQL topic_urls.each do |url| t = topic_lookup_from_imported_post_id(url[:id_first_msg]) - Permalink.create(url: "#{prefix}/#{board_slugs[url[:id_board]]}/#{url[:pretty_url]}", topic_id: t[:topic_id]) + Permalink.create( + url: "#{prefix}/#{board_slugs[url[:id_board]]}/#{url[:pretty_url]}", + topic_id: t[:topic_id], + ) end - rescue + rescue StandardError end end - end ImportScripts::Smf2.run diff --git a/script/import_scripts/socialcast/create_title.rb b/script/import_scripts/socialcast/create_title.rb index 8af625eddb..ea656fadb7 100644 --- a/script/import_scripts/socialcast/create_title.rb +++ b/script/import_scripts/socialcast/create_title.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require 'uri' +require "uri" class CreateTitle - def self.from_body(body) title = remove_mentions body title = remove_urls title @@ -24,11 +23,11 @@ class CreateTitle private def self.remove_mentions(text) - text.gsub(/@[\w]*/, '') + text.gsub(/@[\w]*/, "") end def self.remove_urls(text) - text.gsub(URI::regexp(['http', 'https', 'mailto', 'ftp', 'ldap', 'ldaps']), '') + text.gsub(URI.regexp(%w[http https mailto ftp ldap ldaps]), "") end def self.remove_stray_punctuation(text) @@ -42,7 +41,7 @@ class CreateTitle end def self.complete_sentences(text) - /(^.*[\S]{2,}[.!?:]+)\W/.match(text[0...80] + ' ') + /(^.*[\S]{2,}[.!?:]+)\W/.match(text[0...80] + " ") end def self.complete_words(text) diff --git a/script/import_scripts/socialcast/export.rb b/script/import_scripts/socialcast/export.rb index 1c44c7c5c9..ac3a4690c1 100644 --- a/script/import_scripts/socialcast/export.rb +++ b/script/import_scripts/socialcast/export.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -require 'yaml' -require 'fileutils' -require_relative 'socialcast_api' +require "yaml" +require "fileutils" +require_relative "socialcast_api" def load_config(file) - config = YAML::load_file(File.join(__dir__, file)) - @domain = config['domain'] - @username = config['username'] - @password = config['password'] + config = YAML.load_file(File.join(__dir__, file)) + @domain = config["domain"] + @username = config["username"] + @password = config["password"] end def export @@ -23,8 +23,8 @@ def export_users(page = 1) users = @api.list_users(page: page) return if users.empty? users.each do |user| - File.open("output/users/#{user['id']}.json", 'w') do |f| - puts user['contact_info']['email'] + File.open("output/users/#{user["id"]}.json", "w") do |f| + puts user["contact_info"]["email"] f.write user.to_json f.close end @@ -36,12 +36,12 @@ def export_messages(page = 1) messages = @api.list_messages(page: page) return if messages.empty? messages.each do |message| - File.open("output/messages/#{message['id']}.json", 'w') do |f| - title = message['title'] - title = message['body'] if title.empty? + File.open("output/messages/#{message["id"]}.json", "w") do |f| + title = message["title"] + title = message["body"] if title.empty? title = title.split('\n')[0][0..50] unless title.empty? - puts "#{message['id']}: #{title}" + puts "#{message["id"]}: #{title}" f.write message.to_json f.close end @@ -51,9 +51,7 @@ end def create_dir(path) path = File.join(__dir__, path) - unless File.directory?(path) - FileUtils.mkdir_p(path) - end + FileUtils.mkdir_p(path) unless File.directory?(path) end load_config ARGV.shift diff --git a/script/import_scripts/socialcast/import.rb b/script/import_scripts/socialcast/import.rb index 413fd18ff8..c20237f66c 100644 --- a/script/import_scripts/socialcast/import.rb +++ b/script/import_scripts/socialcast/import.rb @@ -1,12 +1,11 @@ # frozen_string_literal: true -require_relative './socialcast_message.rb' -require_relative './socialcast_user.rb' -require 'set' +require_relative "./socialcast_message.rb" +require_relative "./socialcast_user.rb" +require "set" require File.expand_path(File.dirname(__FILE__) + "/../base.rb") class ImportScripts::Socialcast < ImportScripts::Base - MESSAGES_DIR = "output/messages" USERS_DIR = "output/users" @@ -29,15 +28,13 @@ class ImportScripts::Socialcast < ImportScripts::Base imported = 0 total = count_files(MESSAGES_DIR) Dir.foreach(MESSAGES_DIR) do |filename| - next if filename == ('.') || filename == ('..') + next if filename == (".") || filename == ("..") topics += 1 - message_json = File.read MESSAGES_DIR + '/' + filename + message_json = File.read MESSAGES_DIR + "/" + filename message = SocialcastMessage.new(message_json) next unless message.title created_topic = import_topic message.topic - if created_topic - import_posts message.replies, created_topic.topic_id - end + import_posts message.replies, created_topic.topic_id if created_topic imported += 1 print_status topics, total end @@ -48,8 +45,8 @@ class ImportScripts::Socialcast < ImportScripts::Base users = 0 total = count_files(USERS_DIR) Dir.foreach(USERS_DIR) do |filename| - next if filename == ('.') || filename == ('..') - user_json = File.read USERS_DIR + '/' + filename + next if filename == (".") || filename == ("..") + user_json = File.read USERS_DIR + "/" + filename user = SocialcastUser.new(user_json).user create_user user, user[:id] users += 1 @@ -58,7 +55,7 @@ class ImportScripts::Socialcast < ImportScripts::Base end def count_files(path) - Dir.foreach(path).select { |f| f != '.' && f != '..' }.count + Dir.foreach(path).select { |f| f != "." && f != ".." }.count end def import_topic(topic) @@ -80,9 +77,7 @@ class ImportScripts::Socialcast < ImportScripts::Base end def import_posts(posts, topic_id) - posts.each do |post| - import_post post, topic_id - end + posts.each { |post| import_post post, topic_id } end def import_post(post, topic_id) @@ -95,9 +90,6 @@ class ImportScripts::Socialcast < ImportScripts::Base puts new_post.inspect end end - end -if __FILE__ == $0 - ImportScripts::Socialcast.new.perform -end +ImportScripts::Socialcast.new.perform if __FILE__ == $0 diff --git a/script/import_scripts/socialcast/socialcast_api.rb b/script/import_scripts/socialcast/socialcast_api.rb index 84fc639770..6b080692c3 100644 --- a/script/import_scripts/socialcast/socialcast_api.rb +++ b/script/import_scripts/socialcast/socialcast_api.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true -require 'base64' -require 'json' +require "base64" +require "json" class SocialcastApi - attr_accessor :domain, :username, :password def initialize(domain, username, password) @@ -29,12 +28,12 @@ class SocialcastApi def list_users(opts = {}) page = opts[:page] ? opts[:page] : 1 response = request "#{base_url}/users?page=#{page}" - response['users'].sort { |u| u['id'] } + response["users"].sort { |u| u["id"] } end def list_messages(opts = {}) page = opts[:page] ? opts[:page] : 1 response = request "#{base_url}/messages?page=#{page}" - response['messages'].sort { |m| m['id'] } + response["messages"].sort { |m| m["id"] } end end diff --git a/script/import_scripts/socialcast/socialcast_message.rb b/script/import_scripts/socialcast/socialcast_message.rb index 4c7cf7a445..457713983a 100644 --- a/script/import_scripts/socialcast/socialcast_message.rb +++ b/script/import_scripts/socialcast/socialcast_message.rb @@ -1,24 +1,23 @@ # frozen_string_literal: true -require 'json' -require 'cgi' -require 'time' -require_relative 'create_title.rb' +require "json" +require "cgi" +require "time" +require_relative "create_title.rb" class SocialcastMessage - DEFAULT_CATEGORY = "Socialcast Import" DEFAULT_TAG = "socialcast-import" TAGS_AND_CATEGORIES = { "somegroupname" => { category: "Apple Stems", - tags: ["waxy", "tough"] + tags: %w[waxy tough], }, "someothergroupname" => { category: "Orange Peels", - tags: ["oily"] - } - } + tags: ["oily"], + }, + } def initialize(message_json) @parsed_json = JSON.parse message_json @@ -26,18 +25,18 @@ class SocialcastMessage def topic topic = {} - topic[:id] = @parsed_json['id'] - topic[:author_id] = @parsed_json['user']['id'] + topic[:id] = @parsed_json["id"] + topic[:author_id] = @parsed_json["user"]["id"] topic[:title] = title - topic[:raw] = @parsed_json['body'] - topic[:created_at] = Time.parse @parsed_json['created_at'] + topic[:raw] = @parsed_json["body"] + topic[:created_at] = Time.parse @parsed_json["created_at"] topic[:tags] = tags topic[:category] = category topic end def title - CreateTitle.from_body @parsed_json['body'] + CreateTitle.from_body @parsed_json["body"] end def tags @@ -55,39 +54,37 @@ class SocialcastMessage def category category = DEFAULT_CATEGORY - if group && TAGS_AND_CATEGORIES[group] - category = TAGS_AND_CATEGORIES[group][:category] - end + category = TAGS_AND_CATEGORIES[group][:category] if group && TAGS_AND_CATEGORIES[group] category end def group - @parsed_json['group']['groupname'].downcase if @parsed_json['group'] && @parsed_json['group']['groupname'] + if @parsed_json["group"] && @parsed_json["group"]["groupname"] + @parsed_json["group"]["groupname"].downcase + end end def url - @parsed_json['url'] + @parsed_json["url"] end def message_type - @parsed_json['message_type'] + @parsed_json["message_type"] end def replies posts = [] - comments = @parsed_json['comments'] - comments.each do |comment| - posts << post_from_comment(comment) - end + comments = @parsed_json["comments"] + comments.each { |comment| posts << post_from_comment(comment) } posts end def post_from_comment(comment) post = {} - post[:id] = comment['id'] - post[:author_id] = comment['user']['id'] - post[:raw] = comment['text'] - post[:created_at] = Time.parse comment['created_at'] + post[:id] = comment["id"] + post[:author_id] = comment["user"]["id"] + post[:raw] = comment["text"] + post[:created_at] = Time.parse comment["created_at"] post end diff --git a/script/import_scripts/socialcast/socialcast_user.rb b/script/import_scripts/socialcast/socialcast_user.rb index 1ffc93081c..f882637f66 100644 --- a/script/import_scripts/socialcast/socialcast_user.rb +++ b/script/import_scripts/socialcast/socialcast_user.rb @@ -1,26 +1,24 @@ # frozen_string_literal: true -require 'json' -require 'cgi' -require 'time' +require "json" +require "cgi" +require "time" class SocialcastUser - def initialize(user_json) @parsed_json = JSON.parse user_json end def user - email = @parsed_json['contact_info']['email'] - email = "#{@parsed_json['id']}@noemail.com" unless email + email = @parsed_json["contact_info"]["email"] + email = "#{@parsed_json["id"]}@noemail.com" unless email user = {} - user[:id] = @parsed_json['id'] - user[:name] = @parsed_json['name'] - user[:username] = @parsed_json['username'] + user[:id] = @parsed_json["id"] + user[:name] = @parsed_json["name"] + user[:username] = @parsed_json["username"] user[:email] = email user[:staged] = true user end - end diff --git a/script/import_scripts/socialcast/test/test_create_title.rb b/script/import_scripts/socialcast/test/test_create_title.rb index ee934a4f89..0dac092550 100644 --- a/script/import_scripts/socialcast/test/test_create_title.rb +++ b/script/import_scripts/socialcast/test/test_create_title.rb @@ -1,26 +1,28 @@ # frozen_string_literal: true -require 'minitest/autorun' -require_relative '../create_title.rb' +require "minitest/autorun" +require_relative "../create_title.rb" class TestCreateTitle < Minitest::Test - def test_create_title_1 - body = "@GreatCheerThreading \nWhere can I find information on how GCTS stacks up against the competition? What are the key differentiators?" + body = + "@GreatCheerThreading \nWhere can I find information on how GCTS stacks up against the competition? What are the key differentiators?" expected = "Where can I find information on how GCTS stacks up against the competition?" title = CreateTitle.from_body body assert_equal(expected, title) end def test_create_title_2 - body = "GCTS in 200 stores across town. How many threads per inch would you guess? @GreatCheerThreading" + body = + "GCTS in 200 stores across town. How many threads per inch would you guess? @GreatCheerThreading" expected = "GCTS in 200 stores across town. How many threads per inch would you guess?" title = CreateTitle.from_body body assert_equal(expected, title) end def test_create_title_3 - body = "gFabric Sheets 1.2 now has Great Cheer Threads, letting you feel the softness running through the cotton fibers." + body = + "gFabric Sheets 1.2 now has Great Cheer Threads, letting you feel the softness running through the cotton fibers." expected = "gFabric Sheets 1.2 now has Great Cheer Threads, letting you feel the softness..." title = CreateTitle.from_body body assert_equal(expected, title) @@ -34,49 +36,56 @@ class TestCreateTitle < Minitest::Test end def test_create_title_5 - body = "One sentence. Two sentence. Three sentence. Four is going to go on and on for more words than we want." + body = + "One sentence. Two sentence. Three sentence. Four is going to go on and on for more words than we want." expected = "One sentence. Two sentence. Three sentence." title = CreateTitle.from_body body assert_equal(expected, title) end def test_create_title_6 - body = "Anyone know of any invite codes for www.greatcheer.io (the Great Cheer v2 site)?\n\n//cc @RD @GreatCheerThreading" + body = + "Anyone know of any invite codes for www.greatcheer.io (the Great Cheer v2 site)?\n\n//cc @RD @GreatCheerThreading" expected = "Anyone know of any invite codes for www.greatcheer.io (the Great Cheer v2 site)?" title = CreateTitle.from_body body assert_equal(expected, title) end def test_create_title_6b - body = "Anyone know of any invite codes for www.greatcheer.io (the Great Cheer v2 site of yore)?\n\n//cc @RD @GreatCheerThreading" + body = + "Anyone know of any invite codes for www.greatcheer.io (the Great Cheer v2 site of yore)?\n\n//cc @RD @GreatCheerThreading" expected = "Anyone know of any invite codes for www.greatcheer.io (the Great Cheer v2 site..." title = CreateTitle.from_body body assert_equal(expected, title) end def test_create_title_6c - body = "Anyone know of any invite codes for www.greatcheer.io?! (the Great Cheer v2 site of yore)?\n\n//cc @RD @GreatCheerThreading" + body = + "Anyone know of any invite codes for www.greatcheer.io?! (the Great Cheer v2 site of yore)?\n\n//cc @RD @GreatCheerThreading" expected = "Anyone know of any invite codes for www.greatcheer.io?!" title = CreateTitle.from_body body assert_equal(expected, title) end def test_create_title_7 - body = "@GreatCheerThreading \n\nDoes anyone know what the plan is to move to denser 1.2 threads for GCTS?\n\nI have a customer interested in the higher thread counts offered in 1.2." + body = + "@GreatCheerThreading \n\nDoes anyone know what the plan is to move to denser 1.2 threads for GCTS?\n\nI have a customer interested in the higher thread counts offered in 1.2." expected = "Does anyone know what the plan is to move to denser 1.2 threads for GCTS?" title = CreateTitle.from_body body assert_equal(expected, title) end def test_create_title_8 - body = "@GreatCheerThreading @FabricWeavingWorldwide \n\nI was just chatting with a customer, after receiving this email:\n\n\"Ours is more of a ‘conceptual’ question. We have too much fiber" + body = + "@GreatCheerThreading @FabricWeavingWorldwide \n\nI was just chatting with a customer, after receiving this email:\n\n\"Ours is more of a ‘conceptual’ question. We have too much fiber" expected = "I was just chatting with a customer, after receiving this email:" title = CreateTitle.from_body body assert_equal(expected, title) end def test_create_title_9 - body = "Hi,\n\nDoes anyone have a PPT deck on whats new in cotton (around 10 or so slides) nothing to detailed as per what we have in the current 1.x version?\n\nI am not after a what's coming in cotton 2" + body = + "Hi,\n\nDoes anyone have a PPT deck on whats new in cotton (around 10 or so slides) nothing to detailed as per what we have in the current 1.x version?\n\nI am not after a what's coming in cotton 2" expected = "Does anyone have a PPT deck on whats new in cotton (around 10 or so slides)..." title = CreateTitle.from_body body assert_equal(expected, title) @@ -90,7 +99,8 @@ class TestCreateTitle < Minitest::Test end def test_create_title_11 - body = "Hi Guys,\nI'm working with #gtcs and one of the things we're playing with is TC. What better tool to demo and use than our own \nhttps://greatcheerthreading.com/themostthreads/cool-stuff\n\nThis used to work great in 2013," + body = + "Hi Guys,\nI'm working with #gtcs and one of the things we're playing with is TC. What better tool to demo and use than our own \nhttps://greatcheerthreading.com/themostthreads/cool-stuff\n\nThis used to work great in 2013," expected = "I'm working with #gtcs and one of the things we're playing with is TC." title = CreateTitle.from_body body assert_equal(expected, title) @@ -104,10 +114,10 @@ class TestCreateTitle < Minitest::Test end def test_create_title_13 - body = "Embroidered TC ... http://blogs.greatcheerthreading.com/thread/embroidering-the-threads-is-just-the-beginning\n@SoftStuff @TightWeave and team hopefully can share their thoughts on this recent post." + body = + "Embroidered TC ... http://blogs.greatcheerthreading.com/thread/embroidering-the-threads-is-just-the-beginning\n@SoftStuff @TightWeave and team hopefully can share their thoughts on this recent post." expected = "and team hopefully can share their thoughts on this recent post." title = CreateTitle.from_body body assert_equal(expected, title) end - end diff --git a/script/import_scripts/socialcast/test/test_data.rb b/script/import_scripts/socialcast/test/test_data.rb index 5bdbf52cc9..2dd018c32d 100644 --- a/script/import_scripts/socialcast/test/test_data.rb +++ b/script/import_scripts/socialcast/test/test_data.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -USERS = '{ +USERS = + '{ "users": [ { "contact_info": { @@ -1082,7 +1083,8 @@ USERS = '{ ] }' -MESSAGES = '{ +MESSAGES = + '{ "messages": [ { "id": 426, @@ -5429,7 +5431,8 @@ MESSAGES = '{ "messages_next_page": 2 }' -MESSAGES_PG_2 = '{ +MESSAGES_PG_2 = + '{ "messages": [ { "id": 386, diff --git a/script/import_scripts/socialcast/test/test_socialcast_api.rb b/script/import_scripts/socialcast/test/test_socialcast_api.rb index 70ad038c8b..f46e0fefd5 100644 --- a/script/import_scripts/socialcast/test/test_socialcast_api.rb +++ b/script/import_scripts/socialcast/test/test_socialcast_api.rb @@ -1,21 +1,20 @@ # frozen_string_literal: true -require 'minitest/autorun' -require 'yaml' -require_relative '../socialcast_api.rb' -require_relative './test_data.rb' +require "minitest/autorun" +require "yaml" +require_relative "../socialcast_api.rb" +require_relative "./test_data.rb" class TestSocialcastApi < Minitest::Test - DEBUG = false def initialize(args) - config = YAML::load_file(File.join(__dir__, 'config.ex.yml')) - @domain = config['domain'] - @username = config['username'] - @password = config['password'] - @kb_id = config['kb_id'] - @question_id = config['question_id'] + config = YAML.load_file(File.join(__dir__, "config.ex.yml")) + @domain = config["domain"] + @username = config["username"] + @password = config["password"] + @kb_id = config["kb_id"] + @question_id = config["question_id"] super args end @@ -30,18 +29,18 @@ class TestSocialcastApi < Minitest::Test end def test_base_url - assert_equal 'https://demo.socialcast.com/api', @socialcast.base_url + assert_equal "https://demo.socialcast.com/api", @socialcast.base_url end def test_headers headers = @socialcast.headers - assert_equal 'Basic ZW1pbHlAc29jaWFsY2FzdC5jb206ZGVtbw==', headers[:Authorization] - assert_equal 'application/json', headers[:Accept] + assert_equal "Basic ZW1pbHlAc29jaWFsY2FzdC5jb206ZGVtbw==", headers[:Authorization] + assert_equal "application/json", headers[:Accept] end def test_list_users users = @socialcast.list_users - expected = JSON.parse(USERS)['users'].sort { |u| u['id'] } + expected = JSON.parse(USERS)["users"].sort { |u| u["id"] } assert_equal 15, users.size assert_equal expected[0], users[0] end @@ -53,14 +52,14 @@ class TestSocialcastApi < Minitest::Test def test_list_messages messages = @socialcast.list_messages - expected = JSON.parse(MESSAGES)['messages'].sort { |m| m['id'] } + expected = JSON.parse(MESSAGES)["messages"].sort { |m| m["id"] } assert_equal 20, messages.size check_keys expected[0], messages[0] end def test_messages_next_page messages = @socialcast.list_messages(page: 2) - expected = JSON.parse(MESSAGES_PG_2)['messages'].sort { |m| m['id'] } + expected = JSON.parse(MESSAGES_PG_2)["messages"].sort { |m| m["id"] } assert_equal 20, messages.size check_keys expected[0], messages[0] end @@ -69,18 +68,16 @@ class TestSocialcastApi < Minitest::Test def check_keys(expected, actual) msg = "### caller[0]:\nKey not found in actual keys: #{actual.keys}\n" - expected.keys.each do |k| - assert (actual.keys.include? k), "#{k}" - end + expected.keys.each { |k| assert (actual.keys.include? k), "#{k}" } end def debug(message, show = false) if show || DEBUG - puts '### ' + caller[0] - puts '' + puts "### " + caller[0] + puts "" puts message - puts '' - puts '' + puts "" + puts "" end end end diff --git a/script/import_scripts/socialcast/title.rb b/script/import_scripts/socialcast/title.rb index b9f0e3c8ae..9f2c3dd82d 100644 --- a/script/import_scripts/socialcast/title.rb +++ b/script/import_scripts/socialcast/title.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require_relative './socialcast_message.rb' -require_relative './socialcast_user.rb' -require 'set' +require_relative "./socialcast_message.rb" +require_relative "./socialcast_user.rb" +require "set" require File.expand_path(File.dirname(__FILE__) + "/../base.rb") MESSAGES_DIR = "output/messages" @@ -11,8 +11,8 @@ def titles topics = 0 total = count_files(MESSAGES_DIR) Dir.foreach(MESSAGES_DIR) do |filename| - next if filename == ('.') || filename == ('..') - message_json = File.read MESSAGES_DIR + '/' + filename + next if filename == (".") || filename == ("..") + message_json = File.read MESSAGES_DIR + "/" + filename message = SocialcastMessage.new(message_json) next unless message.title #puts "#{filename}, #{message.replies.size}, #{message.topic[:raw].size}, #{message.message_type}, #{message.title}" @@ -23,7 +23,7 @@ def titles end def count_files(path) - Dir.foreach(path).select { |f| f != '.' && f != '..' }.count + Dir.foreach(path).select { |f| f != "." && f != ".." }.count end titles diff --git a/script/import_scripts/sourceforge.rb b/script/import_scripts/sourceforge.rb index 7d7de0cb8c..8d165a7fa0 100644 --- a/script/import_scripts/sourceforge.rb +++ b/script/import_scripts/sourceforge.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'base.rb' +require_relative "base.rb" # Import script for SourceForge discussions. # @@ -15,10 +15,10 @@ require_relative 'base.rb' class ImportScripts::Sourceforge < ImportScripts::Base # When the URL of your project is https://sourceforge.net/projects/foo/ # than the value of PROJECT_NAME is 'foo' - PROJECT_NAME = 'project_name' + PROJECT_NAME = "project_name" # This is the path to the discussion.json that you exported from SourceForge. - JSON_FILE = '/path/to/discussion.json' + JSON_FILE = "/path/to/discussion.json" def initialize super @@ -27,7 +27,7 @@ class ImportScripts::Sourceforge < ImportScripts::Base end def execute - puts '', 'Importing from SourceForge...' + puts "", "Importing from SourceForge..." load_json @@ -40,25 +40,26 @@ class ImportScripts::Sourceforge < ImportScripts::Base end def import_categories - puts '', 'importing categories' + puts "", "importing categories" create_categories(@json[:forums]) do |forum| { id: forum[:shortname], name: forum[:name], - post_create_action: proc do |category| - changes = { raw: forum[:description] } - opts = { revised_at: Time.now, bypass_bump: true } + post_create_action: + proc do |category| + changes = { raw: forum[:description] } + opts = { revised_at: Time.now, bypass_bump: true } - post = category.topic.first_post - post.revise(@system_user, changes, opts) - end + post = category.topic.first_post + post.revise(@system_user, changes, opts) + end, } end end def import_topics - puts '', 'importing posts' + puts "", "importing posts" imported_post_count = 0 total_post_count = count_posts @@ -78,7 +79,7 @@ class ImportScripts::Sourceforge < ImportScripts::Base id: "#{thread[:_id]}_#{post[:slug]}", user_id: @system_user, created_at: Time.zone.parse(post[:timestamp]), - raw: process_post_text(forum, thread, post) + raw: process_post_text(forum, thread, post), } if post == first_post @@ -103,9 +104,7 @@ class ImportScripts::Sourceforge < ImportScripts::Base total_count = 0 @json[:forums].each do |forum| - forum[:threads].each do |thread| - total_count += thread[:posts].size - end + forum[:threads].each { |thread| total_count += thread[:posts].size } end total_count @@ -117,20 +116,22 @@ class ImportScripts::Sourceforge < ImportScripts::Base def process_post_text(forum, thread, post) text = post[:text] - text.gsub!(/~{3,}/, '```') # Discourse doesn't recognize ~~~ as beginning/end of code blocks + text.gsub!(/~{3,}/, "```") # Discourse doesn't recognize ~~~ as beginning/end of code blocks # SourceForge doesn't allow symbols in usernames, so we are safe here. # Well, unless it's the anonymous user, which has an evil asterisk in the JSON file... username = post[:author] - username = 'anonymous' if username == '*anonymous' + username = "anonymous" if username == "*anonymous" # anonymous and nobody are nonexistent users. Make sure we don't create links for them. - user_without_profile = username == 'anonymous' || username == 'nobody' - user_link = user_without_profile ? username : "[#{username}](https://sourceforge.net/u/#{username}/)" + user_without_profile = username == "anonymous" || username == "nobody" + user_link = + user_without_profile ? username : "[#{username}](https://sourceforge.net/u/#{username}/)" # Create a nice looking header for each imported post that links to the author's user profile and the old post. - post_date = Time.zone.parse(post[:timestamp]).strftime('%A, %B %d, %Y') - post_url = "https://sourceforge.net/p/#{PROJECT_NAME}/discussion/#{forum[:shortname]}/thread/#{thread[:_id]}/##{post[:slug]}" + post_date = Time.zone.parse(post[:timestamp]).strftime("%A, %B %d, %Y") + post_url = + "https://sourceforge.net/p/#{PROJECT_NAME}/discussion/#{forum[:shortname]}/thread/#{thread[:_id]}/##{post[:slug]}" "**#{user_link}** wrote on [#{post_date}](#{post_url}):\n\n#{text}" end diff --git a/script/import_scripts/stack_overflow.rb b/script/import_scripts/stack_overflow.rb index 2eca547dbc..30ddf2e551 100644 --- a/script/import_scripts/stack_overflow.rb +++ b/script/import_scripts/stack_overflow.rb @@ -5,18 +5,18 @@ require "tiny_tds" require File.expand_path(File.dirname(__FILE__) + "/base.rb") class ImportScripts::StackOverflow < ImportScripts::Base - BATCH_SIZE ||= 1000 def initialize super - @client = TinyTds::Client.new( - host: ENV["DB_HOST"], - username: ENV["DB_USERNAME"], - password: ENV["DB_PASSWORD"], - database: ENV["DB_NAME"], - ) + @client = + TinyTds::Client.new( + host: ENV["DB_HOST"], + username: ENV["DB_USERNAME"], + password: ENV["DB_PASSWORD"], + database: ENV["DB_NAME"], + ) end def execute @@ -36,7 +36,7 @@ class ImportScripts::StackOverflow < ImportScripts::Base total = query("SELECT COUNT(*) count FROM Users WHERE Id > 0").first["count"] batches(BATCH_SIZE) do |offset| - users = query(<<~SQL + users = query(<<~SQL).to_a SELECT TOP #{BATCH_SIZE} Id , UserTypeId @@ -55,7 +55,6 @@ class ImportScripts::StackOverflow < ImportScripts::Base AND Id > #{last_user_id} ORDER BY Id SQL - ).to_a break if users.empty? @@ -77,11 +76,16 @@ class ImportScripts::StackOverflow < ImportScripts::Base name: u["RealName"], location: u["Location"], date_of_birth: u["Birthday"], - post_create_action: proc do |user| - if u["ProfileImageUrl"].present? - UserAvatar.import_url_for_user(u["ProfileImageUrl"], user) rescue nil - end - end + post_create_action: + proc do |user| + if u["ProfileImageUrl"].present? + begin + UserAvatar.import_url_for_user(u["ProfileImageUrl"], user) + rescue StandardError + nil + end + end + end, } end end @@ -91,11 +95,16 @@ class ImportScripts::StackOverflow < ImportScripts::Base puts "", "Importing posts..." last_post_id = -1 - total = query("SELECT COUNT(*) count FROM Posts WHERE PostTypeId IN (1,2,3)").first["count"] + - query("SELECT COUNT(*) count FROM PostComments WHERE PostId IN (SELECT Id FROM Posts WHERE PostTypeId IN (1,2,3))").first["count"] + total = + query("SELECT COUNT(*) count FROM Posts WHERE PostTypeId IN (1,2,3)").first["count"] + + query( + "SELECT COUNT(*) count FROM PostComments WHERE PostId IN (SELECT Id FROM Posts WHERE PostTypeId IN (1,2,3))", + ).first[ + "count" + ] batches(BATCH_SIZE) do |offset| - posts = query(<<~SQL + posts = query(<<~SQL).to_a SELECT TOP #{BATCH_SIZE} Id , PostTypeId @@ -113,14 +122,13 @@ class ImportScripts::StackOverflow < ImportScripts::Base AND Id > #{last_post_id} ORDER BY Id SQL - ).to_a break if posts.empty? last_post_id = posts[-1]["Id"] post_ids = posts.map { |p| p["Id"] } - comments = query(<<~SQL + comments = query(<<~SQL).to_a SELECT CONCAT('Comment-', Id) AS Id , PostId AS ParentId , Text @@ -130,7 +138,6 @@ class ImportScripts::StackOverflow < ImportScripts::Base WHERE PostId IN (#{post_ids.join(",")}) ORDER BY Id SQL - ).to_a posts_and_comments = (posts + comments).sort_by { |p| p["CreationDate"] } post_and_comment_ids = posts_and_comments.map { |p| p["Id"] } @@ -173,7 +180,7 @@ class ImportScripts::StackOverflow < ImportScripts::Base last_like_id = -1 batches(BATCH_SIZE) do |offset| - likes = query(<<~SQL + likes = query(<<~SQL).to_a SELECT TOP #{BATCH_SIZE} Id , PostId @@ -185,7 +192,6 @@ class ImportScripts::StackOverflow < ImportScripts::Base AND Id > #{last_like_id} ORDER BY Id SQL - ).to_a break if likes.empty? @@ -196,17 +202,26 @@ class ImportScripts::StackOverflow < ImportScripts::Base next unless post_id = post_id_from_imported_post_id(l["PostId"]) next unless user = User.find_by(id: user_id) next unless post = Post.find_by(id: post_id) - PostActionCreator.like(user, post) rescue nil + begin + PostActionCreator.like(user, post) + rescue StandardError + nil + end end end puts "", "Importing comment likes..." last_like_id = -1 - total = query("SELECT COUNT(*) count FROM Comments2Votes WHERE VoteTypeId = 2 AND DeletionDate IS NULL").first["count"] + total = + query( + "SELECT COUNT(*) count FROM Comments2Votes WHERE VoteTypeId = 2 AND DeletionDate IS NULL", + ).first[ + "count" + ] batches(BATCH_SIZE) do |offset| - likes = query(<<~SQL + likes = query(<<~SQL).to_a SELECT TOP #{BATCH_SIZE} Id , CONCAT('Comment-', PostCommentId) AS PostCommentId @@ -218,7 +233,6 @@ class ImportScripts::StackOverflow < ImportScripts::Base AND Id > #{last_like_id} ORDER BY Id SQL - ).to_a break if likes.empty? @@ -229,7 +243,11 @@ class ImportScripts::StackOverflow < ImportScripts::Base next unless post_id = post_id_from_imported_post_id(l["PostCommentId"]) next unless user = User.find_by(id: user_id) next unless post = Post.find_by(id: post_id) - PostActionCreator.like(user, post) rescue nil + begin + PostActionCreator.like(user, post) + rescue StandardError + nil + end end end end @@ -249,7 +267,6 @@ class ImportScripts::StackOverflow < ImportScripts::Base def query(sql) @client.execute(sql) end - end ImportScripts::StackOverflow.new.perform diff --git a/script/import_scripts/support/convert_mysql_xml_to_mysql.rb b/script/import_scripts/support/convert_mysql_xml_to_mysql.rb index be0e45ca2f..070bfb4123 100644 --- a/script/import_scripts/support/convert_mysql_xml_to_mysql.rb +++ b/script/import_scripts/support/convert_mysql_xml_to_mysql.rb @@ -3,11 +3,10 @@ # convert huge XML dump to mysql friendly import # -require 'ox' -require 'set' +require "ox" +require "set" class Saxy < Ox::Sax - def initialize @stack = [] end @@ -32,7 +31,6 @@ class Saxy < Ox::Sax def cdata(val) @stack[-1][:text] = val end - end class Convert < Saxy @@ -59,10 +57,13 @@ class Convert < Saxy end def output_table_definition(data) - cols = data[:cols].map do |col| - attrs = col[:attrs] - "#{attrs[:Field]} #{attrs[:Type]}" - end.join(", ") + cols = + data[:cols] + .map do |col| + attrs = col[:attrs] + "#{attrs[:Field]} #{attrs[:Type]}" + end + .join(", ") puts "CREATE TABLE #{data[:attrs][:name]} (#{cols});" end @@ -77,4 +78,4 @@ class Convert < Saxy end end -Ox.sax_parse(Convert.new(skip_data: ['metrics2', 'user_log']), File.open(ARGV[0])) +Ox.sax_parse(Convert.new(skip_data: %w[metrics2 user_log]), File.open(ARGV[0])) diff --git a/script/import_scripts/telligent.rb b/script/import_scripts/telligent.rb index 32ffda8acb..c46be23fac 100644 --- a/script/import_scripts/telligent.rb +++ b/script/import_scripts/telligent.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative 'base' -require 'tiny_tds' +require_relative "base" +require "tiny_tds" # Import script for Telligent communities # @@ -40,17 +40,19 @@ require 'tiny_tds' class ImportScripts::Telligent < ImportScripts::Base BATCH_SIZE ||= 1000 - LOCAL_AVATAR_REGEX ||= /\A~\/.*(?communityserver-components-(?:selectable)?avatars)\/(?[^\/]+)\/(?.+)/i - REMOTE_AVATAR_REGEX ||= /\Ahttps?:\/\//i + LOCAL_AVATAR_REGEX ||= + %r{\A~/.*(?communityserver-components-(?:selectable)?avatars)/(?[^/]+)/(?.+)}i + REMOTE_AVATAR_REGEX ||= %r{\Ahttps?://}i ATTACHMENT_REGEXES ||= [ - /]*\shref="[^"]*?\/cfs-file(?:systemfile)?(?:\.ashx)?\/__key\/(?[^\/]+)\/(?[^\/]+)\/(?.+?)".*?>.*?<\/a>/i, - /]*\ssrc="[^"]*?\/cfs-file(?:systemfile)?(?:\.ashx)?\/__key\/(?[^\/]+)\/(?[^\/]+)\/(?.+?)".*?>/i, - /\[View:[^\]]*?\/cfs-file(?:systemfile)?(?:\.ashx)?\/__key\/(?[^\/]+)\/(?[^\/]+)\/(?.+?)(?:\:[:\d\s]*?)?\]/i, - /\[(?img|url)\][^\[]*?cfs-file(?:systemfile)?(?:\.ashx)?\/__key\/(?[^\/]+)\/(?[^\/]+)\/(?.+?)\[\/\k\]/i, - /\[(?img|url)=[^\[]*?cfs-file(?:systemfile)?(?:\.ashx)?\/__key\/(?[^\/]+)\/(?[^\/]+)\/(?.+?)\][^\[]*?\[\/\k\]/i + %r{]*\shref="[^"]*?/cfs-file(?:systemfile)?(?:\.ashx)?/__key/(?[^/]+)/(?[^/]+)/(?.+?)".*?>.*?}i, + %r{]*\ssrc="[^"]*?/cfs-file(?:systemfile)?(?:\.ashx)?/__key/(?[^/]+)/(?[^/]+)/(?.+?)".*?>}i, + %r{\[View:[^\]]*?/cfs-file(?:systemfile)?(?:\.ashx)?/__key/(?[^/]+)/(?[^/]+)/(?.+?)(?:\:[:\d\s]*?)?\]}i, + %r{\[(?img|url)\][^\[]*?cfs-file(?:systemfile)?(?:\.ashx)?/__key/(?[^/]+)/(?[^/]+)/(?.+?)\[/\k\]}i, + %r{\[(?img|url)=[^\[]*?cfs-file(?:systemfile)?(?:\.ashx)?/__key/(?[^/]+)/(?[^/]+)/(?.+?)\][^\[]*?\[/\k\]}i, ] PROPERTY_NAMES_REGEX ||= /(?\w+):S:(?\d+):(?\d+):/ - INTERNAL_LINK_REGEX ||= /\shref=".*?\/f\/\d+(?:(\/t\/(?\d+))|(?:\/p\/\d+\/(?\d+))|(?:\/p\/(?\d+)\/reply))\.aspx[^"]*?"/i + INTERNAL_LINK_REGEX ||= + %r{\shref=".*?/f/\d+(?:(/t/(?\d+))|(?:/p/\d+/(?\d+))|(?:/p/(?\d+)/reply))\.aspx[^"]*?"}i CATEGORY_LINK_NORMALIZATION = '/.*?(f\/\d+)$/\1' TOPIC_LINK_NORMALIZATION = '/.*?(f\/\d+\/t\/\d+)$/\1' @@ -82,19 +84,20 @@ class ImportScripts::Telligent < ImportScripts::Base "1D20" => "”", "B000" => "°", "0003" => ["0300".to_i(16)].pack("U"), - "0103" => ["0301".to_i(16)].pack("U") + "0103" => ["0301".to_i(16)].pack("U"), } def initialize super() - @client = TinyTds::Client.new( - host: ENV["DB_HOST"], - username: ENV["DB_USERNAME"], - password: ENV["DB_PASSWORD"], - database: ENV["DB_NAME"], - timeout: 60 # the user query is very slow - ) + @client = + TinyTds::Client.new( + host: ENV["DB_HOST"], + username: ENV["DB_USERNAME"], + password: ENV["DB_PASSWORD"], + database: ENV["DB_NAME"], + timeout: 60, # the user query is very slow + ) @filestore_root_directory = ENV["FILE_BASE_DIR"] @files = {} @@ -180,10 +183,11 @@ class ImportScripts::Telligent < ImportScripts::Base bio_raw: html_to_markdown(ap_properties["bio"]), location: ap_properties["location"], website: ap_properties["webAddress"], - post_create_action: proc do |user| - import_avatar(user, up_properties["avatarUrl"]) - suspend_user(user, up_properties["BannedUntil"], up_properties["UserBanReason"]) - end + post_create_action: + proc do |user| + import_avatar(user, up_properties["avatarUrl"]) + suspend_user(user, up_properties["BannedUntil"], up_properties["UserBanReason"]) + end, } end @@ -193,13 +197,18 @@ class ImportScripts::Telligent < ImportScripts::Base # TODO move into base importer (create_user) and use consistent error handling def import_avatar(user, avatar_url) - return if @filestore_root_directory.blank? || avatar_url.blank? || avatar_url.include?("anonymous") + if @filestore_root_directory.blank? || avatar_url.blank? || avatar_url.include?("anonymous") + return + end if match_data = avatar_url.match(LOCAL_AVATAR_REGEX) - avatar_path = File.join(@filestore_root_directory, - match_data[:directory].gsub("-", "."), - match_data[:path].split("-"), - match_data[:filename]) + avatar_path = + File.join( + @filestore_root_directory, + match_data[:directory].gsub("-", "."), + match_data[:path].split("-"), + match_data[:filename], + ) if File.file?(avatar_path) @uploader.create_avatar(user, avatar_path) @@ -207,7 +216,11 @@ class ImportScripts::Telligent < ImportScripts::Base STDERR.puts "Could not find avatar: #{avatar_path}" end elsif avatar_url.match?(REMOTE_AVATAR_REGEX) - UserAvatar.import_url_for_user(avatar_url, user) rescue nil + begin + UserAvatar.import_url_for_user(avatar_url, user) + rescue StandardError + nil + end end end @@ -224,7 +237,7 @@ class ImportScripts::Telligent < ImportScripts::Base end def import_categories - if ENV['CATEGORY_MAPPING'] + if ENV["CATEGORY_MAPPING"] import_mapped_forums_as_categories else import_groups_and_forums_as_categories @@ -234,7 +247,7 @@ class ImportScripts::Telligent < ImportScripts::Base def import_mapped_forums_as_categories puts "", "Importing categories..." - json = JSON.parse(File.read(ENV['CATEGORY_MAPPING'])) + json = JSON.parse(File.read(ENV["CATEGORY_MAPPING"])) categories = [] @forum_ids_to_tags = {} @@ -256,7 +269,7 @@ class ImportScripts::Telligent < ImportScripts::Base id: id, name: name, parent_id: parent_id, - forum_ids: index == last_index ? forum_ids : nil + forum_ids: index == last_index ? forum_ids : nil, } parent_id = id end @@ -271,9 +284,7 @@ class ImportScripts::Telligent < ImportScripts::Base id: c[:id], name: c[:name], parent_category_id: category_id_from_imported_category_id(c[:parent_id]), - post_create_action: proc do |category| - map_forum_ids(category.id, c[:forum_ids]) - end + post_create_action: proc { |category| map_forum_ids(category.id, c[:forum_ids]) }, } end end @@ -302,10 +313,10 @@ class ImportScripts::Telligent < ImportScripts::Base create_categories(parent_categories) do |row| { - id: "G#{row['GroupID']}", + id: "G#{row["GroupID"]}", name: clean_category_name(row["Name"]), description: html_to_markdown(row["HtmlDescription"]), - position: row["SortOrder"] + position: row["SortOrder"], } end @@ -320,28 +331,31 @@ class ImportScripts::Telligent < ImportScripts::Base parent_category_id = parent_category_id_for(row) if category_id = replace_with_category_id(child_categories, parent_category_id) - add_category(row['ForumId'], Category.find_by_id(category_id)) - url = "f/#{row['ForumId']}" + add_category(row["ForumId"], Category.find_by_id(category_id)) + url = "f/#{row["ForumId"]}" Permalink.create(url: url, category_id: category_id) unless Permalink.exists?(url: url) nil else { - id: row['ForumId'], + id: row["ForumId"], parent_category_id: parent_category_id, name: clean_category_name(row["Name"]), description: html_to_markdown(row["Description"]), position: row["SortOrder"], - post_create_action: proc do |category| - url = "f/#{row['ForumId']}" - Permalink.create(url: url, category_id: category.id) unless Permalink.exists?(url: url) - end + post_create_action: + proc do |category| + url = "f/#{row["ForumId"]}" + unless Permalink.exists?(url: url) + Permalink.create(url: url, category_id: category.id) + end + end, } end end end def parent_category_id_for(row) - category_id_from_imported_category_id("G#{row['GroupId']}") if row.key?("GroupId") + category_id_from_imported_category_id("G#{row["GroupId"]}") if row.key?("GroupId") end def replace_with_category_id(child_categories, parent_category_id) @@ -351,23 +365,21 @@ class ImportScripts::Telligent < ImportScripts::Base def only_child?(child_categories, parent_category_id) count = 0 - child_categories.each do |row| - count += 1 if parent_category_id_for(row) == parent_category_id - end + child_categories.each { |row| count += 1 if parent_category_id_for(row) == parent_category_id } count == 1 end def clean_category_name(name) - CGI.unescapeHTML(name) - .strip + CGI.unescapeHTML(name).strip end def import_topics puts "", "Importing topics..." last_topic_id = -1 - total_count = count("SELECT COUNT(1) AS count FROM te_Forum_Threads t WHERE #{ignored_forum_sql_condition}") + total_count = + count("SELECT COUNT(1) AS count FROM te_Forum_Threads t WHERE #{ignored_forum_sql_condition}") batches do |offset| rows = query(<<~SQL) @@ -399,13 +411,16 @@ class ImportScripts::Telligent < ImportScripts::Base created_at: row["DateCreated"], closed: row["IsLocked"], views: row["TotalViews"], - post_create_action: proc do |action_post| - topic = action_post.topic - Jobs.enqueue_at(topic.pinned_until, :unpin_topic, topic_id: topic.id) if topic.pinned_until - url = "f/#{row['ForumId']}/t/#{row['ThreadId']}" - Permalink.create(url: url, topic_id: topic.id) unless Permalink.exists?(url: url) - import_topic_views(topic, row["TopicContentId"]) - end + post_create_action: + proc do |action_post| + topic = action_post.topic + if topic.pinned_until + Jobs.enqueue_at(topic.pinned_until, :unpin_topic, topic_id: topic.id) + end + url = "f/#{row["ForumId"]}/t/#{row["ThreadId"]}" + Permalink.create(url: url, topic_id: topic.id) unless Permalink.exists?(url: url) + import_topic_views(topic, row["TopicContentId"]) + end, } if row["StickyDate"] > Time.now @@ -446,9 +461,8 @@ class ImportScripts::Telligent < ImportScripts::Base end def ignored_forum_sql_condition - @ignored_forum_sql_condition ||= @ignored_forum_ids.present? \ - ? "t.ForumId NOT IN (#{@ignored_forum_ids.join(',')})" \ - : "1 = 1" + @ignored_forum_sql_condition ||= + @ignored_forum_ids.present? ? "t.ForumId NOT IN (#{@ignored_forum_ids.join(",")})" : "1 = 1" end def import_posts @@ -492,7 +506,8 @@ class ImportScripts::Telligent < ImportScripts::Base next if all_records_exist?(:post, rows.map { |row| row["ThreadReplyId"] }) create_posts(rows, total: total_count, offset: offset) do |row| - imported_parent_id = row["ParentReplyId"]&.nonzero? ? row["ParentReplyId"] : import_topic_id(row["ThreadId"]) + imported_parent_id = + row["ParentReplyId"]&.nonzero? ? row["ParentReplyId"] : import_topic_id(row["ThreadId"]) parent_post = topic_lookup_from_imported_post_id(imported_parent_id) user_id = user_id_from_imported_user_id(row["UserId"]) || Discourse::SYSTEM_USER_ID @@ -503,13 +518,13 @@ class ImportScripts::Telligent < ImportScripts::Base user_id: user_id, topic_id: parent_post[:topic_id], created_at: row["ThreadReplyDate"], - reply_to_post_number: parent_post[:post_number] + reply_to_post_number: parent_post[:post_number], } post[:custom_fields] = { is_accepted_answer: "true" } if row["IsFirstVerifiedAnswer"] post else - puts "Failed to import post #{row['ThreadReplyId']}. Parent was not found." + puts "Failed to import post #{row["ThreadReplyId"]}. Parent was not found." end end end @@ -565,7 +580,7 @@ class ImportScripts::Telligent < ImportScripts::Base id: row["MessageId"], raw: raw_with_attachment(row, user_id, :message), user_id: user_id, - created_at: row["DateCreated"] + created_at: row["DateCreated"], } if current_conversation_id == row["ConversationId"] @@ -574,7 +589,7 @@ class ImportScripts::Telligent < ImportScripts::Base if parent_post post[:topic_id] = parent_post[:topic_id] else - puts "Failed to import message #{row['MessageId']}. Parent was not found." + puts "Failed to import message #{row["MessageId"]}. Parent was not found." post = nil end else @@ -583,7 +598,7 @@ class ImportScripts::Telligent < ImportScripts::Base post[:target_usernames] = get_recipient_usernames(row) if post[:target_usernames].empty? - puts "Private message without recipients. Skipping #{row['MessageId']}" + puts "Private message without recipients. Skipping #{row["MessageId"]}" post = nil end @@ -611,7 +626,7 @@ class ImportScripts::Telligent < ImportScripts::Base def get_recipient_user_ids(participant_ids) return [] if participant_ids.blank? - user_ids = participant_ids.split(';') + user_ids = participant_ids.split(";") user_ids.uniq! user_ids.map!(&:strip) end @@ -619,9 +634,9 @@ class ImportScripts::Telligent < ImportScripts::Base def get_recipient_usernames(row) import_user_ids = get_recipient_user_ids(row["ParticipantIds"]) - import_user_ids.map! do |import_user_id| - find_user_by_import_id(import_user_id).try(:username) - end.compact + import_user_ids + .map! { |import_user_id| find_user_by_import_id(import_user_id).try(:username) } + .compact end def index_directory(root_directory) @@ -646,17 +661,16 @@ class ImportScripts::Telligent < ImportScripts::Base filename = row["FileName"] return raw if @filestore_root_directory.blank? || filename.blank? - if row["IsRemote"] - return "#{raw}\n#{filename}" - end + return "#{raw}\n#{filename}" if row["IsRemote"] - path = File.join( - "telligent.evolution.components.attachments", - "%02d" % row["ApplicationTypeId"], - "%02d" % row["ApplicationId"], - "%02d" % row["ApplicationContentTypeId"], - ("%010d" % row["ContentId"]).scan(/.{2}/) - ) + path = + File.join( + "telligent.evolution.components.attachments", + "%02d" % row["ApplicationTypeId"], + "%02d" % row["ApplicationId"], + "%02d" % row["ApplicationContentTypeId"], + ("%010d" % row["ContentId"]).scan(/.{2}/), + ) path = fix_attachment_path(path, filename) if path && !embedded_paths.include?(path) @@ -677,11 +691,11 @@ class ImportScripts::Telligent < ImportScripts::Base def print_file_not_found_error(type, path, row) case type when :topic - id = row['ThreadId'] + id = row["ThreadId"] when :post - id = row['ThreadReplyId'] + id = row["ThreadReplyId"] when :message - id = row['MessageId'] + id = row["MessageId"] end STDERR.puts "Could not find file for #{type} #{id}: #{path}" @@ -692,30 +706,31 @@ class ImportScripts::Telligent < ImportScripts::Base paths = [] upload_ids = [] - return [raw, paths, upload_ids] if @filestore_root_directory.blank? + return raw, paths, upload_ids if @filestore_root_directory.blank? ATTACHMENT_REGEXES.each do |regex| - raw = raw.gsub(regex) do - match_data = Regexp.last_match + raw = + raw.gsub(regex) do + match_data = Regexp.last_match - path = File.join(match_data[:directory], match_data[:path]) - fixed_path = fix_attachment_path(path, match_data[:filename]) + path = File.join(match_data[:directory], match_data[:path]) + fixed_path = fix_attachment_path(path, match_data[:filename]) - if fixed_path && File.file?(fixed_path) - filename = File.basename(fixed_path) - upload = @uploader.create_upload(user_id, fixed_path, filename) + if fixed_path && File.file?(fixed_path) + filename = File.basename(fixed_path) + upload = @uploader.create_upload(user_id, fixed_path, filename) - if upload.present? && upload.persisted? - paths << fixed_path - upload_ids << upload.id - @uploader.html_for_upload(upload, filename) + if upload.present? && upload.persisted? + paths << fixed_path + upload_ids << upload.id + @uploader.html_for_upload(upload, filename) + end + else + path = File.join(path, match_data[:filename]) + print_file_not_found_error(type, path, row) + match_data[0] end - else - path = File.join(path, match_data[:filename]) - print_file_not_found_error(type, path, row) - match_data[0] end - end end [raw, paths, upload_ids] @@ -806,8 +821,8 @@ class ImportScripts::Telligent < ImportScripts::Base md = HtmlToMarkdown.new(html).to_markdown md.gsub!(/\[quote.*?\]/, "\n" + '\0' + "\n") - md.gsub!(/(?/i, "\n```\n") - .gsub(/<\/?code\s*>/i, "`") + raw + .gsub("\\n", "\n") + .gsub(%r{}i, "\n```\n") + .gsub(%r{}i, "`") .gsub("<", "<") .gsub(">", ">") end - end ImportScripts::Vanilla.new.perform diff --git a/script/import_scripts/vanilla_body_parser.rb b/script/import_scripts/vanilla_body_parser.rb index ba4608e3ff..74c39583b9 100644 --- a/script/import_scripts/vanilla_body_parser.rb +++ b/script/import_scripts/vanilla_body_parser.rb @@ -14,9 +14,9 @@ class VanillaBodyParser end def parse - return clean_up(@row['Body']) unless rich? + return clean_up(@row["Body"]) unless rich? - full_text = json.each_with_index.map(&method(:parse_fragment)).join('') + full_text = json.each_with_index.map(&method(:parse_fragment)).join("") normalize full_text end @@ -25,30 +25,46 @@ class VanillaBodyParser def clean_up(text) #
...
- text = text.gsub(/\
(.*?)\<\/pre\>/im) { "\n```\n#{$1}\n```\n" }
+    text = text.gsub(%r{\
(.*?)\}im) { "\n```\n#{$1}\n```\n" }
     # 
...
- text = text.gsub(/\(.*?)\<\/pre\>/im) { "\n```\n#{$1}\n```\n" } + text = text.gsub(%r{\(.*?)\}im) { "\n```\n#{$1}\n```\n" } # - text = text.gsub("\\", "").gsub(/\(.*?)\<\/code\>/im) { "#{$1}" } + text = text.gsub("\\", "").gsub(%r{\(.*?)\}im) { "#{$1}" } #
...
- text = text.gsub(/\
(.*?)\<\/div\>/im) { "\n[quote]\n#{$1}\n[/quote]\n" } + text = text.gsub(%r{\
(.*?)\}im) { "\n[quote]\n#{$1}\n[/quote]\n" } # [code], [quote] - text = text.gsub(/\[\/?code\]/i, "\n```\n").gsub(/\[quote.*?\]/i, "\n" + '\0' + "\n").gsub(/\[\/quote\]/i, "\n" + '\0' + "\n") + text = + text + .gsub(%r{\[/?code\]}i, "\n```\n") + .gsub(/\[quote.*?\]/i, "\n" + '\0' + "\n") + .gsub(%r{\[/quote\]}i, "\n" + '\0' + "\n") - text.gsub(/<\/?font[^>]*>/, '').gsub(/<\/?span[^>]*>/, '').gsub(/<\/?div[^>]*>/, '').gsub(/^ +/, '').gsub(/ +/, ' ') + text + .gsub(%r{]*>}, "") + .gsub(%r{]*>}, "") + .gsub(%r{]*>}, "") + .gsub(/^ +/, "") + .gsub(/ +/, " ") end def rich? - @row['Format'].casecmp?('Rich') + @row["Format"].casecmp?("Rich") end def json return nil unless rich? - @json ||= JSON.parse(@row['Body']).map(&:deep_symbolize_keys) + @json ||= JSON.parse(@row["Body"]).map(&:deep_symbolize_keys) end def parse_fragment(fragment, index) - text = fragment.keys.one? && fragment[:insert].is_a?(String) ? fragment[:insert] : rich_parse(fragment) + text = + ( + if fragment.keys.one? && fragment[:insert].is_a?(String) + fragment[:insert] + else + rich_parse(fragment) + end + ) text = parse_code(text, fragment, index) text = parse_list(text, fragment, index) @@ -59,16 +75,18 @@ class VanillaBodyParser def rich_parse(fragment) insert = fragment[:insert] - return parse_mention(insert[:mention]) if insert.respond_to?(:dig) && insert.dig(:mention, :userID) + if insert.respond_to?(:dig) && insert.dig(:mention, :userID) + return parse_mention(insert[:mention]) + end return parse_formatting(fragment) if fragment[:attributes] - embed_type = insert.dig(:'embed-external', :data, :embedType) + embed_type = insert.dig(:"embed-external", :data, :embedType) - quoting = embed_type == 'quote' + quoting = embed_type == "quote" return parse_quote(insert) if quoting - embed = embed_type.in? ['image', 'link', 'file'] + embed = embed_type.in? %w[image link file] parse_embed(insert, embed_type) if embed end @@ -101,10 +119,10 @@ class VanillaBodyParser def parse_code(text, fragment, index) next_fragment = next_fragment(index) - next_code = next_fragment.dig(:attributes, :'code-block') + next_code = next_fragment.dig(:attributes, :"code-block") if next_code previous_fragment = previous_fragment(index) - previous_code = previous_fragment.dig(:attributes, :'code-block') + previous_code = previous_fragment.dig(:attributes, :"code-block") if previous_code text = text.gsub(/\\n(.*?)\\n/) { "\n```\n#{$1}\n```\n" } @@ -112,7 +130,7 @@ class VanillaBodyParser last_pos = text.rindex(/\n/) if last_pos - array = [text[0..last_pos].strip, text[last_pos + 1 .. text.length].strip] + array = [text[0..last_pos].strip, text[last_pos + 1..text.length].strip] text = array.join("\n```\n") else text = "\n```\n#{text}" @@ -120,10 +138,10 @@ class VanillaBodyParser end end - current_code = fragment.dig(:attributes, :'code-block') + current_code = fragment.dig(:attributes, :"code-block") if current_code second_next_fragment = second_next_fragment(index) - second_next_code = second_next_fragment.dig(:attributes, :'code-block') + second_next_code = second_next_fragment.dig(:attributes, :"code-block") # if current is code and 2 after is not, prepend ``` text = "\n```\n#{text}" unless second_next_code @@ -138,13 +156,13 @@ class VanillaBodyParser next_list = next_fragment.dig(:attributes, :list, :type) if next_list # if next is list, prepend
  • - text = '
  • ' + text + text = "
  • " + text previous_fragment = previous_fragment(index) previous_list = previous_fragment.dig(:attributes, :list, :type) # if next is list and previous is not, prepend
      or
        - list_tag = next_list == 'ordered' ? '
          ' : '
            ' + list_tag = next_list == "ordered" ? "
              " : "
                " text = "\n#{list_tag}\n#{text}" unless previous_list end @@ -152,13 +170,13 @@ class VanillaBodyParser if current_list # if current is list prepend - tag_closings = '' + tag_closings = "" second_next_fragment = second_next_fragment(index) second_next_list = second_next_fragment.dig(:attributes, :list, :type) # if current is list and 2 after is not, prepend
            - list_tag = current_list == 'ordered' ? '
        ' : '
      ' + list_tag = current_list == "ordered" ? "
    " : "" tag_closings = "#{tag_closings}\n#{list_tag}" unless second_next_list text = tag_closings + text @@ -180,24 +198,32 @@ class VanillaBodyParser end def parse_quote(insert) - embed = insert.dig(:'embed-external', :data) + embed = insert.dig(:"embed-external", :data) import_post_id = "#{embed[:recordType]}##{embed[:recordID]}" topic = @@lookup.topic_lookup_from_imported_post_id(import_post_id) user = user_from_imported_id(embed.dig(:insertUser, :userID)) - quote_info = topic && user ? "=\"#{user.username}, post: #{topic[:post_number]}, topic: #{topic[:topic_id]}\"" : '' + quote_info = + ( + if topic && user + "=\"#{user.username}, post: #{topic[:post_number]}, topic: #{topic[:topic_id]}\"" + else + "" + end + ) - "[quote#{quote_info}]\n#{embed[:body]}\n[/quote]\n\n""" + "[quote#{quote_info}]\n#{embed[:body]}\n[/quote]\n\n" \ + "" end def parse_embed(insert, embed_type) - embed = insert.dig(:'embed-external', :data) + embed = insert.dig(:"embed-external", :data) url = embed[:url] - if /https?\:\/\/#{@@host}\/uploads\/.*/.match?(url) - remote_path = url.scan(/uploads\/(.*)/) + if %r{https?\://#{@@host}/uploads/.*}.match?(url) + remote_path = url.scan(%r{uploads/(.*)}) path = File.join(@@uploads_path, remote_path) upload = @@uploader.create_upload(@user_id, path, embed[:name]) @@ -206,7 +232,7 @@ class VanillaBodyParser return "\n" + @@uploader.html_for_upload(upload, embed[:name]) + "\n" else puts "Failed to upload #{path}" - puts upload.errors.full_messages.join(', ') if upload + puts upload.errors.full_messages.join(", ") if upload end end @@ -222,9 +248,9 @@ class VanillaBodyParser def normalize(full_text) code_matcher = /```(.*\n)+```/ code_block = full_text[code_matcher] - full_text[code_matcher] = '{{{CODE_BLOCK}}}' if code_block + full_text[code_matcher] = "{{{CODE_BLOCK}}}" if code_block full_text = double_new_lines(full_text) - full_text['{{{CODE_BLOCK}}}'] = code_block if code_block + full_text["{{{CODE_BLOCK}}}"] = code_block if code_block full_text end diff --git a/script/import_scripts/vanilla_mysql.rb b/script/import_scripts/vanilla_mysql.rb index d0a5e34893..72f294337a 100644 --- a/script/import_scripts/vanilla_mysql.rb +++ b/script/import_scripts/vanilla_mysql.rb @@ -2,12 +2,11 @@ require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require 'htmlentities' -require 'reverse_markdown' -require_relative 'vanilla_body_parser' +require "htmlentities" +require "reverse_markdown" +require_relative "vanilla_body_parser" class ImportScripts::VanillaSQL < ImportScripts::Base - VANILLA_DB = "vanilla" TABLE_PREFIX = "GDN_" ATTACHMENTS_BASE_DIR = nil # "/absolute/path/to/attachments" set the absolute path if you have attachments @@ -17,19 +16,15 @@ class ImportScripts::VanillaSQL < ImportScripts::Base def initialize super @htmlentities = HTMLEntities.new - @client = Mysql2::Client.new( - host: "localhost", - username: "root", - database: VANILLA_DB - ) + @client = Mysql2::Client.new(host: "localhost", username: "root", database: VANILLA_DB) # by default, don't use the body parser as it's not pertinent to all versions @vb_parser = false VanillaBodyParser.configure( lookup: @lookup, uploader: @uploader, - host: 'forum.example.com', # your Vanilla forum domain - uploads_path: 'uploads' # relative path to your vanilla uploads folder + host: "forum.example.com", # your Vanilla forum domain + uploads_path: "uploads", # relative path to your vanilla uploads folder ) @import_tags = false @@ -77,80 +72,83 @@ class ImportScripts::VanillaSQL < ImportScripts::Base SQL create_groups(groups) do |group| - { - id: group["RoleID"], - name: @htmlentities.decode(group["Name"]).strip - } + { id: group["RoleID"], name: @htmlentities.decode(group["Name"]).strip } end end def import_users - puts '', "creating users" + puts "", "creating users" @user_is_deleted = false @last_deleted_username = nil username = nil @last_user_id = -1 - total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}User;").first['count'] + total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}User;").first["count"] batches(BATCH_SIZE) do |offset| - results = mysql_query( - "SELECT UserID, Name, Title, Location, About, Email, Admin, Banned, CountComments, + results = + mysql_query( + "SELECT UserID, Name, Title, Location, About, Email, Admin, Banned, CountComments, DateInserted, DateLastActive, InsertIPAddress FROM #{TABLE_PREFIX}User WHERE UserID > #{@last_user_id} ORDER BY UserID ASC - LIMIT #{BATCH_SIZE};") + LIMIT #{BATCH_SIZE};", + ) break if results.size < 1 - @last_user_id = results.to_a.last['UserID'] - next if all_records_exist? :users, results.map { |u| u['UserID'].to_i } + @last_user_id = results.to_a.last["UserID"] + next if all_records_exist? :users, results.map { |u| u["UserID"].to_i } create_users(results, total: total_count, offset: offset) do |user| - email = user['Email'].squish + email = user["Email"].squish next if email.blank? - next if user['Name'].blank? - next if @lookup.user_id_from_imported_user_id(user['UserID']) - if user['Name'] == '[Deleted User]' + next if user["Name"].blank? + next if @lookup.user_id_from_imported_user_id(user["UserID"]) + if user["Name"] == "[Deleted User]" # EVERY deleted user record in Vanilla has the same username: [Deleted User] # Save our UserNameSuggester some pain: @user_is_deleted = true - username = @last_deleted_username || user['Name'] + username = @last_deleted_username || user["Name"] else @user_is_deleted = false - username = user['Name'] + username = user["Name"] end - banned = user['Banned'] != 0 - commented = (user['CountComments'] || 0) > 0 + banned = user["Banned"] != 0 + commented = (user["CountComments"] || 0) > 0 - { id: user['UserID'], + { + id: user["UserID"], email: email, username: username, - name: user['Name'], - created_at: user['DateInserted'] == nil ? 0 : Time.zone.at(user['DateInserted']), - bio_raw: user['About'], - registration_ip_address: user['InsertIPAddress'], - last_seen_at: user['DateLastActive'] == nil ? 0 : Time.zone.at(user['DateLastActive']), - location: user['Location'], - admin: user['Admin'] == 1, + name: user["Name"], + created_at: user["DateInserted"] == nil ? 0 : Time.zone.at(user["DateInserted"]), + bio_raw: user["About"], + registration_ip_address: user["InsertIPAddress"], + last_seen_at: user["DateLastActive"] == nil ? 0 : Time.zone.at(user["DateLastActive"]), + location: user["Location"], + admin: user["Admin"] == 1, trust_level: !banned && commented ? 2 : 0, - post_create_action: proc do |newuser| - if @user_is_deleted - @last_deleted_username = newuser.username - end - if banned - newuser.suspended_at = Time.now - # banning on Vanilla doesn't have an end, so a thousand years seems equivalent - newuser.suspended_till = 1000.years.from_now - if newuser.save - StaffActionLogger.new(Discourse.system_user).log_user_suspend(newuser, 'Imported from Vanilla Forum') - else - puts "Failed to suspend user #{newuser.username}. #{newuser.errors.full_messages.join(', ')}" + post_create_action: + proc do |newuser| + @last_deleted_username = newuser.username if @user_is_deleted + if banned + newuser.suspended_at = Time.now + # banning on Vanilla doesn't have an end, so a thousand years seems equivalent + newuser.suspended_till = 1000.years.from_now + if newuser.save + StaffActionLogger.new(Discourse.system_user).log_user_suspend( + newuser, + "Imported from Vanilla Forum", + ) + else + puts "Failed to suspend user #{newuser.username}. #{newuser.errors.full_messages.join(", ")}" + end end - end - end } + end, + } end end end @@ -162,7 +160,10 @@ class ImportScripts::VanillaSQL < ImportScripts::Base User.find_each do |u| next unless u.custom_fields["import_id"] - r = mysql_query("SELECT photo FROM #{TABLE_PREFIX}User WHERE UserID = #{u.custom_fields['import_id']};").first + r = + mysql_query( + "SELECT photo FROM #{TABLE_PREFIX}User WHERE UserID = #{u.custom_fields["import_id"]};", + ).first next if r.nil? photo = r["photo"] next unless photo.present? @@ -175,9 +176,9 @@ class ImportScripts::VanillaSQL < ImportScripts::Base photo_real_filename = nil parts = photo.squeeze("/").split("/") if parts[0] =~ /^[a-z0-9]{2}:/ - photo_path = "#{ATTACHMENTS_BASE_DIR}/#{parts[2..-2].join('/')}".squeeze("/") + photo_path = "#{ATTACHMENTS_BASE_DIR}/#{parts[2..-2].join("/")}".squeeze("/") elsif parts[0] == "~cf" - photo_path = "#{ATTACHMENTS_BASE_DIR}/#{parts[1..-2].join('/')}".squeeze("/") + photo_path = "#{ATTACHMENTS_BASE_DIR}/#{parts[1..-2].join("/")}".squeeze("/") else puts "UNKNOWN FORMAT: #{photo}" next @@ -218,7 +219,7 @@ class ImportScripts::VanillaSQL < ImportScripts::Base # Otherwise, the file exists but with a prefix: # The p prefix seems to be the full file, so try to find that one first. - ['p', 't', 'n'].each do |prefix| + %w[p t n].each do |prefix| full_guess = File.join(path, "#{prefix}#{base_guess}") return full_guess if File.exist?(full_guess) end @@ -230,38 +231,43 @@ class ImportScripts::VanillaSQL < ImportScripts::Base def import_group_users puts "", "importing group users..." - group_users = mysql_query(" + group_users = + mysql_query( + " SELECT RoleID, UserID FROM #{TABLE_PREFIX}UserRole - ").to_a + ", + ).to_a group_users.each do |row| user_id = user_id_from_imported_user_id(row["UserID"]) group_id = group_id_from_imported_group_id(row["RoleID"]) - if user_id && group_id - GroupUser.find_or_create_by(user_id: user_id, group_id: group_id) - end + GroupUser.find_or_create_by(user_id: user_id, group_id: group_id) if user_id && group_id end end def import_categories puts "", "importing categories..." - categories = mysql_query(" + categories = + mysql_query( + " SELECT CategoryID, ParentCategoryID, Name, Description FROM #{TABLE_PREFIX}Category WHERE CategoryID > 0 ORDER BY CategoryID ASC - ").to_a + ", + ).to_a - top_level_categories = categories.select { |c| c['ParentCategoryID'].blank? || c['ParentCategoryID'] == -1 } + top_level_categories = + categories.select { |c| c["ParentCategoryID"].blank? || c["ParentCategoryID"] == -1 } create_categories(top_level_categories) do |category| { - id: category['CategoryID'], - name: CGI.unescapeHTML(category['Name']), - description: CGI.unescapeHTML(category['Description']) + id: category["CategoryID"], + name: CGI.unescapeHTML(category["Name"]), + description: CGI.unescapeHTML(category["Description"]), } end @@ -272,37 +278,37 @@ class ImportScripts::VanillaSQL < ImportScripts::Base # Depth = 3 create_categories(subcategories) do |category| { - id: category['CategoryID'], - parent_category_id: category_id_from_imported_category_id(category['ParentCategoryID']), - name: CGI.unescapeHTML(category['Name']), - description: category['Description'] ? CGI.unescapeHTML(category['Description']) : nil, + id: category["CategoryID"], + parent_category_id: category_id_from_imported_category_id(category["ParentCategoryID"]), + name: CGI.unescapeHTML(category["Name"]), + description: category["Description"] ? CGI.unescapeHTML(category["Description"]) : nil, } end - subcategory_ids = Set.new(subcategories.map { |c| c['CategoryID'] }) + subcategory_ids = Set.new(subcategories.map { |c| c["CategoryID"] }) # Depth 4 and 5 need to be tags categories.each do |c| - next if c['ParentCategoryID'] == -1 - next if top_level_category_ids.include?(c['CategoryID']) - next if subcategory_ids.include?(c['CategoryID']) + next if c["ParentCategoryID"] == -1 + next if top_level_category_ids.include?(c["CategoryID"]) + next if subcategory_ids.include?(c["CategoryID"]) # Find a depth 3 category for topics in this category parent = c - while !parent.nil? && !subcategory_ids.include?(parent['CategoryID']) - parent = categories.find { |subcat| subcat['CategoryID'] == parent['ParentCategoryID'] } + while !parent.nil? && !subcategory_ids.include?(parent["CategoryID"]) + parent = categories.find { |subcat| subcat["CategoryID"] == parent["ParentCategoryID"] } end if parent - tag_name = DiscourseTagging.clean_tag(c['Name']) + tag_name = DiscourseTagging.clean_tag(c["Name"]) tag = Tag.find_by_name(tag_name) || Tag.create(name: tag_name) - @category_mappings[c['CategoryID']] = { - category_id: category_id_from_imported_category_id(parent['CategoryID']), - tag: tag[:name] + @category_mappings[c["CategoryID"]] = { + category_id: category_id_from_imported_category_id(parent["CategoryID"]), + tag: tag[:name], } else - puts '', "Couldn't find a category for #{c['CategoryID']} '#{c['Name']}'!" + puts "", "Couldn't find a category for #{c["CategoryID"]} '#{c["Name"]}'!" end end end @@ -310,46 +316,66 @@ class ImportScripts::VanillaSQL < ImportScripts::Base def import_topics puts "", "importing topics..." - tag_names_sql = "select t.name as tag_name from GDN_Tag t, GDN_TagDiscussion td where t.tagid = td.tagid and td.discussionid = {discussionid} and t.name != '';" + tag_names_sql = + "select t.name as tag_name from GDN_Tag t, GDN_TagDiscussion td where t.tagid = td.tagid and td.discussionid = {discussionid} and t.name != '';" - total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}Discussion;").first['count'] + total_count = + mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}Discussion;").first["count"] @last_topic_id = -1 batches(BATCH_SIZE) do |offset| - discussions = mysql_query( - "SELECT DiscussionID, CategoryID, Name, Body, Format, CountViews, Closed, Announce, + discussions = + mysql_query( + "SELECT DiscussionID, CategoryID, Name, Body, Format, CountViews, Closed, Announce, DateInserted, InsertUserID, DateLastComment FROM #{TABLE_PREFIX}Discussion WHERE DiscussionID > #{@last_topic_id} ORDER BY DiscussionID ASC - LIMIT #{BATCH_SIZE};") + LIMIT #{BATCH_SIZE};", + ) break if discussions.size < 1 - @last_topic_id = discussions.to_a.last['DiscussionID'] - next if all_records_exist? :posts, discussions.map { |t| "discussion#" + t['DiscussionID'].to_s } + @last_topic_id = discussions.to_a.last["DiscussionID"] + if all_records_exist? :posts, discussions.map { |t| "discussion#" + t["DiscussionID"].to_s } + next + end create_posts(discussions, total: total_count, offset: offset) do |discussion| - user_id = user_id_from_imported_user_id(discussion['InsertUserID']) || Discourse::SYSTEM_USER_ID + user_id = + user_id_from_imported_user_id(discussion["InsertUserID"]) || Discourse::SYSTEM_USER_ID { - id: "discussion#" + discussion['DiscussionID'].to_s, + id: "discussion#" + discussion["DiscussionID"].to_s, user_id: user_id, - title: discussion['Name'], - category: category_id_from_imported_category_id(discussion['CategoryID']) || @category_mappings[discussion['CategoryID']].try(:[], :category_id), + title: discussion["Name"], + category: + category_id_from_imported_category_id(discussion["CategoryID"]) || + @category_mappings[discussion["CategoryID"]].try(:[], :category_id), raw: get_raw(discussion, user_id), - views: discussion['CountViews'] || 0, - closed: discussion['Closed'] == 1, - pinned_at: discussion['Announce'] == 0 ? nil : Time.zone.at(discussion['DateLastComment'] || discussion['DateInserted']), - pinned_globally: discussion['Announce'] == 1, - created_at: Time.zone.at(discussion['DateInserted']), - post_create_action: proc do |post| - if @import_tags - tag_names = @client.query(tag_names_sql.gsub('{discussionid}', discussion['DiscussionID'].to_s)).map { |row| row['tag_name'] } - category_tag = @category_mappings[discussion['CategoryID']].try(:[], :tag) - tag_names = category_tag ? tag_names.append(category_tag) : tag_names - DiscourseTagging.tag_topic_by_names(post.topic, staff_guardian, tag_names) - end - end + views: discussion["CountViews"] || 0, + closed: discussion["Closed"] == 1, + pinned_at: + ( + if discussion["Announce"] == 0 + nil + else + Time.zone.at(discussion["DateLastComment"] || discussion["DateInserted"]) + end + ), + pinned_globally: discussion["Announce"] == 1, + created_at: Time.zone.at(discussion["DateInserted"]), + post_create_action: + proc do |post| + if @import_tags + tag_names = + @client + .query(tag_names_sql.gsub("{discussionid}", discussion["DiscussionID"].to_s)) + .map { |row| row["tag_name"] } + category_tag = @category_mappings[discussion["CategoryID"]].try(:[], :tag) + tag_names = category_tag ? tag_names.append(category_tag) : tag_names + DiscourseTagging.tag_topic_by_names(post.topic, staff_guardian, tag_names) + end + end, } end end @@ -358,36 +384,42 @@ class ImportScripts::VanillaSQL < ImportScripts::Base def import_posts puts "", "importing posts..." - total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}Comment;").first['count'] + total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}Comment;").first["count"] @last_post_id = -1 batches(BATCH_SIZE) do |offset| - comments = mysql_query( - "SELECT CommentID, DiscussionID, Body, Format, + comments = + mysql_query( + "SELECT CommentID, DiscussionID, Body, Format, DateInserted, InsertUserID, QnA FROM #{TABLE_PREFIX}Comment WHERE CommentID > #{@last_post_id} ORDER BY CommentID ASC - LIMIT #{BATCH_SIZE};") + LIMIT #{BATCH_SIZE};", + ) break if comments.size < 1 - @last_post_id = comments.to_a.last['CommentID'] - next if all_records_exist? :posts, comments.map { |comment| "comment#" + comment['CommentID'].to_s } + @last_post_id = comments.to_a.last["CommentID"] + if all_records_exist? :posts, + comments.map { |comment| "comment#" + comment["CommentID"].to_s } + next + end create_posts(comments, total: total_count, offset: offset) do |comment| - next unless t = topic_lookup_from_imported_post_id("discussion#" + comment['DiscussionID'].to_s) - next if comment['Body'].blank? - user_id = user_id_from_imported_user_id(comment['InsertUserID']) || Discourse::SYSTEM_USER_ID + unless t = topic_lookup_from_imported_post_id("discussion#" + comment["DiscussionID"].to_s) + next + end + next if comment["Body"].blank? + user_id = + user_id_from_imported_user_id(comment["InsertUserID"]) || Discourse::SYSTEM_USER_ID post = { - id: "comment#" + comment['CommentID'].to_s, + id: "comment#" + comment["CommentID"].to_s, user_id: user_id, topic_id: t[:topic_id], raw: get_raw(comment, user_id), - created_at: Time.zone.at(comment['DateInserted']) + created_at: Time.zone.at(comment["DateInserted"]), } - if comment['QnA'] == "Accepted" - post[:custom_fields] = { is_accepted_answer: true } - end + post[:custom_fields] = { is_accepted_answer: true } if comment["QnA"] == "Accepted" post end @@ -397,19 +429,22 @@ class ImportScripts::VanillaSQL < ImportScripts::Base def import_likes puts "", "importing likes..." - total_count = mysql_query("SELECT count(*) count FROM GDN_ThanksLog;").first['count'] + total_count = mysql_query("SELECT count(*) count FROM GDN_ThanksLog;").first["count"] current_count = 0 start_time = Time.now - likes = mysql_query(" + likes = + mysql_query( + " SELECT CommentID, DateInserted, InsertUserID FROM #{TABLE_PREFIX}ThanksLog ORDER BY CommentID ASC; - ") + ", + ) likes.each do |like| - post_id = post_id_from_imported_post_id("comment##{like['CommentID']}") - user_id = user_id_from_imported_user_id(like['InsertUserID']) + post_id = post_id_from_imported_post_id("comment##{like["CommentID"]}") + user_id = user_id_from_imported_user_id(like["InsertUserID"]) post = Post.find(post_id) if post_id user = User.find(user_id) if user_id @@ -428,51 +463,58 @@ class ImportScripts::VanillaSQL < ImportScripts::Base def import_messages puts "", "importing messages..." - total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}ConversationMessage;").first['count'] + total_count = + mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}ConversationMessage;").first["count"] @last_message_id = -1 batches(BATCH_SIZE) do |offset| - messages = mysql_query( - "SELECT m.MessageID, m.Body, m.Format, + messages = + mysql_query( + "SELECT m.MessageID, m.Body, m.Format, m.InsertUserID, m.DateInserted, m.ConversationID, c.Contributors FROM #{TABLE_PREFIX}ConversationMessage m INNER JOIN #{TABLE_PREFIX}Conversation c on c.ConversationID = m.ConversationID WHERE m.MessageID > #{@last_message_id} ORDER BY m.MessageID ASC - LIMIT #{BATCH_SIZE};") + LIMIT #{BATCH_SIZE};", + ) break if messages.size < 1 - @last_message_id = messages.to_a.last['MessageID'] - next if all_records_exist? :posts, messages.map { |t| "message#" + t['MessageID'].to_s } + @last_message_id = messages.to_a.last["MessageID"] + next if all_records_exist? :posts, messages.map { |t| "message#" + t["MessageID"].to_s } create_posts(messages, total: total_count, offset: offset) do |message| - user_id = user_id_from_imported_user_id(message['InsertUserID']) || Discourse::SYSTEM_USER_ID + user_id = + user_id_from_imported_user_id(message["InsertUserID"]) || Discourse::SYSTEM_USER_ID body = get_raw(message, user_id) common = { user_id: user_id, raw: body, - created_at: Time.zone.at(message['DateInserted']), + created_at: Time.zone.at(message["DateInserted"]), custom_fields: { - conversation_id: message['ConversationID'], - participants: message['Contributors'], - message_id: message['MessageID'] - } + conversation_id: message["ConversationID"], + participants: message["Contributors"], + message_id: message["MessageID"], + }, } - conversation_id = "conversation#" + message['ConversationID'].to_s - message_id = "message#" + message['MessageID'].to_s + conversation_id = "conversation#" + message["ConversationID"].to_s + message_id = "message#" + message["MessageID"].to_s imported_conversation = topic_lookup_from_imported_post_id(conversation_id) if imported_conversation.present? common.merge(id: message_id, topic_id: imported_conversation[:topic_id]) else - user_ids = (message['Contributors'] || '').scan(/\"(\d+)\"/).flatten.map(&:to_i) - usernames = user_ids.map { |id| @lookup.find_user_by_import_id(id).try(:username) }.compact - usernames = [@lookup.find_user_by_import_id(message['InsertUserID']).try(:username)].compact if usernames.empty? + user_ids = (message["Contributors"] || "").scan(/\"(\d+)\"/).flatten.map(&:to_i) + usernames = + user_ids.map { |id| @lookup.find_user_by_import_id(id).try(:username) }.compact + usernames = [ + @lookup.find_user_by_import_id(message["InsertUserID"]).try(:username), + ].compact if usernames.empty? title = body.truncate(40) { @@ -487,8 +529,8 @@ class ImportScripts::VanillaSQL < ImportScripts::Base end def get_raw(record, user_id) - format = (record['Format'] || "").downcase - body = record['Body'] + format = (record["Format"] || "").downcase + body = record["Body"] case format when "html" @@ -507,7 +549,7 @@ class ImportScripts::VanillaSQL < ImportScripts::Base raw = @htmlentities.decode(raw) # convert user profile links to user mentions - raw.gsub!(/(@\S+?)<\/a>/) { $1 } + raw.gsub!(%r{(@\S+?)}) { $1 } raw = ReverseMarkdown.convert(raw) unless skip_reverse_markdown @@ -526,14 +568,21 @@ class ImportScripts::VanillaSQL < ImportScripts::Base end def create_permalinks - puts '', 'Creating redirects...', '' + puts "", "Creating redirects...", "" User.find_each do |u| ucf = u.custom_fields if ucf && ucf["import_id"] && ucf["import_username"] - encoded_username = CGI.escape(ucf['import_username']).gsub('+', '%20') - Permalink.create(url: "profile/#{ucf['import_id']}/#{encoded_username}", external_url: "/users/#{u.username}") rescue nil - print '.' + encoded_username = CGI.escape(ucf["import_username"]).gsub("+", "%20") + begin + Permalink.create( + url: "profile/#{ucf["import_id"]}/#{encoded_username}", + external_url: "/users/#{u.username}", + ) + rescue StandardError + nil + end + print "." end end @@ -541,14 +590,22 @@ class ImportScripts::VanillaSQL < ImportScripts::Base pcf = post.custom_fields if pcf && pcf["import_id"] topic = post.topic - id = pcf["import_id"].split('#').last + id = pcf["import_id"].split("#").last if post.post_number == 1 slug = Slug.for(topic.title) # probably matches what vanilla would do... - Permalink.create(url: "discussion/#{id}/#{slug}", topic_id: topic.id) rescue nil + begin + Permalink.create(url: "discussion/#{id}/#{slug}", topic_id: topic.id) + rescue StandardError + nil + end else - Permalink.create(url: "discussion/comment/#{id}", post_id: post.id) rescue nil + begin + Permalink.create(url: "discussion/comment/#{id}", post_id: post.id) + rescue StandardError + nil + end end - print '.' + print "." end end end @@ -561,75 +618,86 @@ class ImportScripts::VanillaSQL < ImportScripts::Base count = 0 # https://us.v-cdn.net/1234567/uploads/editor/xyz/image.jpg - cdn_regex = /https:\/\/us.v-cdn.net\/1234567\/uploads\/(\S+\/(\w|-)+.\w+)/i + cdn_regex = %r{https://us.v-cdn.net/1234567/uploads/(\S+/(\w|-)+.\w+)}i # [attachment=10109:Screen Shot 2012-04-01 at 3.47.35 AM.png] attachment_regex = /\[attachment=(\d+):(.*?)\]/i - Post.where("raw LIKE '%/us.v-cdn.net/%' OR raw LIKE '%[attachment%'").find_each do |post| - count += 1 - print "\r%7d - %6d/sec" % [count, count.to_f / (Time.now - start)] - new_raw = post.raw.dup + Post + .where("raw LIKE '%/us.v-cdn.net/%' OR raw LIKE '%[attachment%'") + .find_each do |post| + count += 1 + print "\r%7d - %6d/sec" % [count, count.to_f / (Time.now - start)] + new_raw = post.raw.dup - new_raw.gsub!(attachment_regex) do |s| - matches = attachment_regex.match(s) - attachment_id = matches[1] - file_name = matches[2] - next unless attachment_id + new_raw.gsub!(attachment_regex) do |s| + matches = attachment_regex.match(s) + attachment_id = matches[1] + file_name = matches[2] + next unless attachment_id - r = mysql_query("SELECT Path, Name FROM #{TABLE_PREFIX}Media WHERE MediaID = #{attachment_id};").first - next if r.nil? - path = r["Path"] - name = r["Name"] - next unless path.present? + r = + mysql_query( + "SELECT Path, Name FROM #{TABLE_PREFIX}Media WHERE MediaID = #{attachment_id};", + ).first + next if r.nil? + path = r["Path"] + name = r["Name"] + next unless path.present? - path.gsub!("s3://content/", "") - path.gsub!("s3://uploads/", "") - file_path = "#{ATTACHMENTS_BASE_DIR}/#{path}" + path.gsub!("s3://content/", "") + path.gsub!("s3://uploads/", "") + file_path = "#{ATTACHMENTS_BASE_DIR}/#{path}" - if File.exist?(file_path) - upload = create_upload(post.user.id, file_path, File.basename(file_path)) - if upload && upload.errors.empty? - # upload.url - filename = name || file_name || File.basename(file_path) - html_for_upload(upload, normalize_text(filename)) + if File.exist?(file_path) + upload = create_upload(post.user.id, file_path, File.basename(file_path)) + if upload && upload.errors.empty? + # upload.url + filename = name || file_name || File.basename(file_path) + html_for_upload(upload, normalize_text(filename)) + else + puts "Error: Upload did not persist for #{post.id} #{attachment_id}!" + end else - puts "Error: Upload did not persist for #{post.id} #{attachment_id}!" + puts "Couldn't find file for #{attachment_id}. Skipping." + next end - else - puts "Couldn't find file for #{attachment_id}. Skipping." - next end - end - new_raw.gsub!(cdn_regex) do |s| - matches = cdn_regex.match(s) - attachment_id = matches[1] + new_raw.gsub!(cdn_regex) do |s| + matches = cdn_regex.match(s) + attachment_id = matches[1] - file_path = "#{ATTACHMENTS_BASE_DIR}/#{attachment_id}" + file_path = "#{ATTACHMENTS_BASE_DIR}/#{attachment_id}" - if File.exist?(file_path) - upload = create_upload(post.user.id, file_path, File.basename(file_path)) - if upload && upload.errors.empty? - upload.url + if File.exist?(file_path) + upload = create_upload(post.user.id, file_path, File.basename(file_path)) + if upload && upload.errors.empty? + upload.url + else + puts "Error: Upload did not persist for #{post.id} #{attachment_id}!" + end else - puts "Error: Upload did not persist for #{post.id} #{attachment_id}!" + puts "Couldn't find file for #{attachment_id}. Skipping." + next end - else - puts "Couldn't find file for #{attachment_id}. Skipping." - next end - end - if new_raw != post.raw - begin - PostRevisor.new(post).revise!(post.user, { raw: new_raw }, skip_revision: true, skip_validations: true, bypass_bump: true) - rescue - puts "PostRevisor error for #{post.id}" - post.raw = new_raw - post.save(validate: false) + if new_raw != post.raw + begin + PostRevisor.new(post).revise!( + post.user, + { raw: new_raw }, + skip_revision: true, + skip_validations: true, + bypass_bump: true, + ) + rescue StandardError + puts "PostRevisor error for #{post.id}" + post.raw = new_raw + post.save(validate: false) + end end end - end end end diff --git a/script/import_scripts/vbulletin.rb b/script/import_scripts/vbulletin.rb index 8b3de80bdc..f534b43848 100644 --- a/script/import_scripts/vbulletin.rb +++ b/script/import_scripts/vbulletin.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -require 'mysql2' +require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require 'htmlentities' +require "htmlentities" begin - require 'php_serialize' # https://github.com/jqr/php-serialize + require "php_serialize" # https://github.com/jqr/php-serialize rescue LoadError puts - puts 'php_serialize not found.' - puts 'Add to Gemfile, like this: ' + puts "php_serialize not found." + puts "Add to Gemfile, like this: " puts puts "echo gem \\'php-serialize\\' >> Gemfile" puts "bundle install" @@ -23,13 +23,13 @@ class ImportScripts::VBulletin < ImportScripts::Base # CHANGE THESE BEFORE RUNNING THE IMPORTER - DB_HOST ||= ENV['DB_HOST'] || "localhost" - DB_NAME ||= ENV['DB_NAME'] || "vbulletin" - DB_PW ||= ENV['DB_PW'] || "" - DB_USER ||= ENV['DB_USER'] || "root" - TIMEZONE ||= ENV['TIMEZONE'] || "America/Los_Angeles" - TABLE_PREFIX ||= ENV['TABLE_PREFIX'] || "vb_" - ATTACHMENT_DIR ||= ENV['ATTACHMENT_DIR'] || '/path/to/your/attachment/folder' + DB_HOST ||= ENV["DB_HOST"] || "localhost" + DB_NAME ||= ENV["DB_NAME"] || "vbulletin" + DB_PW ||= ENV["DB_PW"] || "" + DB_USER ||= ENV["DB_USER"] || "root" + TIMEZONE ||= ENV["TIMEZONE"] || "America/Los_Angeles" + TABLE_PREFIX ||= ENV["TABLE_PREFIX"] || "vb_" + ATTACHMENT_DIR ||= ENV["ATTACHMENT_DIR"] || "/path/to/your/attachment/folder" puts "#{DB_USER}:#{DB_PW}@#{DB_HOST} wants #{DB_NAME}" @@ -44,16 +44,12 @@ class ImportScripts::VBulletin < ImportScripts::Base @htmlentities = HTMLEntities.new - @client = Mysql2::Client.new( - host: DB_HOST, - username: DB_USER, - password: DB_PW, - database: DB_NAME - ) - rescue Exception => e - puts '=' * 50 - puts e.message - puts <<~TEXT + @client = + Mysql2::Client.new(host: DB_HOST, username: DB_USER, password: DB_PW, database: DB_NAME) + rescue Exception => e + puts "=" * 50 + puts e.message + puts <<~TEXT Cannot connect in to database. Hostname: #{DB_HOST} @@ -72,11 +68,15 @@ class ImportScripts::VBulletin < ImportScripts::Base Exiting. TEXT - exit + exit end def execute - mysql_query("CREATE INDEX firstpostid_index ON #{TABLE_PREFIX}thread (firstpostid)") rescue nil + begin + mysql_query("CREATE INDEX firstpostid_index ON #{TABLE_PREFIX}thread (firstpostid)") + rescue StandardError + nil + end import_groups import_users @@ -104,10 +104,7 @@ class ImportScripts::VBulletin < ImportScripts::Base SQL create_groups(groups) do |group| - { - id: group["usergroupid"], - name: @htmlentities.decode(group["title"]).strip - } + { id: group["usergroupid"], name: @htmlentities.decode(group["title"]).strip } end end @@ -127,7 +124,7 @@ class ImportScripts::VBulletin < ImportScripts::Base last_user_id = -1 batches(BATCH_SIZE) do |offset| - users = mysql_query(<<-SQL + users = mysql_query(<<-SQL).to_a SELECT userid , username , homepage @@ -142,7 +139,6 @@ class ImportScripts::VBulletin < ImportScripts::Base ORDER BY userid LIMIT #{BATCH_SIZE} SQL - ).to_a break if users.empty? @@ -169,15 +165,21 @@ class ImportScripts::VBulletin < ImportScripts::Base primary_group_id: group_id_from_imported_group_id(user["usergroupid"].to_i), created_at: parse_timestamp(user["joindate"]), last_seen_at: parse_timestamp(user["lastvisit"]), - post_create_action: proc do |u| - import_profile_picture(user, u) - import_profile_background(user, u) - end + post_create_action: + proc do |u| + import_profile_picture(user, u) + import_profile_background(user, u) + end, } end end - @usernames = UserCustomField.joins(:user).where(name: 'import_username').pluck('user_custom_fields.value', 'users.username').to_h + @usernames = + UserCustomField + .joins(:user) + .where(name: "import_username") + .pluck("user_custom_fields.value", "users.username") + .to_h end def create_groups_membership @@ -190,7 +192,10 @@ class ImportScripts::VBulletin < ImportScripts::Base next if GroupUser.where(group_id: group.id).count > 0 user_ids_in_group = User.where(primary_group_id: group.id).pluck(:id).to_a next if user_ids_in_group.size == 0 - values = user_ids_in_group.map { |user_id| "(#{group.id}, #{user_id}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)" }.join(",") + values = + user_ids_in_group + .map { |user_id| "(#{group.id}, #{user_id}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)" } + .join(",") DB.exec <<~SQL INSERT INTO group_users (group_id, user_id, created_at, updated_at) VALUES #{values} @@ -230,8 +235,16 @@ class ImportScripts::VBulletin < ImportScripts::Base imported_user.user_avatar.update(custom_upload_id: upload.id) imported_user.update(uploaded_avatar_id: upload.id) ensure - file.close rescue nil - file.unlind rescue nil + begin + file.close + rescue StandardError + nil + end + begin + file.unlind + rescue StandardError + nil + end end def import_profile_background(old_user, imported_user) @@ -258,14 +271,25 @@ class ImportScripts::VBulletin < ImportScripts::Base imported_user.user_profile.upload_profile_background(upload) ensure - file.close rescue nil - file.unlink rescue nil + begin + file.close + rescue StandardError + nil + end + begin + file.unlink + rescue StandardError + nil + end end def import_categories puts "", "importing top level categories..." - categories = mysql_query("SELECT forumid, title, description, displayorder, parentid FROM #{TABLE_PREFIX}forum ORDER BY forumid").to_a + categories = + mysql_query( + "SELECT forumid, title, description, displayorder, parentid FROM #{TABLE_PREFIX}forum ORDER BY forumid", + ).to_a top_level_categories = categories.select { |c| c["parentid"] == -1 } @@ -274,7 +298,7 @@ class ImportScripts::VBulletin < ImportScripts::Base id: category["forumid"], name: @htmlentities.decode(category["title"]).strip, position: category["displayorder"], - description: @htmlentities.decode(category["description"]).strip + description: @htmlentities.decode(category["description"]).strip, } end @@ -296,7 +320,7 @@ class ImportScripts::VBulletin < ImportScripts::Base name: @htmlentities.decode(category["title"]).strip, position: category["displayorder"], description: @htmlentities.decode(category["description"]).strip, - parent_category_id: category_id_from_imported_category_id(category["parentid"]) + parent_category_id: category_id_from_imported_category_id(category["parentid"]), } end end @@ -304,12 +328,13 @@ class ImportScripts::VBulletin < ImportScripts::Base def import_topics puts "", "importing topics..." - topic_count = mysql_query("SELECT COUNT(threadid) count FROM #{TABLE_PREFIX}thread").first["count"] + topic_count = + mysql_query("SELECT COUNT(threadid) count FROM #{TABLE_PREFIX}thread").first["count"] last_topic_id = -1 batches(BATCH_SIZE) do |offset| - topics = mysql_query(<<-SQL + topics = mysql_query(<<-SQL).to_a SELECT t.threadid threadid, t.title title, forumid, open, postuserid, t.dateline dateline, views, t.visible visible, sticky, p.pagetext raw FROM #{TABLE_PREFIX}thread t @@ -318,7 +343,6 @@ class ImportScripts::VBulletin < ImportScripts::Base ORDER BY t.threadid LIMIT #{BATCH_SIZE} SQL - ).to_a break if topics.empty? @@ -326,7 +350,12 @@ class ImportScripts::VBulletin < ImportScripts::Base topics.reject! { |t| @lookup.post_already_imported?("thread-#{t["threadid"]}") } create_posts(topics, total: topic_count, offset: offset) do |topic| - raw = preprocess_post_raw(topic["raw"]) rescue nil + raw = + begin + preprocess_post_raw(topic["raw"]) + rescue StandardError + nil + end next if raw.blank? topic_id = "thread-#{topic["threadid"]}" t = { @@ -351,28 +380,28 @@ class ImportScripts::VBulletin < ImportScripts::Base topic = topic_lookup_from_imported_post_id(topic_id) if topic.present? url_slug = "thread/#{thread["threadid"]}" if thread["title"].present? - Permalink.create(url: url_slug, topic_id: topic[:topic_id].to_i) if url_slug.present? && topic[:topic_id].present? + if url_slug.present? && topic[:topic_id].present? + Permalink.create(url: url_slug, topic_id: topic[:topic_id].to_i) + end end end - end end def import_posts puts "", "importing posts..." - post_count = mysql_query(<<-SQL + post_count = mysql_query(<<-SQL).first["count"] SELECT COUNT(postid) count FROM #{TABLE_PREFIX}post p JOIN #{TABLE_PREFIX}thread t ON t.threadid = p.threadid WHERE t.firstpostid <> p.postid SQL - ).first["count"] last_post_id = -1 batches(BATCH_SIZE) do |offset| - posts = mysql_query(<<-SQL + posts = mysql_query(<<-SQL).to_a SELECT p.postid, p.userid, p.threadid, p.pagetext raw, p.dateline, p.visible, p.parentid FROM #{TABLE_PREFIX}post p JOIN #{TABLE_PREFIX}thread t ON t.threadid = p.threadid @@ -381,7 +410,6 @@ class ImportScripts::VBulletin < ImportScripts::Base ORDER BY p.postid LIMIT #{BATCH_SIZE} SQL - ).to_a break if posts.empty? @@ -389,7 +417,12 @@ class ImportScripts::VBulletin < ImportScripts::Base posts.reject! { |p| @lookup.post_already_imported?(p["postid"].to_i) } create_posts(posts, total: post_count, offset: offset) do |post| - raw = preprocess_post_raw(post["raw"]) rescue nil + raw = + begin + preprocess_post_raw(post["raw"]) + rescue StandardError + nil + end next if raw.blank? next unless topic = topic_lookup_from_imported_post_id("thread-#{post["threadid"]}") p = { @@ -410,7 +443,8 @@ class ImportScripts::VBulletin < ImportScripts::Base # find the uploaded file information from the db def find_upload(post, attachment_id) - sql = "SELECT a.attachmentid attachment_id, a.userid user_id, a.filedataid file_id, a.filename filename, + sql = + "SELECT a.attachmentid attachment_id, a.userid user_id, a.filedataid file_id, a.filename filename, LENGTH(fd.filedata) AS dbsize, filedata, a.caption caption FROM #{TABLE_PREFIX}attachment a LEFT JOIN #{TABLE_PREFIX}filedata fd ON fd.filedataid = a.filedataid @@ -418,25 +452,24 @@ class ImportScripts::VBulletin < ImportScripts::Base results = mysql_query(sql) unless row = results.first - puts "Couldn't find attachment record for post.id = #{post.id}, import_id = #{post.custom_fields['import_id']}" + puts "Couldn't find attachment record for post.id = #{post.id}, import_id = #{post.custom_fields["import_id"]}" return end - filename = File.join(ATTACHMENT_DIR, row['user_id'].to_s.split('').join('/'), "#{row['file_id']}.attach") - real_filename = row['filename'] - real_filename.prepend SecureRandom.hex if real_filename[0] == '.' + filename = + File.join(ATTACHMENT_DIR, row["user_id"].to_s.split("").join("/"), "#{row["file_id"]}.attach") + real_filename = row["filename"] + real_filename.prepend SecureRandom.hex if real_filename[0] == "." unless File.exist?(filename) - if row['dbsize'].to_i == 0 - puts "Attachment file #{row['filedataid']} doesn't exist" + if row["dbsize"].to_i == 0 + puts "Attachment file #{row["filedataid"]} doesn't exist" return nil end - tmpfile = 'attach_' + row['filedataid'].to_s - filename = File.join('/tmp/', tmpfile) - File.open(filename, 'wb') { |f| - f.write(row['filedata']) - } + tmpfile = "attach_" + row["filedataid"].to_s + filename = File.join("/tmp/", tmpfile) + File.open(filename, "wb") { |f| f.write(row["filedata"]) } end upload = create_upload(post.user.id, filename, real_filename) @@ -457,24 +490,24 @@ class ImportScripts::VBulletin < ImportScripts::Base def import_private_messages puts "", "importing private messages..." - topic_count = mysql_query("SELECT COUNT(pmtextid) count FROM #{TABLE_PREFIX}pmtext").first["count"] + topic_count = + mysql_query("SELECT COUNT(pmtextid) count FROM #{TABLE_PREFIX}pmtext").first["count"] last_private_message_id = -1 batches(BATCH_SIZE) do |offset| - private_messages = mysql_query(<<-SQL + private_messages = mysql_query(<<-SQL).to_a SELECT pmtextid, fromuserid, title, message, touserarray, dateline FROM #{TABLE_PREFIX}pmtext WHERE pmtextid > #{last_private_message_id} ORDER BY pmtextid LIMIT #{BATCH_SIZE} SQL - ).to_a break if private_messages.empty? last_private_message_id = private_messages[-1]["pmtextid"] - private_messages.reject! { |pm| @lookup.post_already_imported?("pm-#{pm['pmtextid']}") } + private_messages.reject! { |pm| @lookup.post_already_imported?("pm-#{pm["pmtextid"]}") } title_username_of_pm_first_post = {} @@ -482,11 +515,16 @@ class ImportScripts::VBulletin < ImportScripts::Base skip = false mapped = {} - mapped[:id] = "pm-#{m['pmtextid']}" - mapped[:user_id] = user_id_from_imported_user_id(m['fromuserid']) || Discourse::SYSTEM_USER_ID - mapped[:raw] = preprocess_post_raw(m['message']) rescue nil - mapped[:created_at] = Time.zone.at(m['dateline']) - title = @htmlentities.decode(m['title']).strip[0...255] + mapped[:id] = "pm-#{m["pmtextid"]}" + mapped[:user_id] = user_id_from_imported_user_id(m["fromuserid"]) || + Discourse::SYSTEM_USER_ID + mapped[:raw] = begin + preprocess_post_raw(m["message"]) + rescue StandardError + nil + end + mapped[:created_at] = Time.zone.at(m["dateline"]) + title = @htmlentities.decode(m["title"]).strip[0...255] topic_id = nil next if mapped[:raw].blank? @@ -495,9 +533,9 @@ class ImportScripts::VBulletin < ImportScripts::Base target_usernames = [] target_userids = [] begin - to_user_array = PHP.unserialize(m['touserarray']) - rescue - puts "#{m['pmtextid']} -- #{m['touserarray']}" + to_user_array = PHP.unserialize(m["touserarray"]) + rescue StandardError + puts "#{m["pmtextid"]} -- #{m["touserarray"]}" skip = true end @@ -517,8 +555,8 @@ class ImportScripts::VBulletin < ImportScripts::Base target_usernames << username if username end end - rescue - puts "skipping pm-#{m['pmtextid']} `to_user_array` is not properly serialized -- #{to_user_array.inspect}" + rescue StandardError + puts "skipping pm-#{m["pmtextid"]} `to_user_array` is not properly serialized -- #{to_user_array.inspect}" skip = true end @@ -526,18 +564,18 @@ class ImportScripts::VBulletin < ImportScripts::Base participants << mapped[:user_id] begin participants.sort! - rescue + rescue StandardError puts "one of the participant's id is nil -- #{participants.inspect}" end if title =~ /^Re:/ - - parent_id = title_username_of_pm_first_post[[title[3..-1], participants]] || - title_username_of_pm_first_post[[title[4..-1], participants]] || - title_username_of_pm_first_post[[title[5..-1], participants]] || - title_username_of_pm_first_post[[title[6..-1], participants]] || - title_username_of_pm_first_post[[title[7..-1], participants]] || - title_username_of_pm_first_post[[title[8..-1], participants]] + parent_id = + title_username_of_pm_first_post[[title[3..-1], participants]] || + title_username_of_pm_first_post[[title[4..-1], participants]] || + title_username_of_pm_first_post[[title[5..-1], participants]] || + title_username_of_pm_first_post[[title[6..-1], participants]] || + title_username_of_pm_first_post[[title[7..-1], participants]] || + title_username_of_pm_first_post[[title[8..-1], participants]] if parent_id if t = topic_lookup_from_imported_post_id("pm-#{parent_id}") @@ -545,18 +583,18 @@ class ImportScripts::VBulletin < ImportScripts::Base end end else - title_username_of_pm_first_post[[title, participants]] ||= m['pmtextid'] + title_username_of_pm_first_post[[title, participants]] ||= m["pmtextid"] end unless topic_id mapped[:title] = title mapped[:archetype] = Archetype.private_message - mapped[:target_usernames] = target_usernames.join(',') + mapped[:target_usernames] = target_usernames.join(",") if mapped[:target_usernames].size < 1 # pm with yourself? # skip = true mapped[:target_usernames] = "system" - puts "pm-#{m['pmtextid']} has no target (#{m['touserarray']})" + puts "pm-#{m["pmtextid"]} has no target (#{m["touserarray"]})" end else mapped[:topic_id] = topic_id @@ -568,25 +606,24 @@ class ImportScripts::VBulletin < ImportScripts::Base end def import_attachments - puts '', 'importing attachments...' + puts "", "importing attachments..." mapping = {} - attachments = mysql_query(<<-SQL + attachments = mysql_query(<<-SQL) SELECT a.attachmentid, a.contentid as postid, p.threadid FROM #{TABLE_PREFIX}attachment a, #{TABLE_PREFIX}post p WHERE a.contentid = p.postid AND contenttypeid = 1 AND state = 'visible' SQL - ) attachments.each do |attachment| - post_id = post_id_from_imported_post_id(attachment['postid']) - post_id = post_id_from_imported_post_id("thread-#{attachment['threadid']}") unless post_id + post_id = post_id_from_imported_post_id(attachment["postid"]) + post_id = post_id_from_imported_post_id("thread-#{attachment["threadid"]}") unless post_id if post_id.nil? - puts "Post for attachment #{attachment['attachmentid']} not found" + puts "Post for attachment #{attachment["attachmentid"]} not found" next end mapping[post_id] ||= [] - mapping[post_id] << attachment['attachmentid'].to_i + mapping[post_id] << attachment["attachmentid"].to_i end current_count = 0 @@ -594,7 +631,7 @@ class ImportScripts::VBulletin < ImportScripts::Base success_count = 0 fail_count = 0 - attachment_regex = /\[attach[^\]]*\](\d+)\[\/attach\]/i + attachment_regex = %r{\[attach[^\]]*\](\d+)\[/attach\]}i Post.find_each do |post| current_count += 1 @@ -605,9 +642,7 @@ class ImportScripts::VBulletin < ImportScripts::Base matches = attachment_regex.match(s) attachment_id = matches[1] - unless mapping[post.id].nil? - mapping[post.id].delete(attachment_id.to_i) - end + mapping[post.id].delete(attachment_id.to_i) unless mapping[post.id].nil? upload, filename = find_upload(post, attachment_id) unless upload @@ -621,13 +656,12 @@ class ImportScripts::VBulletin < ImportScripts::Base # make resumed imports faster if new_raw == post.raw unless mapping[post.id].nil? || mapping[post.id].empty? - imported_text = mysql_query(<<-SQL + imported_text = mysql_query(<<-SQL).first["pagetext"] SELECT p.pagetext FROM #{TABLE_PREFIX}attachment a, #{TABLE_PREFIX}post p WHERE a.contentid = p.postid AND a.attachmentid = #{mapping[post.id][0]} SQL - ).first["pagetext"] imported_text.scan(attachment_regex) do |match| attachment_id = match[0] @@ -646,14 +680,17 @@ class ImportScripts::VBulletin < ImportScripts::Base # internal upload deduplication will make sure that we do not import attachments again html = html_for_upload(upload, filename) - if !new_raw[html] - new_raw += "\n\n#{html}\n\n" - end + new_raw += "\n\n#{html}\n\n" if !new_raw[html] end end if new_raw != post.raw - PostRevisor.new(post).revise!(post.user, { raw: new_raw }, bypass_bump: true, edit_reason: 'Import attachments from vBulletin') + PostRevisor.new(post).revise!( + post.user, + { raw: new_raw }, + bypass_bump: true, + edit_reason: "Import attachments from vBulletin", + ) end success_count += 1 @@ -728,22 +765,22 @@ class ImportScripts::VBulletin < ImportScripts::Base # [HTML]...[/HTML] raw.gsub!(/\[html\]/i, "\n```html\n") - raw.gsub!(/\[\/html\]/i, "\n```\n") + raw.gsub!(%r{\[/html\]}i, "\n```\n") # [PHP]...[/PHP] raw.gsub!(/\[php\]/i, "\n```php\n") - raw.gsub!(/\[\/php\]/i, "\n```\n") + raw.gsub!(%r{\[/php\]}i, "\n```\n") # [HIGHLIGHT="..."] raw.gsub!(/\[highlight="?(\w+)"?\]/i) { "\n```#{$1.downcase}\n" } # [CODE]...[/CODE] # [HIGHLIGHT]...[/HIGHLIGHT] - raw.gsub!(/\[\/?code\]/i, "\n```\n") - raw.gsub!(/\[\/?highlight\]/i, "\n```\n") + raw.gsub!(%r{\[/?code\]}i, "\n```\n") + raw.gsub!(%r{\[/?highlight\]}i, "\n```\n") # [SAMP]...[/SAMP] - raw.gsub!(/\[\/?samp\]/i, "`") + raw.gsub!(%r{\[/?samp\]}i, "`") # replace all chevrons with HTML entities # NOTE: must be done @@ -758,96 +795,99 @@ class ImportScripts::VBulletin < ImportScripts::Base raw.gsub!("\u2603", ">") # [URL=...]...[/URL] - raw.gsub!(/\[url="?([^"]+?)"?\](.*?)\[\/url\]/im) { "[#{$2.strip}](#{$1})" } - raw.gsub!(/\[url="?(.+?)"?\](.+)\[\/url\]/im) { "[#{$2.strip}](#{$1})" } + raw.gsub!(%r{\[url="?([^"]+?)"?\](.*?)\[/url\]}im) { "[#{$2.strip}](#{$1})" } + raw.gsub!(%r{\[url="?(.+?)"?\](.+)\[/url\]}im) { "[#{$2.strip}](#{$1})" } # [URL]...[/URL] # [MP3]...[/MP3] - raw.gsub!(/\[\/?url\]/i, "") - raw.gsub!(/\[\/?mp3\]/i, "") + raw.gsub!(%r{\[/?url\]}i, "") + raw.gsub!(%r{\[/?mp3\]}i, "") # [MENTION][/MENTION] - raw.gsub!(/\[mention\](.+?)\[\/mention\]/i) do + raw.gsub!(%r{\[mention\](.+?)\[/mention\]}i) do new_username = get_username_for_old_username($1) "@#{new_username}" end # [FONT=blah] and [COLOR=blah] - raw.gsub! /\[FONT=.*?\](.*?)\[\/FONT\]/im, '\1' - raw.gsub! /\[COLOR=.*?\](.*?)\[\/COLOR\]/im, '\1' - raw.gsub! /\[COLOR=#.*?\](.*?)\[\/COLOR\]/im, '\1' + raw.gsub! %r{\[FONT=.*?\](.*?)\[/FONT\]}im, '\1' + raw.gsub! %r{\[COLOR=.*?\](.*?)\[/COLOR\]}im, '\1' + raw.gsub! %r{\[COLOR=#.*?\](.*?)\[/COLOR\]}im, '\1' - raw.gsub! /\[SIZE=.*?\](.*?)\[\/SIZE\]/im, '\1' - raw.gsub! /\[SUP\](.*?)\[\/SUP\]/im, '\1' - raw.gsub! /\[h=.*?\](.*?)\[\/h\]/im, '\1' + raw.gsub! %r{\[SIZE=.*?\](.*?)\[/SIZE\]}im, '\1' + raw.gsub! %r{\[SUP\](.*?)\[/SUP\]}im, '\1' + raw.gsub! %r{\[h=.*?\](.*?)\[/h\]}im, '\1' # [CENTER]...[/CENTER] - raw.gsub! /\[CENTER\](.*?)\[\/CENTER\]/im, '\1' + raw.gsub! %r{\[CENTER\](.*?)\[/CENTER\]}im, '\1' # [INDENT]...[/INDENT] - raw.gsub! /\[INDENT\](.*?)\[\/INDENT\]/im, '\1' + raw.gsub! %r{\[INDENT\](.*?)\[/INDENT\]}im, '\1' # Tables to MD - raw.gsub!(/\[TABLE.*?\](.*?)\[\/TABLE\]/im) { |t| - rows = $1.gsub!(/\s*\[TR\](.*?)\[\/TR\]\s*/im) { |r| - cols = $1.gsub! /\s*\[TD.*?\](.*?)\[\/TD\]\s*/im, '|\1' - "#{cols}|\n" - } + raw.gsub!(%r{\[TABLE.*?\](.*?)\[/TABLE\]}im) do |t| + rows = + $1.gsub!(%r{\s*\[TR\](.*?)\[/TR\]\s*}im) do |r| + cols = $1.gsub! %r{\s*\[TD.*?\](.*?)\[/TD\]\s*}im, '|\1' + "#{cols}|\n" + end header, rest = rows.split "\n", 2 c = header.count "|" sep = "|---" * (c - 1) "#{header}\n#{sep}|\n#{rest}\n" - } + end # [QUOTE]...[/QUOTE] - raw.gsub!(/\[quote\](.+?)\[\/quote\]/im) { |quote| - quote.gsub!(/\[quote\](.+?)\[\/quote\]/im) { "\n#{$1}\n" } + raw.gsub!(%r{\[quote\](.+?)\[/quote\]}im) do |quote| + quote.gsub!(%r{\[quote\](.+?)\[/quote\]}im) { "\n#{$1}\n" } quote.gsub!(/\n(.+?)/) { "\n> #{$1}" } - } + end # [QUOTE=]...[/QUOTE] - raw.gsub!(/\[quote=([^;\]]+)\](.+?)\[\/quote\]/im) do + raw.gsub!(%r{\[quote=([^;\]]+)\](.+?)\[/quote\]}im) do old_username, quote = $1, $2 new_username = get_username_for_old_username(old_username) "\n[quote=\"#{new_username}\"]\n#{quote}\n[/quote]\n" end # [YOUTUBE][/YOUTUBE] - raw.gsub!(/\[youtube\](.+?)\[\/youtube\]/i) { "\n//youtu.be/#{$1}\n" } + raw.gsub!(%r{\[youtube\](.+?)\[/youtube\]}i) { "\n//youtu.be/#{$1}\n" } # [VIDEO=youtube;]...[/VIDEO] - raw.gsub!(/\[video=youtube;([^\]]+)\].*?\[\/video\]/i) { "\n//youtu.be/#{$1}\n" } + raw.gsub!(%r{\[video=youtube;([^\]]+)\].*?\[/video\]}i) { "\n//youtu.be/#{$1}\n" } # Fix uppercase B U and I tags - raw.gsub!(/(\[\/?[BUI]\])/i) { $1.downcase } + raw.gsub!(%r{(\[/?[BUI]\])}i) { $1.downcase } # More Additions .... # [spoiler=Some hidden stuff]SPOILER HERE!![/spoiler] - raw.gsub!(/\[spoiler="?(.+?)"?\](.+?)\[\/spoiler\]/im) { "\n#{$1}\n[spoiler]#{$2}[/spoiler]\n" } + raw.gsub!(%r{\[spoiler="?(.+?)"?\](.+?)\[/spoiler\]}im) do + "\n#{$1}\n[spoiler]#{$2}[/spoiler]\n" + end # [IMG][IMG]http://i63.tinypic.com/akga3r.jpg[/IMG][/IMG] - raw.gsub!(/\[IMG\]\[IMG\](.+?)\[\/IMG\]\[\/IMG\]/i) { "[IMG]#{$1}[/IMG]" } + raw.gsub!(%r{\[IMG\]\[IMG\](.+?)\[/IMG\]\[/IMG\]}i) { "[IMG]#{$1}[/IMG]" } # convert list tags to ul and list=1 tags to ol # (basically, we're only missing list=a here...) # (https://meta.discourse.org/t/phpbb-3-importer-old/17397) - raw.gsub!(/\[list\](.*?)\[\/list\]/im, '[ul]\1[/ul]') - raw.gsub!(/\[list=1\](.*?)\[\/list\]/im, '[ol]\1[/ol]') - raw.gsub!(/\[list\](.*?)\[\/list:u\]/im, '[ul]\1[/ul]') - raw.gsub!(/\[list=1\](.*?)\[\/list:o\]/im, '[ol]\1[/ol]') + raw.gsub!(%r{\[list\](.*?)\[/list\]}im, '[ul]\1[/ul]') + raw.gsub!(%r{\[list=1\](.*?)\[/list\]}im, '[ol]\1[/ol]') + raw.gsub!(%r{\[list\](.*?)\[/list:u\]}im, '[ul]\1[/ul]') + raw.gsub!(%r{\[list=1\](.*?)\[/list:o\]}im, '[ol]\1[/ol]') # convert *-tags to li-tags so bbcode-to-md can do its magic on phpBB's lists: - raw.gsub!(/\[\*\]\n/, '') - raw.gsub!(/\[\*\](.*?)\[\/\*:m\]/, '[li]\1[/li]') + raw.gsub!(/\[\*\]\n/, "") + raw.gsub!(%r{\[\*\](.*?)\[/\*:m\]}, '[li]\1[/li]') raw.gsub!(/\[\*\](.*?)\n/, '[li]\1[/li]') - raw.gsub!(/\[\*=1\]/, '') + raw.gsub!(/\[\*=1\]/, "") raw end def postprocess_post_raw(raw) # [QUOTE=;]...[/QUOTE] - raw.gsub!(/\[quote=([^;]+);(\d+)\](.+?)\[\/quote\]/im) do + raw.gsub!(%r{\[quote=([^;]+);(\d+)\](.+?)\[/quote\]}im) do old_username, post_id, quote = $1, $2, $3 new_username = get_username_for_old_username(old_username) @@ -859,7 +899,7 @@ class ImportScripts::VBulletin < ImportScripts::Base if topic_lookup = topic_lookup_from_imported_post_id(post_id) post_number = topic_lookup[:post_number] - topic_id = topic_lookup[:topic_id] + topic_id = topic_lookup[:topic_id] "\n[quote=\"#{new_username},post:#{post_number},topic:#{topic_id}\"]\n#{quote}\n[/quote]\n" else "\n[quote=\"#{new_username}\"]\n#{quote}\n[/quote]\n" @@ -867,11 +907,11 @@ class ImportScripts::VBulletin < ImportScripts::Base end # remove attachments - raw.gsub!(/\[attach[^\]]*\]\d+\[\/attach\]/i, "") + raw.gsub!(%r{\[attach[^\]]*\]\d+\[/attach\]}i, "") # [THREAD][/THREAD] # ==> http://my.discourse.org/t/slug/ - raw.gsub!(/\[thread\](\d+)\[\/thread\]/i) do + raw.gsub!(%r{\[thread\](\d+)\[/thread\]}i) do thread_id = $1 if topic_lookup = topic_lookup_from_imported_post_id("thread-#{thread_id}") topic_lookup[:url] @@ -882,7 +922,7 @@ class ImportScripts::VBulletin < ImportScripts::Base # [THREAD=]...[/THREAD] # ==> [...](http://my.discourse.org/t/slug/) - raw.gsub!(/\[thread=(\d+)\](.+?)\[\/thread\]/i) do + raw.gsub!(%r{\[thread=(\d+)\](.+?)\[/thread\]}i) do thread_id, link = $1, $2 if topic_lookup = topic_lookup_from_imported_post_id("thread-#{thread_id}") url = topic_lookup[:url] @@ -894,7 +934,7 @@ class ImportScripts::VBulletin < ImportScripts::Base # [POST][/POST] # ==> http://my.discourse.org/t/slug// - raw.gsub!(/\[post\](\d+)\[\/post\]/i) do + raw.gsub!(%r{\[post\](\d+)\[/post\]}i) do post_id = $1 if topic_lookup = topic_lookup_from_imported_post_id(post_id) topic_lookup[:url] @@ -905,7 +945,7 @@ class ImportScripts::VBulletin < ImportScripts::Base # [POST=]...[/POST] # ==> [...](http://my.discourse.org/t///) - raw.gsub!(/\[post=(\d+)\](.+?)\[\/post\]/i) do + raw.gsub!(%r{\[post=(\d+)\](.+?)\[/post\]}i) do post_id, link = $1, $2 if topic_lookup = topic_lookup_from_imported_post_id(post_id) url = topic_lookup[:url] @@ -919,14 +959,14 @@ class ImportScripts::VBulletin < ImportScripts::Base end def create_permalink_file - puts '', 'Creating Permalink File...', '' + puts "", "Creating Permalink File...", "" id_mapping = [] Topic.listable_topics.find_each do |topic| pcf = topic.first_post.custom_fields if pcf && pcf["import_id"] - id = pcf["import_id"].split('-').last + id = pcf["import_id"].split("-").last id_mapping.push("XXX#{id} YYY#{topic.id}") end end @@ -940,24 +980,21 @@ class ImportScripts::VBulletin < ImportScripts::Base # end CSV.open(File.expand_path("../vb_map.csv", __FILE__), "w") do |csv| - id_mapping.each do |value| - csv << [value] - end + id_mapping.each { |value| csv << [value] } end - end def suspend_users - puts '', "updating banned users" + puts "", "updating banned users" banned = 0 failed = 0 - total = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}userban").first['count'] + total = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}userban").first["count"] system_user = Discourse.system_user mysql_query("SELECT userid, bandate FROM #{TABLE_PREFIX}userban").each do |b| - user = User.find_by_id(user_id_from_imported_user_id(b['userid'])) + user = User.find_by_id(user_id_from_imported_user_id(b["userid"])) if user user.suspended_at = parse_timestamp(user["bandate"]) user.suspended_till = 200.years.from_now @@ -970,7 +1007,7 @@ class ImportScripts::VBulletin < ImportScripts::Base failed += 1 end else - puts "Not found: #{b['userid']}" + puts "Not found: #{b["userid"]}" failed += 1 end @@ -985,7 +1022,6 @@ class ImportScripts::VBulletin < ImportScripts::Base def mysql_query(sql) @client.query(sql, cache_rows: true) end - end ImportScripts::VBulletin.new.perform diff --git a/script/import_scripts/vbulletin5.rb b/script/import_scripts/vbulletin5.rb index 5e5696e4f0..af62c0a6bb 100644 --- a/script/import_scripts/vbulletin5.rb +++ b/script/import_scripts/vbulletin5.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'mysql2' +require "mysql2" require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require 'htmlentities' +require "htmlentities" class ImportScripts::VBulletin < ImportScripts::Base BATCH_SIZE = 1000 @@ -11,14 +11,14 @@ class ImportScripts::VBulletin < ImportScripts::Base # override these using environment vars - URL_PREFIX ||= ENV['URL_PREFIX'] || "forum/" - DB_PREFIX ||= ENV['DB_PREFIX'] || "vb_" - DB_HOST ||= ENV['DB_HOST'] || "localhost" - DB_NAME ||= ENV['DB_NAME'] || "vbulletin" - DB_PASS ||= ENV['DB_PASS'] || "password" - DB_USER ||= ENV['DB_USER'] || "username" - ATTACH_DIR ||= ENV['ATTACH_DIR'] || "/home/discourse/vbulletin/attach" - AVATAR_DIR ||= ENV['AVATAR_DIR'] || "/home/discourse/vbulletin/avatars" + URL_PREFIX ||= ENV["URL_PREFIX"] || "forum/" + DB_PREFIX ||= ENV["DB_PREFIX"] || "vb_" + DB_HOST ||= ENV["DB_HOST"] || "localhost" + DB_NAME ||= ENV["DB_NAME"] || "vbulletin" + DB_PASS ||= ENV["DB_PASS"] || "password" + DB_USER ||= ENV["DB_USER"] || "username" + ATTACH_DIR ||= ENV["ATTACH_DIR"] || "/home/discourse/vbulletin/attach" + AVATAR_DIR ||= ENV["AVATAR_DIR"] || "/home/discourse/vbulletin/avatars" def initialize super @@ -29,16 +29,21 @@ class ImportScripts::VBulletin < ImportScripts::Base @htmlentities = HTMLEntities.new - @client = Mysql2::Client.new( - host: DB_HOST, - username: DB_USER, - database: DB_NAME, - password: DB_PASS - ) + @client = + Mysql2::Client.new(host: DB_HOST, username: DB_USER, database: DB_NAME, password: DB_PASS) - @forum_typeid = mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Forum'").first['contenttypeid'] - @channel_typeid = mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Channel'").first['contenttypeid'] - @text_typeid = mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Text'").first['contenttypeid'] + @forum_typeid = + mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Forum'").first[ + "contenttypeid" + ] + @channel_typeid = + mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Channel'").first[ + "contenttypeid" + ] + @text_typeid = + mysql_query("SELECT contenttypeid FROM #{DB_PREFIX}contenttype WHERE class='Text'").first[ + "contenttypeid" + ] end def execute @@ -64,10 +69,7 @@ class ImportScripts::VBulletin < ImportScripts::Base SQL create_groups(groups) do |group| - { - id: group["usergroupid"], - name: @htmlentities.decode(group["title"]).strip - } + { id: group["usergroupid"], name: @htmlentities.decode(group["title"]).strip } end end @@ -102,17 +104,18 @@ class ImportScripts::VBulletin < ImportScripts::Base name: username, username: username, email: user["email"].presence || fake_email, - admin: user['admin'] == 1, + admin: user["admin"] == 1, password: user["password"], website: user["homepage"].strip, title: @htmlentities.decode(user["usertitle"]).strip, primary_group_id: group_id_from_imported_group_id(user["usergroupid"]), created_at: parse_timestamp(user["joindate"]), - post_create_action: proc do |u| - @old_username_to_new_usernames[user["username"]] = u.username - import_profile_picture(user, u) - # import_profile_background(user, u) - end + post_create_action: + proc do |u| + @old_username_to_new_usernames[user["username"]] = u.username + import_profile_picture(user, u) + # import_profile_background(user, u) + end, } end end @@ -131,18 +134,18 @@ class ImportScripts::VBulletin < ImportScripts::Base return if picture.nil? - if picture['filedata'] + if picture["filedata"] file = Tempfile.new("profile-picture") file.write(picture["filedata"].encode("ASCII-8BIT").force_encoding("UTF-8")) file.rewind upload = UploadCreator.new(file, picture["filename"]).create_for(imported_user.id) else - filename = File.join(AVATAR_DIR, picture['filename']) + filename = File.join(AVATAR_DIR, picture["filename"]) unless File.exist?(filename) puts "Avatar file doesn't exist: #{filename}" return nil end - upload = create_upload(imported_user.id, filename, picture['filename']) + upload = create_upload(imported_user.id, filename, picture["filename"]) end return if !upload.persisted? @@ -151,8 +154,16 @@ class ImportScripts::VBulletin < ImportScripts::Base imported_user.user_avatar.update(custom_upload_id: upload.id) imported_user.update(uploaded_avatar_id: upload.id) ensure - file.close rescue nil - file.unlind rescue nil + begin + file.close + rescue StandardError + nil + end + begin + file.unlind + rescue StandardError + nil + end end def import_profile_background(old_user, imported_user) @@ -178,21 +189,32 @@ class ImportScripts::VBulletin < ImportScripts::Base imported_user.user_profile.upload_profile_background(upload) ensure - file.close rescue nil - file.unlink rescue nil + begin + file.close + rescue StandardError + nil + end + begin + file.unlink + rescue StandardError + nil + end end def import_categories puts "", "importing top level categories..." - categories = mysql_query("SELECT nodeid AS forumid, title, description, displayorder, parentid + categories = + mysql_query( + "SELECT nodeid AS forumid, title, description, displayorder, parentid FROM #{DB_PREFIX}node WHERE parentid=#{ROOT_NODE} UNION SELECT nodeid, title, description, displayorder, parentid FROM #{DB_PREFIX}node WHERE contenttypeid = #{@channel_typeid} - AND parentid IN (SELECT nodeid FROM #{DB_PREFIX}node WHERE parentid=#{ROOT_NODE})").to_a + AND parentid IN (SELECT nodeid FROM #{DB_PREFIX}node WHERE parentid=#{ROOT_NODE})", + ).to_a top_level_categories = categories.select { |c| c["parentid"] == ROOT_NODE } @@ -201,7 +223,7 @@ class ImportScripts::VBulletin < ImportScripts::Base id: category["forumid"], name: @htmlentities.decode(category["title"]).strip, position: category["displayorder"], - description: @htmlentities.decode(category["description"]).strip + description: @htmlentities.decode(category["description"]).strip, } end @@ -223,7 +245,7 @@ class ImportScripts::VBulletin < ImportScripts::Base name: @htmlentities.decode(category["title"]).strip, position: category["displayorder"], description: @htmlentities.decode(category["description"]).strip, - parent_category_id: category_id_from_imported_category_id(category["parentid"]) + parent_category_id: category_id_from_imported_category_id(category["parentid"]), } end end @@ -234,13 +256,17 @@ class ImportScripts::VBulletin < ImportScripts::Base # keep track of closed topics @closed_topic_ids = [] - topic_count = mysql_query("SELECT COUNT(nodeid) cnt + topic_count = + mysql_query( + "SELECT COUNT(nodeid) cnt FROM #{DB_PREFIX}node WHERE (unpublishdate = 0 OR unpublishdate IS NULL) AND (approved = 1 AND showapproved = 1) AND parentid IN ( - SELECT nodeid FROM #{DB_PREFIX}node WHERE contenttypeid=#{@channel_typeid} ) AND contenttypeid=#{@text_typeid};" - ).first["cnt"] + SELECT nodeid FROM #{DB_PREFIX}node WHERE contenttypeid=#{@channel_typeid} ) AND contenttypeid=#{@text_typeid};", + ).first[ + "cnt" + ] batches(BATCH_SIZE) do |offset| topics = mysql_query <<-SQL @@ -265,7 +291,12 @@ class ImportScripts::VBulletin < ImportScripts::Base # next if all_records_exist? :posts, topics.map {|t| "thread-#{topic["threadid"]}" } create_posts(topics, total: topic_count, offset: offset) do |topic| - raw = preprocess_post_raw(topic["raw"]) rescue nil + raw = + begin + preprocess_post_raw(topic["raw"]) + rescue StandardError + nil + end next if raw.blank? topic_id = "thread-#{topic["threadid"]}" @closed_topic_ids << topic_id if topic["open"] == "0" @@ -291,11 +322,16 @@ class ImportScripts::VBulletin < ImportScripts::Base # make sure `firstpostid` is indexed begin mysql_query("CREATE INDEX firstpostid_index ON thread (firstpostid)") - rescue + rescue StandardError end - post_count = mysql_query("SELECT COUNT(nodeid) cnt FROM #{DB_PREFIX}node WHERE parentid NOT IN ( - SELECT nodeid FROM #{DB_PREFIX}node WHERE contenttypeid=#{@channel_typeid} ) AND contenttypeid=#{@text_typeid};").first["cnt"] + post_count = + mysql_query( + "SELECT COUNT(nodeid) cnt FROM #{DB_PREFIX}node WHERE parentid NOT IN ( + SELECT nodeid FROM #{DB_PREFIX}node WHERE contenttypeid=#{@channel_typeid} ) AND contenttypeid=#{@text_typeid};", + ).first[ + "cnt" + ] batches(BATCH_SIZE) do |offset| posts = mysql_query <<-SQL @@ -338,10 +374,14 @@ class ImportScripts::VBulletin < ImportScripts::Base end def import_attachments - puts '', 'importing attachments...' + puts "", "importing attachments..." - ext = mysql_query("SELECT GROUP_CONCAT(DISTINCT(extension)) exts FROM #{DB_PREFIX}filedata").first['exts'].split(',') - SiteSetting.authorized_extensions = (SiteSetting.authorized_extensions.split("|") + ext).uniq.join("|") + ext = + mysql_query("SELECT GROUP_CONCAT(DISTINCT(extension)) exts FROM #{DB_PREFIX}filedata").first[ + "exts" + ].split(",") + SiteSetting.authorized_extensions = + (SiteSetting.authorized_extensions.split("|") + ext).uniq.join("|") uploads = mysql_query <<-SQL SELECT n.parentid nodeid, a.filename, fd.userid, LENGTH(fd.filedata) AS dbsize, filedata, fd.filedataid @@ -354,32 +394,43 @@ class ImportScripts::VBulletin < ImportScripts::Base total_count = uploads.count uploads.each do |upload| - post_id = PostCustomField.where(name: 'import_id').where(value: upload['nodeid']).first&.post_id - post_id = PostCustomField.where(name: 'import_id').where(value: "thread-#{upload['nodeid']}").first&.post_id unless post_id + post_id = + PostCustomField.where(name: "import_id").where(value: upload["nodeid"]).first&.post_id + post_id = + PostCustomField + .where(name: "import_id") + .where(value: "thread-#{upload["nodeid"]}") + .first + &.post_id unless post_id if post_id.nil? - puts "Post for #{upload['nodeid']} not found" + puts "Post for #{upload["nodeid"]} not found" next end post = Post.find(post_id) - filename = File.join(ATTACH_DIR, upload['userid'].to_s.split('').join('/'), "#{upload['filedataid']}.attach") - real_filename = upload['filename'] - real_filename.prepend SecureRandom.hex if real_filename[0] == '.' + filename = + File.join( + ATTACH_DIR, + upload["userid"].to_s.split("").join("/"), + "#{upload["filedataid"]}.attach", + ) + real_filename = upload["filename"] + real_filename.prepend SecureRandom.hex if real_filename[0] == "." unless File.exist?(filename) # attachments can be on filesystem or in database # try to retrieve from database if the file did not exist on filesystem - if upload['dbsize'].to_i == 0 - puts "Attachment file #{upload['filedataid']} doesn't exist" + if upload["dbsize"].to_i == 0 + puts "Attachment file #{upload["filedataid"]} doesn't exist" next end - tmpfile = 'attach_' + upload['filedataid'].to_s - filename = File.join('/tmp/', tmpfile) - File.open(filename, 'wb') { |f| + tmpfile = "attach_" + upload["filedataid"].to_s + filename = File.join("/tmp/", tmpfile) + File.open(filename, "wb") do |f| #f.write(PG::Connection.unescape_bytea(row['filedata'])) - f.write(upload['filedata']) - } + f.write(upload["filedata"]) + end end upl_obj = create_upload(post.user.id, filename, real_filename) @@ -388,7 +439,9 @@ class ImportScripts::VBulletin < ImportScripts::Base if !post.raw[html] post.raw += "\n\n#{html}\n\n" post.save! - PostUpload.create!(post: post, upload: upl_obj) unless PostUpload.where(post: post, upload: upl_obj).exists? + unless PostUpload.where(post: post, upload: upl_obj).exists? + PostUpload.create!(post: post, upload: upl_obj) + end end else puts "Fail" @@ -447,170 +500,177 @@ class ImportScripts::VBulletin < ImportScripts::Base raw = @htmlentities.decode(raw) # fix whitespaces - raw = raw.gsub(/(\\r)?\\n/, "\n") - .gsub("\\t", "\t") + raw = raw.gsub(/(\\r)?\\n/, "\n").gsub("\\t", "\t") # [HTML]...[/HTML] - raw = raw.gsub(/\[html\]/i, "\n```html\n") - .gsub(/\[\/html\]/i, "\n```\n") + raw = raw.gsub(/\[html\]/i, "\n```html\n").gsub(%r{\[/html\]}i, "\n```\n") # [PHP]...[/PHP] - raw = raw.gsub(/\[php\]/i, "\n```php\n") - .gsub(/\[\/php\]/i, "\n```\n") + raw = raw.gsub(/\[php\]/i, "\n```php\n").gsub(%r{\[/php\]}i, "\n```\n") # [HIGHLIGHT="..."] raw = raw.gsub(/\[highlight="?(\w+)"?\]/i) { "\n```#{$1.downcase}\n" } # [CODE]...[/CODE] # [HIGHLIGHT]...[/HIGHLIGHT] - raw = raw.gsub(/\[\/?code\]/i, "\n```\n") - .gsub(/\[\/?highlight\]/i, "\n```\n") + raw = raw.gsub(%r{\[/?code\]}i, "\n```\n").gsub(%r{\[/?highlight\]}i, "\n```\n") # [SAMP]...[/SAMP] - raw = raw.gsub(/\[\/?samp\]/i, "`") + raw = raw.gsub(%r{\[/?samp\]}i, "`") # replace all chevrons with HTML entities # NOTE: must be done # - AFTER all the "code" processing # - BEFORE the "quote" processing - raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub("<", "\u2603") + "`" } - .gsub("<", "<") - .gsub("\u2603", "<") + raw = + raw + .gsub(/`([^`]+)`/im) { "`" + $1.gsub("<", "\u2603") + "`" } + .gsub("<", "<") + .gsub("\u2603", "<") - raw = raw.gsub(/`([^`]+)`/im) { "`" + $1.gsub(">", "\u2603") + "`" } - .gsub(">", ">") - .gsub("\u2603", ">") + raw = + raw + .gsub(/`([^`]+)`/im) { "`" + $1.gsub(">", "\u2603") + "`" } + .gsub(">", ">") + .gsub("\u2603", ">") # [URL=...]...[/URL] - raw.gsub!(/\[url="?(.+?)"?\](.+?)\[\/url\]/i) { "#{$2}" } + raw.gsub!(%r{\[url="?(.+?)"?\](.+?)\[/url\]}i) { "#{$2}" } # [URL]...[/URL] # [MP3]...[/MP3] - raw = raw.gsub(/\[\/?url\]/i, "") - .gsub(/\[\/?mp3\]/i, "") + raw = raw.gsub(%r{\[/?url\]}i, "").gsub(%r{\[/?mp3\]}i, "") # [MENTION][/MENTION] - raw = raw.gsub(/\[mention\](.+?)\[\/mention\]/i) do - old_username = $1 - if @old_username_to_new_usernames.has_key?(old_username) - old_username = @old_username_to_new_usernames[old_username] + raw = + raw.gsub(%r{\[mention\](.+?)\[/mention\]}i) do + old_username = $1 + if @old_username_to_new_usernames.has_key?(old_username) + old_username = @old_username_to_new_usernames[old_username] + end + "@#{old_username}" end - "@#{old_username}" - end # [USER=][/USER] - raw = raw.gsub(/\[user="?(\d+)"?\](.+?)\[\/user\]/i) do - user_id, old_username = $1, $2 - if @old_username_to_new_usernames.has_key?(old_username) - new_username = @old_username_to_new_usernames[old_username] - else - new_username = old_username + raw = + raw.gsub(%r{\[user="?(\d+)"?\](.+?)\[/user\]}i) do + user_id, old_username = $1, $2 + if @old_username_to_new_usernames.has_key?(old_username) + new_username = @old_username_to_new_usernames[old_username] + else + new_username = old_username + end + "@#{new_username}" end - "@#{new_username}" - end # [FONT=blah] and [COLOR=blah] # no idea why the /i is not matching case insensitive.. - raw.gsub! /\[color=.*?\](.*?)\[\/color\]/im, '\1' - raw.gsub! /\[COLOR=.*?\](.*?)\[\/COLOR\]/im, '\1' - raw.gsub! /\[font=.*?\](.*?)\[\/font\]/im, '\1' - raw.gsub! /\[FONT=.*?\](.*?)\[\/FONT\]/im, '\1' + raw.gsub! %r{\[color=.*?\](.*?)\[/color\]}im, '\1' + raw.gsub! %r{\[COLOR=.*?\](.*?)\[/COLOR\]}im, '\1' + raw.gsub! %r{\[font=.*?\](.*?)\[/font\]}im, '\1' + raw.gsub! %r{\[FONT=.*?\](.*?)\[/FONT\]}im, '\1' # [CENTER]...[/CENTER] - raw.gsub! /\[CENTER\](.*?)\[\/CENTER\]/im, '\1' + raw.gsub! %r{\[CENTER\](.*?)\[/CENTER\]}im, '\1' # fix LIST - raw.gsub! /\[LIST\](.*?)\[\/LIST\]/im, '
      \1
    ' - raw.gsub! /\[\*\]/im, '
  • ' + raw.gsub! %r{\[LIST\](.*?)\[/LIST\]}im, '
      \1
    ' + raw.gsub! /\[\*\]/im, "
  • " # [QUOTE]...[/QUOTE] - raw = raw.gsub(/\[quote\](.+?)\[\/quote\]/im) { "\n> #{$1}\n" } + raw = raw.gsub(%r{\[quote\](.+?)\[/quote\]}im) { "\n> #{$1}\n" } # [QUOTE=]...[/QUOTE] - raw = raw.gsub(/\[quote=([^;\]]+)\](.+?)\[\/quote\]/im) do - old_username, quote = $1, $2 + raw = + raw.gsub(%r{\[quote=([^;\]]+)\](.+?)\[/quote\]}im) do + old_username, quote = $1, $2 - if @old_username_to_new_usernames.has_key?(old_username) - old_username = @old_username_to_new_usernames[old_username] + if @old_username_to_new_usernames.has_key?(old_username) + old_username = @old_username_to_new_usernames[old_username] + end + "\n[quote=\"#{old_username}\"]\n#{quote}\n[/quote]\n" end - "\n[quote=\"#{old_username}\"]\n#{quote}\n[/quote]\n" - end # [YOUTUBE][/YOUTUBE] - raw = raw.gsub(/\[youtube\](.+?)\[\/youtube\]/i) { "\n//youtu.be/#{$1}\n" } + raw = raw.gsub(%r{\[youtube\](.+?)\[/youtube\]}i) { "\n//youtu.be/#{$1}\n" } # [VIDEO=youtube;]...[/VIDEO] - raw = raw.gsub(/\[video=youtube;([^\]]+)\].*?\[\/video\]/i) { "\n//youtu.be/#{$1}\n" } + raw = raw.gsub(%r{\[video=youtube;([^\]]+)\].*?\[/video\]}i) { "\n//youtu.be/#{$1}\n" } raw end def postprocess_post_raw(raw) # [QUOTE=;]...[/QUOTE] - raw = raw.gsub(/\[quote=([^;]+);n(\d+)\](.+?)\[\/quote\]/im) do - old_username, post_id, quote = $1, $2, $3 + raw = + raw.gsub(%r{\[quote=([^;]+);n(\d+)\](.+?)\[/quote\]}im) do + old_username, post_id, quote = $1, $2, $3 - if @old_username_to_new_usernames.has_key?(old_username) - old_username = @old_username_to_new_usernames[old_username] - end + if @old_username_to_new_usernames.has_key?(old_username) + old_username = @old_username_to_new_usernames[old_username] + end - if topic_lookup = topic_lookup_from_imported_post_id(post_id) - post_number = topic_lookup[:post_number] - topic_id = topic_lookup[:topic_id] - "\n[quote=\"#{old_username},post:#{post_number},topic:#{topic_id}\"]\n#{quote}\n[/quote]\n" - else - "\n[quote=\"#{old_username}\"]\n#{quote}\n[/quote]\n" + if topic_lookup = topic_lookup_from_imported_post_id(post_id) + post_number = topic_lookup[:post_number] + topic_id = topic_lookup[:topic_id] + "\n[quote=\"#{old_username},post:#{post_number},topic:#{topic_id}\"]\n#{quote}\n[/quote]\n" + else + "\n[quote=\"#{old_username}\"]\n#{quote}\n[/quote]\n" + end end - end # remove attachments - raw = raw.gsub(/\[attach[^\]]*\]\d+\[\/attach\]/i, "") + raw = raw.gsub(%r{\[attach[^\]]*\]\d+\[/attach\]}i, "") # [THREAD][/THREAD] # ==> http://my.discourse.org/t/slug/ - raw = raw.gsub(/\[thread\](\d+)\[\/thread\]/i) do - thread_id = $1 - if topic_lookup = topic_lookup_from_imported_post_id("thread-#{thread_id}") - topic_lookup[:url] - else - $& + raw = + raw.gsub(%r{\[thread\](\d+)\[/thread\]}i) do + thread_id = $1 + if topic_lookup = topic_lookup_from_imported_post_id("thread-#{thread_id}") + topic_lookup[:url] + else + $& + end end - end # [THREAD=]...[/THREAD] # ==> [...](http://my.discourse.org/t/slug/) - raw = raw.gsub(/\[thread=(\d+)\](.+?)\[\/thread\]/i) do - thread_id, link = $1, $2 - if topic_lookup = topic_lookup_from_imported_post_id("thread-#{thread_id}") - url = topic_lookup[:url] - "[#{link}](#{url})" - else - $& + raw = + raw.gsub(%r{\[thread=(\d+)\](.+?)\[/thread\]}i) do + thread_id, link = $1, $2 + if topic_lookup = topic_lookup_from_imported_post_id("thread-#{thread_id}") + url = topic_lookup[:url] + "[#{link}](#{url})" + else + $& + end end - end # [POST][/POST] # ==> http://my.discourse.org/t/slug// - raw = raw.gsub(/\[post\](\d+)\[\/post\]/i) do - post_id = $1 - if topic_lookup = topic_lookup_from_imported_post_id(post_id) - topic_lookup[:url] - else - $& + raw = + raw.gsub(%r{\[post\](\d+)\[/post\]}i) do + post_id = $1 + if topic_lookup = topic_lookup_from_imported_post_id(post_id) + topic_lookup[:url] + else + $& + end end - end # [POST=]...[/POST] # ==> [...](http://my.discourse.org/t///) - raw = raw.gsub(/\[post=(\d+)\](.+?)\[\/post\]/i) do - post_id, link = $1, $2 - if topic_lookup = topic_lookup_from_imported_post_id(post_id) - url = topic_lookup[:url] - "[#{link}](#{url})" - else - $& + raw = + raw.gsub(%r{\[post=(\d+)\](.+?)\[/post\]}i) do + post_id, link = $1, $2 + if topic_lookup = topic_lookup_from_imported_post_id(post_id) + url = topic_lookup[:url] + "[#{link}](#{url})" + else + $& + end end - end raw end @@ -619,13 +679,17 @@ class ImportScripts::VBulletin < ImportScripts::Base puts "", "creating permalinks..." current_count = 0 - total_count = mysql_query("SELECT COUNT(nodeid) cnt + total_count = + mysql_query( + "SELECT COUNT(nodeid) cnt FROM #{DB_PREFIX}node WHERE (unpublishdate = 0 OR unpublishdate IS NULL) AND (approved = 1 AND showapproved = 1) AND parentid IN ( - SELECT nodeid FROM #{DB_PREFIX}node WHERE contenttypeid=#{@channel_typeid} ) AND contenttypeid=#{@text_typeid};" - ).first["cnt"] + SELECT nodeid FROM #{DB_PREFIX}node WHERE contenttypeid=#{@channel_typeid} ) AND contenttypeid=#{@text_typeid};", + ).first[ + "cnt" + ] batches(BATCH_SIZE) do |offset| topics = mysql_query <<-SQL @@ -647,12 +711,16 @@ class ImportScripts::VBulletin < ImportScripts::Base topics.each do |topic| current_count += 1 print_status current_count, total_count - disc_topic = topic_lookup_from_imported_post_id("thread-#{topic['nodeid']}") + disc_topic = topic_lookup_from_imported_post_id("thread-#{topic["nodeid"]}") - Permalink.create( - url: "#{URL_PREFIX}#{topic['p1']}/#{topic['p2']}/#{topic['nodeid']}-#{topic['p3']}", - topic_id: disc_topic[:topic_id] - ) rescue nil + begin + Permalink.create( + url: "#{URL_PREFIX}#{topic["p1"]}/#{topic["p2"]}/#{topic["nodeid"]}-#{topic["p3"]}", + topic_id: disc_topic[:topic_id], + ) + rescue StandardError + nil + end end end @@ -664,8 +732,13 @@ class ImportScripts::VBulletin < ImportScripts::Base AND parentid=#{ROOT_NODE}; SQL cats.each do |c| - category_id = CategoryCustomField.where(name: 'import_id').where(value: c['nodeid']).first.category_id - Permalink.create(url: "#{URL_PREFIX}#{c['urlident']}", category_id: category_id) rescue nil + category_id = + CategoryCustomField.where(name: "import_id").where(value: c["nodeid"]).first.category_id + begin + Permalink.create(url: "#{URL_PREFIX}#{c["urlident"]}", category_id: category_id) + rescue StandardError + nil + end end # subcats @@ -677,8 +750,13 @@ class ImportScripts::VBulletin < ImportScripts::Base AND n1.contenttypeid=#{@channel_typeid}; SQL subcats.each do |sc| - category_id = CategoryCustomField.where(name: 'import_id').where(value: sc['nodeid']).first.category_id - Permalink.create(url: "#{URL_PREFIX}#{sc['p1']}/#{sc['p2']}", category_id: category_id) rescue nil + category_id = + CategoryCustomField.where(name: "import_id").where(value: sc["nodeid"]).first.category_id + begin + Permalink.create(url: "#{URL_PREFIX}#{sc["p1"]}/#{sc["p2"]}", category_id: category_id) + rescue StandardError + nil + end end end @@ -689,7 +767,7 @@ class ImportScripts::VBulletin < ImportScripts::Base SiteSetting.max_tags_per_topic = 100 staff_guardian = Guardian.new(Discourse.system_user) - records = mysql_query(<<~SQL + records = mysql_query(<<~SQL).to_a SELECT nodeid, GROUP_CONCAT(tagtext) tags FROM #{DB_PREFIX}tag t LEFT JOIN #{DB_PREFIX}tagnode tn ON tn.tagid = t.tagid @@ -697,7 +775,6 @@ class ImportScripts::VBulletin < ImportScripts::Base AND tn.nodeid IS NOT NULL GROUP BY nodeid SQL - ).to_a current_count = 0 total_count = records.count @@ -705,11 +782,11 @@ class ImportScripts::VBulletin < ImportScripts::Base records.each do |rec| current_count += 1 print_status current_count, total_count - tl = topic_lookup_from_imported_post_id("thread-#{rec['nodeid']}") - next if tl.nil? # topic might have been deleted + tl = topic_lookup_from_imported_post_id("thread-#{rec["nodeid"]}") + next if tl.nil? # topic might have been deleted topic = Topic.find(tl[:topic_id]) - tag_names = rec['tags'].force_encoding("UTF-8").split(',') + tag_names = rec["tags"].force_encoding("UTF-8").split(",") DiscourseTagging.tag_topic_by_names(topic, staff_guardian, tag_names) end end diff --git a/script/import_scripts/xenforo.rb b/script/import_scripts/xenforo.rb index 38e45342c6..1c9aaaeb77 100755 --- a/script/import_scripts/xenforo.rb +++ b/script/import_scripts/xenforo.rb @@ -3,11 +3,11 @@ require "mysql2" begin - require 'php_serialize' # https://github.com/jqr/php-serialize + require "php_serialize" # https://github.com/jqr/php-serialize rescue LoadError puts - puts 'php_serialize not found.' - puts 'Add to Gemfile, like this: ' + puts "php_serialize not found." + puts "Add to Gemfile, like this: " puts puts "echo gem \\'php-serialize\\' >> Gemfile" puts "bundle install" @@ -19,20 +19,20 @@ require File.expand_path(File.dirname(__FILE__) + "/base.rb") # Call it like this: # RAILS_ENV=production bundle exec ruby script/import_scripts/xenforo.rb class ImportScripts::XenForo < ImportScripts::Base - XENFORO_DB = "xenforo_db" TABLE_PREFIX = "xf_" BATCH_SIZE = 1000 - ATTACHMENT_DIR = '/tmp/attachments' + ATTACHMENT_DIR = "/tmp/attachments" def initialize super - @client = Mysql2::Client.new( - host: "localhost", - username: "root", - password: "pa$$word", - database: XENFORO_DB - ) + @client = + Mysql2::Client.new( + host: "localhost", + username: "root", + password: "pa$$word", + database: XENFORO_DB, + ) @category_mappings = {} @prefix_as_category = false @@ -47,10 +47,8 @@ class ImportScripts::XenForo < ImportScripts::Base end def import_avatar(id, imported_user) - filename = File.join(AVATAR_DIR, 'l', (id / 1000).to_s, "#{id}.jpg") - unless File.exist?(filename) - return nil - end + filename = File.join(AVATAR_DIR, "l", (id / 1000).to_s, "#{id}.jpg") + return nil unless File.exist?(filename) upload = create_upload(imported_user.id, filename, "avatar_#{id}") return if !upload.persisted? imported_user.create_user_avatar @@ -59,36 +57,42 @@ class ImportScripts::XenForo < ImportScripts::Base end def import_users - puts '', "creating users" + puts "", "creating users" - total_count = mysql_query("SELECT count(*) count FROM #{TABLE_PREFIX}user WHERE user_state = 'valid' AND is_banned = 0;").first['count'] + total_count = + mysql_query( + "SELECT count(*) count FROM #{TABLE_PREFIX}user WHERE user_state = 'valid' AND is_banned = 0;", + ).first[ + "count" + ] batches(BATCH_SIZE) do |offset| - results = mysql_query( - "SELECT user_id id, username, email, custom_title title, register_date created_at, + results = + mysql_query( + "SELECT user_id id, username, email, custom_title title, register_date created_at, last_activity last_visit_time, user_group_id, is_moderator, is_admin, is_staff FROM #{TABLE_PREFIX}user WHERE user_state = 'valid' AND is_banned = 0 LIMIT #{BATCH_SIZE} - OFFSET #{offset};") + OFFSET #{offset};", + ) break if results.size < 1 next if all_records_exist? :users, results.map { |u| u["id"].to_i } create_users(results, total: total_count, offset: offset) do |user| - next if user['username'].blank? - { id: user['id'], - email: user['email'], - username: user['username'], - title: user['title'], - created_at: Time.zone.at(user['created_at']), - last_seen_at: Time.zone.at(user['last_visit_time']), - moderator: user['is_moderator'] == 1 || user['is_staff'] == 1, - admin: user['is_admin'] == 1, - post_create_action: proc do |u| - import_avatar(user['id'], u) - end + next if user["username"].blank? + { + id: user["id"], + email: user["email"], + username: user["username"], + title: user["title"], + created_at: Time.zone.at(user["created_at"]), + last_seen_at: Time.zone.at(user["last_visit_time"]), + moderator: user["is_moderator"] == 1 || user["is_staff"] == 1, + admin: user["is_admin"] == 1, + post_create_action: proc { |u| import_avatar(user["id"], u) }, } end end @@ -97,7 +101,9 @@ class ImportScripts::XenForo < ImportScripts::Base def import_categories puts "", "importing categories..." - categories = mysql_query(" + categories = + mysql_query( + " SELECT node_id id, title, description, @@ -106,20 +112,22 @@ class ImportScripts::XenForo < ImportScripts::Base display_order FROM #{TABLE_PREFIX}node ORDER BY parent_node_id, display_order - ").to_a + ", + ).to_a top_level_categories = categories.select { |c| c["parent_node_id"] == 0 } create_categories(top_level_categories) do |c| { - id: c['id'], - name: c['title'], - description: c['description'], - position: c['display_order'], - post_create_action: proc do |category| - url = "board/#{c['node_name']}" - Permalink.find_or_create_by(url: url, category_id: category.id) - end + id: c["id"], + name: c["title"], + description: c["description"], + position: c["display_order"], + post_create_action: + proc do |category| + url = "board/#{c["node_name"]}" + Permalink.find_or_create_by(url: url, category_id: category.id) + end, } end @@ -129,40 +137,41 @@ class ImportScripts::XenForo < ImportScripts::Base create_categories(subcategories) do |c| { - id: c['id'], - name: c['title'], - description: c['description'], - position: c['display_order'], - parent_category_id: category_id_from_imported_category_id(c['parent_node_id']), - post_create_action: proc do |category| - url = "board/#{c['node_name']}" - Permalink.find_or_create_by(url: url, category_id: category.id) - end + id: c["id"], + name: c["title"], + description: c["description"], + position: c["display_order"], + parent_category_id: category_id_from_imported_category_id(c["parent_node_id"]), + post_create_action: + proc do |category| + url = "board/#{c["node_name"]}" + Permalink.find_or_create_by(url: url, category_id: category.id) + end, } end - subcategory_ids = Set.new(subcategories.map { |c| c['id'] }) + subcategory_ids = Set.new(subcategories.map { |c| c["id"] }) # deeper categories need to be tags categories.each do |c| - next if c['parent_node_id'] == 0 - next if top_level_category_ids.include?(c['id']) - next if subcategory_ids.include?(c['id']) + next if c["parent_node_id"] == 0 + next if top_level_category_ids.include?(c["id"]) + next if subcategory_ids.include?(c["id"]) # Find a subcategory for topics in this category parent = c - while !parent.nil? && !subcategory_ids.include?(parent['id']) - parent = categories.find { |subcat| subcat['id'] == parent['parent_node_id'] } + while !parent.nil? && !subcategory_ids.include?(parent["id"]) + parent = categories.find { |subcat| subcat["id"] == parent["parent_node_id"] } end if parent - tag_name = DiscourseTagging.clean_tag(c['title']) - @category_mappings[c['id']] = { - category_id: category_id_from_imported_category_id(parent['id']), - tag: Tag.find_by_name(tag_name) || Tag.create(name: tag_name) + tag_name = DiscourseTagging.clean_tag(c["title"]) + @category_mappings[c["id"]] = { + category_id: category_id_from_imported_category_id(parent["id"]), + tag: Tag.find_by_name(tag_name) || Tag.create(name: tag_name), } else - puts '', "Couldn't find a category for #{c['id']} '#{c['title']}'!" + puts "", "Couldn't find a category for #{c["id"]} '#{c["title"]}'!" end end end @@ -172,40 +181,46 @@ class ImportScripts::XenForo < ImportScripts::Base def import_categories_from_thread_prefixes puts "", "importing categories..." - categories = mysql_query(" + categories = + mysql_query( + " SELECT prefix_id id FROM #{TABLE_PREFIX}thread_prefix ORDER BY prefix_id ASC - ").to_a + ", + ).to_a create_categories(categories) do |category| - { - id: category["id"], - name: "Category-#{category["id"]}" - } + { id: category["id"], name: "Category-#{category["id"]}" } end @prefix_as_category = true end def import_likes - puts '', 'importing likes' - total_count = mysql_query("SELECT COUNT(*) AS count FROM #{TABLE_PREFIX}liked_content WHERE content_type = 'post'").first["count"] + puts "", "importing likes" + total_count = + mysql_query( + "SELECT COUNT(*) AS count FROM #{TABLE_PREFIX}liked_content WHERE content_type = 'post'", + ).first[ + "count" + ] batches(BATCH_SIZE) do |offset| - results = mysql_query( - "SELECT like_id, content_id, like_user_id, like_date + results = + mysql_query( + "SELECT like_id, content_id, like_user_id, like_date FROM #{TABLE_PREFIX}liked_content WHERE content_type = 'post' ORDER BY like_id LIMIT #{BATCH_SIZE} - OFFSET #{offset};" - ) + OFFSET #{offset};", + ) break if results.size < 1 create_likes(results, total: total_count, offset: offset) do |row| { - post_id: row['content_id'], - user_id: row['like_user_id'], - created_at: Time.zone.at(row['like_date']) + post_id: row["content_id"], + user_id: row["like_user_id"], + created_at: Time.zone.at(row["like_date"]), } end end @@ -216,10 +231,11 @@ class ImportScripts::XenForo < ImportScripts::Base total_count = mysql_query("SELECT count(*) count from #{TABLE_PREFIX}post").first["count"] - posts_sql = " + posts_sql = + " SELECT p.post_id id, t.thread_id topic_id, - #{@prefix_as_category ? 't.prefix_id' : 't.node_id'} category_id, + #{@prefix_as_category ? "t.prefix_id" : "t.node_id"} category_id, t.title title, t.first_post_id first_post_id, t.view_count, @@ -238,35 +254,35 @@ class ImportScripts::XenForo < ImportScripts::Base results = mysql_query("#{posts_sql} OFFSET #{offset};").to_a break if results.size < 1 - next if all_records_exist? :posts, results.map { |p| p['id'] } + next if all_records_exist? :posts, results.map { |p| p["id"] } create_posts(results, total: total_count, offset: offset) do |m| skip = false mapped = {} - mapped[:id] = m['id'] - mapped[:user_id] = user_id_from_imported_user_id(m['user_id']) || -1 - mapped[:raw] = process_xenforo_post(m['raw'], m['id']) - mapped[:created_at] = Time.zone.at(m['created_at']) + mapped[:id] = m["id"] + mapped[:user_id] = user_id_from_imported_user_id(m["user_id"]) || -1 + mapped[:raw] = process_xenforo_post(m["raw"], m["id"]) + mapped[:created_at] = Time.zone.at(m["created_at"]) - if m['id'] == m['first_post_id'] - if m['category_id'].to_i == 0 || m['category_id'].nil? + if m["id"] == m["first_post_id"] + if m["category_id"].to_i == 0 || m["category_id"].nil? mapped[:category] = SiteSetting.uncategorized_category_id else - mapped[:category] = category_id_from_imported_category_id(m['category_id'].to_i) || - @category_mappings[m['category_id']].try(:[], :category_id) + mapped[:category] = category_id_from_imported_category_id(m["category_id"].to_i) || + @category_mappings[m["category_id"]].try(:[], :category_id) end - mapped[:title] = CGI.unescapeHTML(m['title']) - mapped[:views] = m['view_count'] + mapped[:title] = CGI.unescapeHTML(m["title"]) + mapped[:views] = m["view_count"] mapped[:post_create_action] = proc do |pp| - Permalink.find_or_create_by(url: "threads/#{m['topic_id']}", topic_id: pp.topic_id) + Permalink.find_or_create_by(url: "threads/#{m["topic_id"]}", topic_id: pp.topic_id) end else - parent = topic_lookup_from_imported_post_id(m['first_post_id']) + parent = topic_lookup_from_imported_post_id(m["first_post_id"]) if parent mapped[:topic_id] = parent[:topic_id] else - puts "Parent post #{m['first_post_id']} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" + puts "Parent post #{m["first_post_id"]} doesn't exist. Skipping #{m["id"]}: #{m["title"][0..40]}" skip = true end end @@ -281,16 +297,15 @@ class ImportScripts::XenForo < ImportScripts::Base break if results.size < 1 results.each do |m| - next unless m['id'] == m['first_post_id'] && m['category_id'].to_i > 0 - next unless tag = @category_mappings[m['category_id']].try(:[], :tag) - next unless topic_mapping = topic_lookup_from_imported_post_id(m['id']) + next unless m["id"] == m["first_post_id"] && m["category_id"].to_i > 0 + next unless tag = @category_mappings[m["category_id"]].try(:[], :tag) + next unless topic_mapping = topic_lookup_from_imported_post_id(m["id"]) topic = Topic.find_by_id(topic_mapping[:topic_id]) topic.tags = [tag] if topic end end - end def import_private_messages @@ -318,29 +333,29 @@ class ImportScripts::XenForo < ImportScripts::Base user_id: user_id, raw: raw, created_at: Time.zone.at(post["message_date"].to_i), - import_mode: true + import_mode: true, } unless post["topic_id"] > 0 msg[:title] = post["title"] msg[:archetype] = Archetype.private_message - to_user_array = PHP.unserialize(post['recipients']) + to_user_array = PHP.unserialize(post["recipients"]) if to_user_array.size > 0 discourse_user_ids = to_user_array.keys.map { |id| user_id_from_imported_user_id(id) } usernames = User.where(id: [discourse_user_ids]).pluck(:username) - msg[:target_usernames] = usernames.join(',') + msg[:target_usernames] = usernames.join(",") end else topic_id = post["topic_id"] if t = topic_lookup_from_imported_post_id("pm_#{topic_id}") msg[:topic_id] = t[:topic_id] else - puts "Topic ID #{topic_id} not found, skipping post #{post['message_id']} from #{post['user_id']}" + puts "Topic ID #{topic_id} not found, skipping post #{post["message_id"]} from #{post["user_id"]}" next end end msg else - puts "Empty message, skipping post #{post['message_id']}" + puts "Empty message, skipping post #{post["message_id"]}" next end end @@ -351,18 +366,18 @@ class ImportScripts::XenForo < ImportScripts::Base s = raw.dup # :) is encoded as :) - s.gsub!(/]+) \/>/, '\1') + s.gsub!(%r{]+) />}, '\1') # Some links look like this: http://www.onegameamonth.com - s.gsub!(/(.+)<\/a>/, '[\2](\1)') + s.gsub!(%r{(.+)}, '[\2](\1)') # Many phpbb bbcode tags have a hash attached to them. Examples: # [url=https://google.com:1qh1i7ky]click here[/url:1qh1i7ky] # [quote="cybereality":b0wtlzex]Some text.[/quote:b0wtlzex] - s.gsub!(/:(?:\w{8})\]/, ']') + s.gsub!(/:(?:\w{8})\]/, "]") # Remove mybb video tags. - s.gsub!(/(^\[video=.*?\])|(\[\/video\]$)/, '') + s.gsub!(%r{(^\[video=.*?\])|(\[/video\]$)}, "") s = CGI.unescapeHTML(s) @@ -370,18 +385,16 @@ class ImportScripts::XenForo < ImportScripts::Base # [http://answers.yahoo.com/question/index ... 223AAkkPli](http://answers.yahoo.com/question/index?qid=20070920134223AAkkPli) # #Fix for the error: xenforo.rb: 160: in `gsub!': invalid byte sequence in UTF-8 (ArgumentError) - if ! s.valid_encoding? - s = s.encode("UTF-16be", invalid: :replace, replace: "?").encode('UTF-8') - end + s = s.encode("UTF-16be", invalid: :replace, replace: "?").encode("UTF-8") if !s.valid_encoding? # Work around it for now: - s.gsub!(/\[http(s)?:\/\/(www\.)?/, '[') + s.gsub!(%r{\[http(s)?://(www\.)?}, "[") # [QUOTE]...[/QUOTE] - s.gsub!(/\[quote\](.+?)\[\/quote\]/im) { "\n> #{$1}\n" } + s.gsub!(%r{\[quote\](.+?)\[/quote\]}im) { "\n> #{$1}\n" } # Nested Quotes - s.gsub!(/(\[\/?QUOTE.*?\])/mi) { |q| "\n#{q}\n" } + s.gsub!(%r{(\[/?QUOTE.*?\])}mi) { |q| "\n#{q}\n" } # [QUOTE="username, post: 28662, member: 1283"] s.gsub!(/\[quote="(\w+), post: (\d*), member: (\d*)"\]/i) do @@ -397,48 +410,52 @@ class ImportScripts::XenForo < ImportScripts::Base end # [URL=...]...[/URL] - s.gsub!(/\[url="?(.+?)"?\](.+?)\[\/url\]/i) { "[#{$2}](#{$1})" } + s.gsub!(%r{\[url="?(.+?)"?\](.+?)\[/url\]}i) { "[#{$2}](#{$1})" } # [URL]...[/URL] - s.gsub!(/\[url\](.+?)\[\/url\]/i) { " #{$1} " } + s.gsub!(%r{\[url\](.+?)\[/url\]}i) { " #{$1} " } # [IMG]...[/IMG] - s.gsub!(/\[\/?img\]/i, "") + s.gsub!(%r{\[/?img\]}i, "") # convert list tags to ul and list=1 tags to ol # (basically, we're only missing list=a here...) - s.gsub!(/\[list\](.*?)\[\/list\]/im, '[ul]\1[/ul]') - s.gsub!(/\[list=1\](.*?)\[\/list\]/im, '[ol]\1[/ol]') - s.gsub!(/\[list\](.*?)\[\/list:u\]/im, '[ul]\1[/ul]') - s.gsub!(/\[list=1\](.*?)\[\/list:o\]/im, '[ol]\1[/ol]') + s.gsub!(%r{\[list\](.*?)\[/list\]}im, '[ul]\1[/ul]') + s.gsub!(%r{\[list=1\](.*?)\[/list\]}im, '[ol]\1[/ol]') + s.gsub!(%r{\[list\](.*?)\[/list:u\]}im, '[ul]\1[/ul]') + s.gsub!(%r{\[list=1\](.*?)\[/list:o\]}im, '[ol]\1[/ol]') # convert *-tags to li-tags so bbcode-to-md can do its magic on phpBB's lists: - s.gsub!(/\[\*\]\n/, '') - s.gsub!(/\[\*\](.*?)\[\/\*:m\]/, '[li]\1[/li]') + s.gsub!(/\[\*\]\n/, "") + s.gsub!(%r{\[\*\](.*?)\[/\*:m\]}, '[li]\1[/li]') s.gsub!(/\[\*\](.*?)\n/, '[li]\1[/li]') - s.gsub!(/\[\*=1\]/, '') + s.gsub!(/\[\*=1\]/, "") # [YOUTUBE][/YOUTUBE] - s.gsub!(/\[youtube\](.+?)\[\/youtube\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } + s.gsub!(%r{\[youtube\](.+?)\[/youtube\]}i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } # [youtube=425,350]id[/youtube] - s.gsub!(/\[youtube="?(.+?)"?\](.+?)\[\/youtube\]/i) { "\nhttps://www.youtube.com/watch?v=#{$2}\n" } + s.gsub!(%r{\[youtube="?(.+?)"?\](.+?)\[/youtube\]}i) do + "\nhttps://www.youtube.com/watch?v=#{$2}\n" + end # [MEDIA=youtube]id[/MEDIA] - s.gsub!(/\[MEDIA=youtube\](.+?)\[\/MEDIA\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } + s.gsub!(%r{\[MEDIA=youtube\](.+?)\[/MEDIA\]}i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } # [ame="youtube_link"]title[/ame] - s.gsub!(/\[ame="?(.+?)"?\](.+?)\[\/ame\]/i) { "\n#{$1}\n" } + s.gsub!(%r{\[ame="?(.+?)"?\](.+?)\[/ame\]}i) { "\n#{$1}\n" } # [VIDEO=youtube;]...[/VIDEO] - s.gsub!(/\[video=youtube;([^\]]+)\].*?\[\/video\]/i) { "\nhttps://www.youtube.com/watch?v=#{$1}\n" } + s.gsub!(%r{\[video=youtube;([^\]]+)\].*?\[/video\]}i) do + "\nhttps://www.youtube.com/watch?v=#{$1}\n" + end # [USER=706]@username[/USER] - s.gsub!(/\[user="?(.+?)"?\](.+?)\[\/user\]/i) { $2 } + s.gsub!(%r{\[user="?(.+?)"?\](.+?)\[/user\]}i) { $2 } # Remove the color tag s.gsub!(/\[color=[#a-z0-9]+\]/i, "") - s.gsub!(/\[\/color\]/i, "") + s.gsub!(%r{\[/color\]}i, "") if Dir.exist? ATTACHMENT_DIR s = process_xf_attachments(:gallery, s, import_id) @@ -454,7 +471,8 @@ class ImportScripts::XenForo < ImportScripts::Base # not all attachments have an [ATTACH=] tag so we need to get the other ID's from the xf_attachment table if xf_type == :attachment && import_id > 0 - sql = "SELECT attachment_id FROM #{TABLE_PREFIX}attachment WHERE content_id=#{import_id} and content_type='post';" + sql = + "SELECT attachment_id FROM #{TABLE_PREFIX}attachment WHERE content_id=#{import_id} and content_type='post';" ids.merge(mysql_query(sql).to_a.map { |v| v["attachment_id"].to_i }) end @@ -464,18 +482,22 @@ class ImportScripts::XenForo < ImportScripts::Base results = mysql_query(sql) if results.size < 1 # Strip attachment - s.gsub!(get_xf_regexp(xf_type, id), '') + s.gsub!(get_xf_regexp(xf_type, id), "") STDERR.puts "#{xf_type.capitalize} id #{id} not found in source database. Stripping." next end - original_filename = results.first['filename'] + original_filename = results.first["filename"] result = results.first - upload = import_xf_attachment(result['data_id'], result['file_hash'], result['user_id'], original_filename) + upload = + import_xf_attachment( + result["data_id"], + result["file_hash"], + result["user_id"], + original_filename, + ) if upload && upload.present? && upload.persisted? html = @uploader.html_for_upload(upload, original_filename) - unless s.gsub!(get_xf_regexp(xf_type, id), html) - s = s + "\n\n#{html}\n\n" - end + s = s + "\n\n#{html}\n\n" unless s.gsub!(get_xf_regexp(xf_type, id), html) else STDERR.puts "Could not process upload: #{original_filename}. Skipping attachment id #{id}" end @@ -503,7 +525,7 @@ class ImportScripts::XenForo < ImportScripts::Base when :gallery Regexp.new(/\[GALLERY=media,\s#{id ? id : '(\d+)'}\].+?\]/i) when :attachment - Regexp.new(/\[ATTACH(?>=\w+)?\]#{id ? id : '(\d+)'}\[\/ATTACH\]/i) + Regexp.new(%r{\[ATTACH(?>=\w+)?\]#{id ? id : '(\d+)'}\[/ATTACH\]}i) end end diff --git a/script/import_scripts/yahoogroup.rb b/script/import_scripts/yahoogroup.rb index 93651e5d7f..4c307f506e 100644 --- a/script/import_scripts/yahoogroup.rb +++ b/script/import_scripts/yahoogroup.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require File.expand_path(File.dirname(__FILE__) + "/base.rb") -require 'mongo' +require "mongo" # Import YahooGroups data as exported into MongoDB by: # https://github.com/jonbartlett/yahoo-groups-export @@ -13,14 +13,13 @@ require 'mongo' # =end class ImportScripts::YahooGroup < ImportScripts::Base - - MONGODB_HOST = '192.168.10.1:27017' - MONGODB_DB = 'syncro' + MONGODB_HOST = "192.168.10.1:27017" + MONGODB_DB = "syncro" def initialize super - client = Mongo::Client.new([ MONGODB_HOST ], database: MONGODB_DB) + client = Mongo::Client.new([MONGODB_HOST], database: MONGODB_DB) db = client.database Mongo::Logger.logger.level = Logger::FATAL puts "connected to db...." @@ -28,7 +27,6 @@ class ImportScripts::YahooGroup < ImportScripts::Base @collection = client[:posts] @user_profile_map = {} - end def execute @@ -41,43 +39,42 @@ class ImportScripts::YahooGroup < ImportScripts::Base end def import_users - - puts '', "Importing users" + puts "", "Importing users" # fetch distinct list of Yahoo "profile" names - profiles = @collection.aggregate( - [ - { "$group": { "_id": { profile: "$ygData.profile" } } } - ] - ) + profiles = @collection.aggregate([{ "$group": { _id: { profile: "$ygData.profile" } } }]) user_id = 0 create_users(profiles.to_a) do |u| - user_id = user_id + 1 # fetch last message for profile to pickup latest user info as this may have changed - user_info = @collection.find("ygData.profile": u["_id"]["profile"]).sort("ygData.msgId": -1).limit(1).to_a[0] + user_info = + @collection + .find("ygData.profile": u["_id"]["profile"]) + .sort("ygData.msgId": -1) + .limit(1) + .to_a[ + 0 + ] # Store user_id to profile lookup @user_profile_map.store(user_info["ygData"]["profile"], user_id) puts "User created: #{user_info["ygData"]["profile"]}" - user = - { - id: user_id, # yahoo "userId" sequence appears to have changed mid forum life so generate this + user = { + id: user_id, # yahoo "userId" sequence appears to have changed mid forum life so generate this username: user_info["ygData"]["profile"], name: user_info["ygData"]["authorName"], email: user_info["ygData"]["from"], # mandatory - created_at: Time.now + created_at: Time.now, } user end puts "#{user_id} users created" - end def import_discussions @@ -86,21 +83,16 @@ class ImportScripts::YahooGroup < ImportScripts::Base topics_count = 0 posts_count = 0 - topics = @collection.aggregate( - [ - { "$group": { "_id": { topicId: "$ygData.topicId" } } } - ] - ).to_a + topics = @collection.aggregate([{ "$group": { _id: { topicId: "$ygData.topicId" } } }]).to_a # for each distinct topicId found topics.each_with_index do |t, tidx| - # create "topic" post first. # fetch topic document topic_post = @collection.find("ygData.msgId": t["_id"]["topicId"]).to_a[0] next if topic_post.nil? - puts "Topic: #{tidx + 1} / #{topics.count()} (#{sprintf('%.2f', ((tidx + 1).to_f / topics.count().to_f) * 100)}%) Subject: #{topic_post["ygData"]["subject"]}" + puts "Topic: #{tidx + 1} / #{topics.count()} (#{sprintf("%.2f", ((tidx + 1).to_f / topics.count().to_f) * 100)}%) Subject: #{topic_post["ygData"]["subject"]}" if topic_post["ygData"]["subject"].to_s.empty? topic_title = "No Subject" @@ -115,8 +107,10 @@ class ImportScripts::YahooGroup < ImportScripts::Base created_at: Time.at(topic_post["ygData"]["postDate"].to_i), cook_method: Post.cook_methods[:raw_html], title: topic_title, - category: ENV['CATEGORY_ID'], - custom_fields: { import_id: topic_post["ygData"]["msgId"] } + category: ENV["CATEGORY_ID"], + custom_fields: { + import_id: topic_post["ygData"]["msgId"], + }, } topics_count += 1 @@ -128,34 +122,31 @@ class ImportScripts::YahooGroup < ImportScripts::Base posts = @collection.find("ygData.topicId": topic_post["ygData"]["topicId"]).to_a posts.each_with_index do |p, pidx| - # skip over first post as this is created by topic above next if p["ygData"]["msgId"] == topic_post["ygData"]["topicId"] puts " Post: #{pidx + 1} / #{posts.count()}" post = { - id: pidx + 1, - topic_id: parent_post[:topic_id], - user_id: @user_profile_map[p["ygData"]["profile"]] || -1, - raw: p["ygData"]["messageBody"], - created_at: Time.at(p["ygData"]["postDate"].to_i), - cook_method: Post.cook_methods[:raw_html], - custom_fields: { import_id: p["ygData"]["msgId"] } + id: pidx + 1, + topic_id: parent_post[:topic_id], + user_id: @user_profile_map[p["ygData"]["profile"]] || -1, + raw: p["ygData"]["messageBody"], + created_at: Time.at(p["ygData"]["postDate"].to_i), + cook_method: Post.cook_methods[:raw_html], + custom_fields: { + import_id: p["ygData"]["msgId"], + }, } child_post = create_post(post, post[:id]) posts_count += 1 - end - end puts "", "Imported #{topics_count} topics with #{topics_count + posts_count} posts." - end - end ImportScripts::YahooGroup.new.perform diff --git a/script/import_scripts/zendesk.rb b/script/import_scripts/zendesk.rb index a8e44f5ffd..a8b5ef59ac 100644 --- a/script/import_scripts/zendesk.rb +++ b/script/import_scripts/zendesk.rb @@ -9,10 +9,10 @@ # - posts.csv (posts in Zendesk are topics in Discourse) # - comments.csv (comments in Zendesk are posts in Discourse) -require 'csv' -require 'reverse_markdown' -require_relative 'base' -require_relative 'base/generic_database' +require "csv" +require "reverse_markdown" +require_relative "base" +require_relative "base/generic_database" # Call it like this: # RAILS_ENV=production bundle exec ruby script/import_scripts/zendesk.rb DIRNAME @@ -45,7 +45,7 @@ class ImportScripts::Zendesk < ImportScripts::Base name: row[:name], description: row[:description], position: row[:position], - url: row[:htmlurl] + url: row[:htmlurl], ) end @@ -56,7 +56,7 @@ class ImportScripts::Zendesk < ImportScripts::Base name: row[:name], created_at: parse_datetime(row[:createdat]), last_seen_at: parse_datetime(row[:lastloginat]), - active: true + active: true, ) end @@ -69,7 +69,7 @@ class ImportScripts::Zendesk < ImportScripts::Base closed: row[:closed] == "TRUE", user_id: row[:authorid], created_at: parse_datetime(row[:createdat]), - url: row[:htmlurl] + url: row[:htmlurl], ) end @@ -80,7 +80,7 @@ class ImportScripts::Zendesk < ImportScripts::Base topic_id: row[:postid], user_id: row[:authorid], created_at: parse_datetime(row[:createdat]), - url: row[:htmlurl] + url: row[:htmlurl], ) end @@ -99,14 +99,15 @@ class ImportScripts::Zendesk < ImportScripts::Base create_categories(rows) do |row| { - id: row['id'], - name: row['name'], - description: row['description'], - position: row['position'], - post_create_action: proc do |category| - url = remove_domain(row['url']) - Permalink.create(url: url, category_id: category.id) unless permalink_exists?(url) - end + id: row["id"], + name: row["name"], + description: row["description"], + position: row["position"], + post_create_action: + proc do |category| + url = remove_domain(row["url"]) + Permalink.create(url: url, category_id: category.id) unless permalink_exists?(url) + end, } end end @@ -118,22 +119,22 @@ class ImportScripts::Zendesk < ImportScripts::Base def import_users puts "", "creating users" total_count = @db.count_users - last_id = '' + last_id = "" batches do |offset| rows, last_id = @db.fetch_users(last_id) break if rows.empty? - next if all_records_exist?(:users, rows.map { |row| row['id'] }) + next if all_records_exist?(:users, rows.map { |row| row["id"] }) create_users(rows, total: total_count, offset: offset) do |row| { - id: row['id'], - email: row['email'], - name: row['name'], - created_at: row['created_at'], - last_seen_at: row['last_seen_at'], - active: row['active'] == 1 + id: row["id"], + email: row["email"], + name: row["name"], + created_at: row["created_at"], + last_seen_at: row["last_seen_at"], + active: row["active"] == 1, } end end @@ -142,27 +143,28 @@ class ImportScripts::Zendesk < ImportScripts::Base def import_topics puts "", "creating topics" total_count = @db.count_topics - last_id = '' + last_id = "" batches do |offset| rows, last_id = @db.fetch_topics(last_id) break if rows.empty? - next if all_records_exist?(:posts, rows.map { |row| import_topic_id(row['id']) }) + next if all_records_exist?(:posts, rows.map { |row| import_topic_id(row["id"]) }) create_posts(rows, total: total_count, offset: offset) do |row| { - id: import_topic_id(row['id']), - title: row['title'].present? ? row['title'].strip[0...255] : "Topic title missing", - raw: normalize_raw(row['raw']), - category: category_id_from_imported_category_id(row['category_id']), - user_id: user_id_from_imported_user_id(row['user_id']) || Discourse.system_user.id, - created_at: row['created_at'], - closed: row['closed'] == 1, - post_create_action: proc do |post| - url = remove_domain(row['url']) - Permalink.create(url: url, topic_id: post.topic.id) unless permalink_exists?(url) - end + id: import_topic_id(row["id"]), + title: row["title"].present? ? row["title"].strip[0...255] : "Topic title missing", + raw: normalize_raw(row["raw"]), + category: category_id_from_imported_category_id(row["category_id"]), + user_id: user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id, + created_at: row["created_at"], + closed: row["closed"] == 1, + post_create_action: + proc do |post| + url = remove_domain(row["url"]) + Permalink.create(url: url, topic_id: post.topic.id) unless permalink_exists?(url) + end, } end end @@ -181,34 +183,35 @@ class ImportScripts::Zendesk < ImportScripts::Base rows, last_row_id = @db.fetch_sorted_posts(last_row_id) break if rows.empty? - next if all_records_exist?(:posts, rows.map { |row| row['id'] }) + next if all_records_exist?(:posts, rows.map { |row| row["id"] }) create_posts(rows, total: total_count, offset: offset) do |row| - topic = topic_lookup_from_imported_post_id(import_topic_id(row['topic_id'])) + topic = topic_lookup_from_imported_post_id(import_topic_id(row["topic_id"])) if topic.nil? - p "MISSING TOPIC #{row['topic_id']}" + p "MISSING TOPIC #{row["topic_id"]}" p row next end { - id: row['id'], - raw: normalize_raw(row['raw']), - user_id: user_id_from_imported_user_id(row['user_id']) || Discourse.system_user.id, + id: row["id"], + raw: normalize_raw(row["raw"]), + user_id: user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id, topic_id: topic[:topic_id], - created_at: row['created_at'], - post_create_action: proc do |post| - url = remove_domain(row['url']) - Permalink.create(url: url, post_id: post.id) unless permalink_exists?(url) - end + created_at: row["created_at"], + post_create_action: + proc do |post| + url = remove_domain(row["url"]) + Permalink.create(url: url, post_id: post.id) unless permalink_exists?(url) + end, } end end end def normalize_raw(raw) - raw = raw.gsub('\n', '') + raw = raw.gsub('\n', "") raw = ReverseMarkdown.convert(raw) raw end @@ -222,11 +225,13 @@ class ImportScripts::Zendesk < ImportScripts::Base end def csv_parse(table_name) - CSV.foreach(File.join(@path, "#{table_name}.csv"), - headers: true, - header_converters: :symbol, - skip_blanks: true, - encoding: 'bom|utf-8') { |row| yield row } + CSV.foreach( + File.join(@path, "#{table_name}.csv"), + headers: true, + header_converters: :symbol, + skip_blanks: true, + encoding: "bom|utf-8", + ) { |row| yield row } end end diff --git a/script/import_scripts/zendesk_api.rb b/script/import_scripts/zendesk_api.rb index 9237a76442..d76ff1652d 100644 --- a/script/import_scripts/zendesk_api.rb +++ b/script/import_scripts/zendesk_api.rb @@ -4,10 +4,10 @@ # # This one uses their API. -require 'open-uri' -require 'reverse_markdown' -require_relative 'base' -require_relative 'base/generic_database' +require "open-uri" +require "reverse_markdown" +require_relative "base" +require_relative "base/generic_database" # Call it like this: # RAILS_ENV=production bundle exec ruby script/import_scripts/zendesk_api.rb SOURCE_URL DIRNAME AUTH_EMAIL AUTH_TOKEN @@ -23,7 +23,7 @@ class ImportScripts::ZendeskApi < ImportScripts::Base Net::ProtocolError, Timeout::Error, OpenURI::HTTPError, - OpenSSL::SSL::SSLError + OpenSSL::SSL::SSLError, ] MAX_RETRIES = 5 @@ -62,66 +62,72 @@ class ImportScripts::ZendeskApi < ImportScripts::Base end def fetch_categories - puts '', 'fetching categories...' + puts "", "fetching categories..." - get_from_api('/api/v2/community/topics.json', 'topics', show_status: true) do |row| + get_from_api("/api/v2/community/topics.json", "topics", show_status: true) do |row| @db.insert_category( - id: row['id'], - name: row['name'], - description: row['description'], - position: row['position'], - url: row['html_url'] + id: row["id"], + name: row["name"], + description: row["description"], + position: row["position"], + url: row["html_url"], ) end end def fetch_topics - puts '', 'fetching topics...' + puts "", "fetching topics..." - get_from_api('/api/v2/community/posts.json', 'posts', show_status: true) do |row| - if row['vote_count'] > 0 - like_user_ids = fetch_likes("/api/v2/community/posts/#{row['id']}/votes.json") + get_from_api("/api/v2/community/posts.json", "posts", show_status: true) do |row| + if row["vote_count"] > 0 + like_user_ids = fetch_likes("/api/v2/community/posts/#{row["id"]}/votes.json") end @db.insert_topic( - id: row['id'], - title: row['title'], - raw: row['details'], - category_id: row['topic_id'], - closed: row['closed'], - user_id: row['author_id'], - created_at: row['created_at'], - url: row['html_url'], - like_user_ids: like_user_ids + id: row["id"], + title: row["title"], + raw: row["details"], + category_id: row["topic_id"], + closed: row["closed"], + user_id: row["author_id"], + created_at: row["created_at"], + url: row["html_url"], + like_user_ids: like_user_ids, ) end end def fetch_posts - puts '', 'fetching posts...' + puts "", "fetching posts..." current_count = 0 total_count = @db.count_topics start_time = Time.now - last_id = '' + last_id = "" batches do |offset| rows, last_id = @db.fetch_topics(last_id) break if rows.empty? rows.each do |topic_row| - get_from_api("/api/v2/community/posts/#{topic_row['id']}/comments.json", 'comments') do |row| - if row['vote_count'] > 0 - like_user_ids = fetch_likes("/api/v2/community/posts/#{topic_row['id']}/comments/#{row['id']}/votes.json") + get_from_api( + "/api/v2/community/posts/#{topic_row["id"]}/comments.json", + "comments", + ) do |row| + if row["vote_count"] > 0 + like_user_ids = + fetch_likes( + "/api/v2/community/posts/#{topic_row["id"]}/comments/#{row["id"]}/votes.json", + ) end @db.insert_post( - id: row['id'], - raw: row['body'], - topic_id: topic_row['id'], - user_id: row['author_id'], - created_at: row['created_at'], - url: row['html_url'], - like_user_ids: like_user_ids + id: row["id"], + raw: row["body"], + topic_id: topic_row["id"], + user_id: row["author_id"], + created_at: row["created_at"], + url: row["html_url"], + like_user_ids: like_user_ids, ) end @@ -132,9 +138,9 @@ class ImportScripts::ZendeskApi < ImportScripts::Base end def fetch_users - puts '', 'fetching users...' + puts "", "fetching users..." - user_ids = @db.execute_sql(<<~SQL).map { |row| row['user_id'] } + user_ids = @db.execute_sql(<<~SQL).map { |row| row["user_id"] } SELECT user_id FROM topic UNION SELECT user_id FROM post @@ -147,15 +153,18 @@ class ImportScripts::ZendeskApi < ImportScripts::Base start_time = Time.now while !user_ids.empty? - get_from_api("/api/v2/users/show_many.json?ids=#{user_ids.shift(50).join(',')}", 'users') do |row| + get_from_api( + "/api/v2/users/show_many.json?ids=#{user_ids.shift(50).join(",")}", + "users", + ) do |row| @db.insert_user( - id: row['id'], - email: row['email'], - name: row['name'], - created_at: row['created_at'], - last_seen_at: row['last_login_at'], - active: row['active'], - avatar_path: row['photo'].present? ? row['photo']['content_url'] : nil + id: row["id"], + email: row["email"], + name: row["name"], + created_at: row["created_at"], + last_seen_at: row["last_login_at"], + active: row["active"], + avatar_path: row["photo"].present? ? row["photo"]["content_url"] : nil, ) current_count += 1 @@ -167,10 +176,8 @@ class ImportScripts::ZendeskApi < ImportScripts::Base def fetch_likes(url) user_ids = [] - get_from_api(url, 'votes') do |row| - if row['id'].present? && row['value'] == 1 - user_ids << row['user_id'] - end + get_from_api(url, "votes") do |row| + user_ids << row["user_id"] if row["id"].present? && row["value"] == 1 end user_ids @@ -182,14 +189,15 @@ class ImportScripts::ZendeskApi < ImportScripts::Base create_categories(rows) do |row| { - id: row['id'], - name: row['name'], - description: row['description'], - position: row['position'], - post_create_action: proc do |category| - url = remove_domain(row['url']) - Permalink.create(url: url, category_id: category.id) unless permalink_exists?(url) - end + id: row["id"], + name: row["name"], + description: row["description"], + position: row["position"], + post_create_action: + proc do |category| + url = remove_domain(row["url"]) + Permalink.create(url: url, category_id: category.id) unless permalink_exists?(url) + end, } end end @@ -197,27 +205,32 @@ class ImportScripts::ZendeskApi < ImportScripts::Base def import_users puts "", "creating users" total_count = @db.count_users - last_id = '' + last_id = "" batches do |offset| rows, last_id = @db.fetch_users(last_id) break if rows.empty? - next if all_records_exist?(:users, rows.map { |row| row['id'] }) + next if all_records_exist?(:users, rows.map { |row| row["id"] }) create_users(rows, total: total_count, offset: offset) do |row| { - id: row['id'], - email: row['email'], - name: row['name'], - created_at: row['created_at'], - last_seen_at: row['last_seen_at'], - active: row['active'] == 1, - post_create_action: proc do |user| - if row['avatar_path'].present? - UserAvatar.import_url_for_user(row['avatar_path'], user) rescue nil - end - end + id: row["id"], + email: row["email"], + name: row["name"], + created_at: row["created_at"], + last_seen_at: row["last_seen_at"], + active: row["active"] == 1, + post_create_action: + proc do |user| + if row["avatar_path"].present? + begin + UserAvatar.import_url_for_user(row["avatar_path"], user) + rescue StandardError + nil + end + end + end, } end end @@ -226,27 +239,32 @@ class ImportScripts::ZendeskApi < ImportScripts::Base def import_topics puts "", "creating topics" total_count = @db.count_topics - last_id = '' + last_id = "" batches do |offset| rows, last_id = @db.fetch_topics(last_id) break if rows.empty? - next if all_records_exist?(:posts, rows.map { |row| import_topic_id(row['id']) }) + next if all_records_exist?(:posts, rows.map { |row| import_topic_id(row["id"]) }) create_posts(rows, total: total_count, offset: offset) do |row| { - id: import_topic_id(row['id']), - title: row['title'].present? ? row['title'].strip[0...255] : "Topic title missing", - raw: normalize_raw(row['raw'], user_id_from_imported_user_id(row['user_id']) || Discourse.system_user.id), - category: category_id_from_imported_category_id(row['category_id']), - user_id: user_id_from_imported_user_id(row['user_id']) || Discourse.system_user.id, - created_at: row['created_at'], - closed: row['closed'] == 1, - post_create_action: proc do |post| - url = remove_domain(row['url']) - Permalink.create(url: url, topic_id: post.topic.id) unless permalink_exists?(url) - end + id: import_topic_id(row["id"]), + title: row["title"].present? ? row["title"].strip[0...255] : "Topic title missing", + raw: + normalize_raw( + row["raw"], + user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id, + ), + category: category_id_from_imported_category_id(row["category_id"]), + user_id: user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id, + created_at: row["created_at"], + closed: row["closed"] == 1, + post_create_action: + proc do |post| + url = remove_domain(row["url"]) + Permalink.create(url: url, topic_id: post.topic.id) unless permalink_exists?(url) + end, } end end @@ -266,24 +284,29 @@ class ImportScripts::ZendeskApi < ImportScripts::Base break if rows.empty? create_posts(rows, total: total_count, offset: offset) do |row| - topic = topic_lookup_from_imported_post_id(import_topic_id(row['topic_id'])) + topic = topic_lookup_from_imported_post_id(import_topic_id(row["topic_id"])) if topic.nil? - p "MISSING TOPIC #{row['topic_id']}" + p "MISSING TOPIC #{row["topic_id"]}" p row next end { - id: row['id'], - raw: normalize_raw(row['raw'], user_id_from_imported_user_id(row['user_id']) || Discourse.system_user.id), - user_id: user_id_from_imported_user_id(row['user_id']) || Discourse.system_user.id, + id: row["id"], + raw: + normalize_raw( + row["raw"], + user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id, + ), + user_id: user_id_from_imported_user_id(row["user_id"]) || Discourse.system_user.id, topic_id: topic[:topic_id], - created_at: row['created_at'], - post_create_action: proc do |post| - url = remove_domain(row['url']) - Permalink.create(url: url, post_id: post.id) unless permalink_exists?(url) - end + created_at: row["created_at"], + post_create_action: + proc do |post| + url = remove_domain(row["url"]) + Permalink.create(url: url, post_id: post.id) unless permalink_exists?(url) + end, } end end @@ -301,9 +324,9 @@ class ImportScripts::ZendeskApi < ImportScripts::Base break if rows.empty? rows.each do |row| - import_id = row['topic_id'] ? import_topic_id(row['topic_id']) : row['post_id'] + import_id = row["topic_id"] ? import_topic_id(row["topic_id"]) : row["post_id"] post = Post.find_by(id: post_id_from_imported_post_id(import_id)) if import_id - user = User.find_by(id: user_id_from_imported_user_id(row['user_id'])) + user = User.find_by(id: user_id_from_imported_user_id(row["user_id"])) if post && user begin @@ -312,7 +335,7 @@ class ImportScripts::ZendeskApi < ImportScripts::Base puts "error acting on post #{e}" end else - puts "Skipping Like from #{row['user_id']} on topic #{row['topic_id']} / post #{row['post_id']}" + puts "Skipping Like from #{row["user_id"]} on topic #{row["topic_id"]} / post #{row["post_id"]}" end current_count += 1 @@ -322,23 +345,23 @@ class ImportScripts::ZendeskApi < ImportScripts::Base end def normalize_raw(raw, user_id) - raw = raw.gsub('\n', '') + raw = raw.gsub('\n', "") raw = ReverseMarkdown.convert(raw) # Process images, after the ReverseMarkdown they look like # ![](https://.zendesk.com/.) - raw.gsub!(/!\[\]\((https:\/\/#{SUBDOMAIN}\.zendesk\.com\/hc\/user_images\/([^).]+\.[^)]+))\)/i) do + raw.gsub!(%r{!\[\]\((https://#{SUBDOMAIN}\.zendesk\.com/hc/user_images/([^).]+\.[^)]+))\)}i) do image_url = $1 filename = $2 attempts = 0 begin - URI.parse(image_url).open do |image| - # IMAGE_DOWNLOAD_PATH is whatever image, it will be replaced with the downloaded image - File.open(IMAGE_DOWNLOAD_PATH, "wb") do |file| - file.write(image.read) + URI + .parse(image_url) + .open do |image| + # IMAGE_DOWNLOAD_PATH is whatever image, it will be replaced with the downloaded image + File.open(IMAGE_DOWNLOAD_PATH, "wb") { |file| file.write(image.read) } end - end rescue *HTTP_ERRORS => e if attempts < MAX_RETRIES attempts += 1 @@ -374,23 +397,25 @@ class ImportScripts::ZendeskApi < ImportScripts::Base end def connection - @_connection ||= begin - connect_uri = URI.parse(@source_url) + @_connection ||= + begin + connect_uri = URI.parse(@source_url) - http = Net::HTTP.new(connect_uri.host, connect_uri.port) - http.open_timeout = 30 - http.read_timeout = 30 - http.use_ssl = connect_uri.scheme == "https" + http = Net::HTTP.new(connect_uri.host, connect_uri.port) + http.open_timeout = 30 + http.read_timeout = 30 + http.use_ssl = connect_uri.scheme == "https" - http - end + http + end end def authorization - @_authorization ||= begin - auth_str = "#{@auth_email}/token:#{@auth_token}" - "Basic #{Base64.strict_encode64(auth_str)}" - end + @_authorization ||= + begin + auth_str = "#{@auth_email}/token:#{@auth_token}" + "Basic #{Base64.strict_encode64(auth_str)}" + end end def get_from_api(path, array_name, show_status: false) @@ -399,8 +424,8 @@ class ImportScripts::ZendeskApi < ImportScripts::Base while url get = Net::HTTP::Get.new(url) - get['User-Agent'] = 'Discourse Zendesk Importer' - get['Authorization'] = authorization + get["User-Agent"] = "Discourse Zendesk Importer" + get["Authorization"] = authorization retry_count = 0 @@ -420,26 +445,27 @@ class ImportScripts::ZendeskApi < ImportScripts::Base json = JSON.parse(response.body) - json[array_name].each do |row| - yield row - end + json[array_name].each { |row| yield row } - url = json['next_page'] + url = json["next_page"] if show_status - if json['page'] && json['page_count'] - print_status(json['page'], json['page_count'], start_time) + if json["page"] && json["page_count"] + print_status(json["page"], json["page_count"], start_time) else - print '.' + print "." end end end end - end unless ARGV.length == 4 && Dir.exist?(ARGV[1]) - puts "", "Usage:", "", "bundle exec ruby script/import_scripts/zendesk_api.rb SOURCE_URL DIRNAME AUTH_EMAIL AUTH_TOKEN", "" + puts "", + "Usage:", + "", + "bundle exec ruby script/import_scripts/zendesk_api.rb SOURCE_URL DIRNAME AUTH_EMAIL AUTH_TOKEN", + "" exit 1 end diff --git a/script/import_scripts/zoho.rb b/script/import_scripts/zoho.rb index e354b79f8d..b05debeb7e 100644 --- a/script/import_scripts/zoho.rb +++ b/script/import_scripts/zoho.rb @@ -21,14 +21,13 @@ # full names instead of usernames. This may cause duplicate users with slightly different # usernames to be created. -require 'csv' +require "csv" require File.expand_path(File.dirname(__FILE__) + "/base.rb") require File.expand_path(File.dirname(__FILE__) + "/base/csv_helper.rb") # Call it like this: # bundle exec ruby script/import_scripts/zoho.rb class ImportScripts::Zoho < ImportScripts::Base - include ImportScripts::CsvHelper BATCH_SIZE = 1000 @@ -50,19 +49,14 @@ class ImportScripts::Zoho < ImportScripts::Base end def cleanup_zoho_username(s) - s.strip.gsub(/[^A-Za-z0-9_\.\-]/, '') + s.strip.gsub(/[^A-Za-z0-9_\.\-]/, "") end def import_users puts "", "Importing users" - create_users(CSV.parse(File.read(File.join(@path, 'users.csv')))) do |u| + create_users(CSV.parse(File.read(File.join(@path, "users.csv")))) do |u| username = cleanup_zoho_username(u[0]) - { - id: username, - username: username, - email: u[1], - created_at: Time.zone.now - } + { id: username, username: username, email: u[1], created_at: Time.zone.now } end end @@ -83,9 +77,7 @@ class ImportScripts::Zoho < ImportScripts::Base csv_parse(File.join(@path, "posts.csv")) do |row| @all_posts << row.dup - if @categories[row.forum_name].nil? - @categories[row.forum_name] = [] - end + @categories[row.forum_name] = [] if @categories[row.forum_name].nil? unless @categories[row.forum_name].include?(row.category_name) @categories[row.forum_name] << row.category_name @@ -105,56 +97,61 @@ class ImportScripts::Zoho < ImportScripts::Base puts "", "Creating topics and posts" - created, skipped = create_posts(@all_posts, total: @all_posts.size) do |row| - @current_row = row + created, skipped = + create_posts(@all_posts, total: @all_posts.size) do |row| + @current_row = row - # fetch user - username = cleanup_zoho_username(row.author) + # fetch user + username = cleanup_zoho_username(row.author) - next if username.blank? # no author for this post, so skip + next if username.blank? # no author for this post, so skip - user_id = user_id_from_imported_user_id(username) + user_id = user_id_from_imported_user_id(username) - if user_id.nil? - # user CSV file didn't have a user with this username. create it now with an invalid email address. - u = create_user( - { id: username, - username: username, - email: "#{username}@example.com", - created_at: Time.zone.parse(row.posted_time) }, - username - ) - user_id = u.id - end - - if @topic_mapping[row.permalink].nil? - category_id = nil - if row.category_name != "Uncategorized" && row.category_name != "Uncategorised" - category_id = category_id_from_imported_category_id("#{row.forum_name}:#{row.category_name}") - else - category_id = category_id_from_imported_category_id(row.forum_name) + if user_id.nil? + # user CSV file didn't have a user with this username. create it now with an invalid email address. + u = + create_user( + { + id: username, + username: username, + email: "#{username}@example.com", + created_at: Time.zone.parse(row.posted_time), + }, + username, + ) + user_id = u.id end - # create topic - { - id: import_post_id(row), - user_id: user_id, - category: category_id, - title: CGI.unescapeHTML(row.topic_title), - raw: cleanup_post(row.content), - created_at: Time.zone.parse(row.posted_time) - } - # created_post callback will be called - else - { - id: import_post_id(row), - user_id: user_id, - raw: cleanup_post(row.content), - created_at: Time.zone.parse(row.posted_time), - topic_id: @topic_mapping[row.permalink] - } + if @topic_mapping[row.permalink].nil? + category_id = nil + if row.category_name != "Uncategorized" && row.category_name != "Uncategorised" + category_id = + category_id_from_imported_category_id("#{row.forum_name}:#{row.category_name}") + else + category_id = category_id_from_imported_category_id(row.forum_name) + end + + # create topic + { + id: import_post_id(row), + user_id: user_id, + category: category_id, + title: CGI.unescapeHTML(row.topic_title), + raw: cleanup_post(row.content), + created_at: Time.zone.parse(row.posted_time), + } + # created_post callback will be called + else + { + id: import_post_id(row), + user_id: user_id, + raw: cleanup_post(row.content), + created_at: Time.zone.parse(row.posted_time), + topic_id: @topic_mapping[row.permalink], + } + end end - end puts "" puts "Created: #{created}" @@ -176,31 +173,30 @@ class ImportScripts::Zoho < ImportScripts::Base STYLE_ATTR = /(\s)*style="(.)*"/ def cleanup_post(raw) - # Check if Zoho's most common form of a code block is present. # If so, don't clean up the post as much because we can't tell which markup # is inside the code block. These posts will look worse than others. has_code_block = !!(raw =~ ZOHO_CODE_BLOCK_START) - x = raw.gsub(STYLE_ATTR, '') + x = raw.gsub(STYLE_ATTR, "") if has_code_block # We have to assume all lists in this post are meant to be code blocks # to make it somewhat readable. x.gsub!(/( )*
      (\s)*/, "") - x.gsub!(/( )*<\/ol>/, "") - x.gsub!('
    1. ', '') - x.gsub!('
    2. ', '') + x.gsub!(%r{( )*
    }, "") + x.gsub!("
  • ", "") + x.gsub!("
  • ", "") else # No code block (probably...) so clean up more aggressively. x.gsub!("\n", " ") - x.gsub!('
    ', "\n\n") - x.gsub('
    ', ' ') + x.gsub!("
    ", "\n\n") + x.gsub("
    ", " ") x.gsub!("
    ", "\n") - x.gsub!('', '') - x.gsub!('', '') - x.gsub!(/]*)>/, '') - x.gsub!('', '') + x.gsub!("", "") + x.gsub!("", "") + x.gsub!(/]*)>/, "") + x.gsub!("", "") end x.gsub!(TOO_MANY_LINE_BREAKS, "\n\n") @@ -213,13 +209,10 @@ class ImportScripts::Zoho < ImportScripts::Base # The posted_time seems to be the same for all posts in a topic, so we can't use that. Digest::SHA1.hexdigest "#{row.permalink}:#{row.content}" end - end unless ARGV[0] && Dir.exist?(ARGV[0]) - if ARGV[0] && !Dir.exist?(ARGV[0]) - puts "", "ERROR! Dir #{ARGV[0]} not found.", "" - end + puts "", "ERROR! Dir #{ARGV[0]} not found.", "" if ARGV[0] && !Dir.exist?(ARGV[0]) puts "", "Usage:", "", " bundle exec ruby script/import_scripts/zoho.rb DIRNAME", "" exit 1 diff --git a/script/measure.rb b/script/measure.rb index 9711206ae6..1dc391735b 100644 --- a/script/measure.rb +++ b/script/measure.rb @@ -1,38 +1,36 @@ # frozen_string_literal: true # using this script to try figure out why Ruby 2 is slower than 1.9 -require 'flamegraph' +require "flamegraph" -Flamegraph.generate('test.html', fidelity: 2) do +Flamegraph.generate("test.html", fidelity: 2) do require File.expand_path("../../config/environment", __FILE__) end exit -require 'memory_profiler' +require "memory_profiler" -result = MemoryProfiler.report do - require File.expand_path("../../config/environment", __FILE__) -end +result = MemoryProfiler.report { require File.expand_path("../../config/environment", __FILE__) } result.pretty_print exit -require 'benchmark' +require "benchmark" def profile_allocations(name) GC.disable initial_size = ObjectSpace.count_objects yield changes = ObjectSpace.count_objects - changes.each do |k, _| - changes[k] -= initial_size[k] - end + changes.each { |k, _| changes[k] -= initial_size[k] } puts "#{name} changes" - changes.sort { |a, b| b[1] <=> a[1] }.each do |a, b| - next if b <= 0 - # 1 extra hash for tracking - puts "#{a} #{a == :T_HASH ? b - 1 : b}" - end + changes + .sort { |a, b| b[1] <=> a[1] } + .each do |a, b| + next if b <= 0 + # 1 extra hash for tracking + puts "#{a} #{a == :T_HASH ? b - 1 : b}" + end GC.enable end @@ -47,9 +45,7 @@ def profile(name, &block) ObjectSpace.trace_object_allocations do block.call - ObjectSpace.each_object do |o| - objs << o - end + ObjectSpace.each_object { |o| objs << o } objs.each do |o| g = ObjectSpace.allocation_generation(o) @@ -63,9 +59,10 @@ def profile(name, &block) end end - items.group_by { |x| x }.sort { |a, b| b[1].length <=> a[1].length }.each do |row, group| - puts "#{row} x #{group.length}" - end + items + .group_by { |x| x } + .sort { |a, b| b[1].length <=> a[1].length } + .each { |row, group| puts "#{row} x #{group.length}" } GC.enable profile_allocations(name, &block) diff --git a/script/memstats.rb b/script/memstats.rb index 998d8d525f..4ca1f531dc 100755 --- a/script/memstats.rb +++ b/script/memstats.rb @@ -28,7 +28,7 @@ #------------------------------------------------------------------------------ class Mapping - FIELDS = %w[ size rss shared_clean shared_dirty private_clean private_dirty swap pss ] + FIELDS = %w[size rss shared_clean shared_dirty private_clean private_dirty swap pss] attr_reader :address_start attr_reader :address_end attr_reader :perms @@ -48,15 +48,10 @@ class Mapping attr_accessor :pss def initialize(lines) - - FIELDS.each do |field| - self.public_send("#{field}=", 0) - end + FIELDS.each { |field| self.public_send("#{field}=", 0) } parse_first_line(lines.shift) - lines.each do |l| - parse_field_line(l) - end + lines.each { |l| parse_field_line(l) } end def parse_first_line(line) @@ -71,7 +66,7 @@ class Mapping def parse_field_line(line) parts = line.strip.split - field = parts[0].downcase.sub(':', '') + field = parts[0].downcase.sub(":", "") if respond_to? "#{field}=" value = Float(parts[1]).to_i self.public_send("#{field}=", value) @@ -82,26 +77,21 @@ end def consume_mapping(map_lines, totals) m = Mapping.new(map_lines) - Mapping::FIELDS.each do |field| - totals[field] += m.public_send(field) - end + Mapping::FIELDS.each { |field| totals[field] += m.public_send(field) } m end def create_memstats_not_available(totals) - Mapping::FIELDS.each do |field| - totals[field] += Float::NAN - end + Mapping::FIELDS.each { |field| totals[field] += Float::NAN } end -abort 'usage: memstats [pid]' unless ARGV.first +abort "usage: memstats [pid]" unless ARGV.first pid = ARGV.shift.to_i totals = Hash.new(0) mappings = [] begin File.open("/proc/#{pid}/smaps") do |smaps| - map_lines = [] loop do @@ -111,9 +101,7 @@ begin when /\w+:\s+/ map_lines << line when /[0-9a-f]+:[0-9a-f]+\s+/ - if map_lines.size > 0 then - mappings << consume_mapping(map_lines, totals) - end + mappings << consume_mapping(map_lines, totals) if map_lines.size > 0 map_lines.clear map_lines << line else @@ -121,7 +109,7 @@ begin end end end -rescue +rescue StandardError create_memstats_not_available(totals) end @@ -132,23 +120,19 @@ end def get_commandline(pid) commandline = File.read("/proc/#{pid}/cmdline").split("\0") - if commandline.first =~ /java$/ then + if commandline.first =~ /java$/ loop { break if commandline.shift == "-jar" } return "[java] #{commandline.shift}" end - commandline.join(' ') + commandline.join(" ") end -if ARGV.include? '--yaml' - require 'yaml' - puts Hash[*totals.map do |k, v| - [k + '_kb', v] - end.flatten].to_yaml +if ARGV.include? "--yaml" + require "yaml" + puts Hash[*totals.map do |k, v| [k + "_kb", v] end.flatten].to_yaml else puts "#{"Process:".ljust(20)} #{pid}" puts "#{"Command Line:".ljust(20)} #{get_commandline(pid)}" puts "Memory Summary:" - totals.keys.sort.each do |k| - puts " #{k.ljust(20)} #{format_number(totals[k]).rjust(12)} kB" - end + totals.keys.sort.each { |k| puts " #{k.ljust(20)} #{format_number(totals[k]).rjust(12)} kB" } end diff --git a/script/micro_bench.rb b/script/micro_bench.rb index 51ea6c8912..7ba5dceffe 100644 --- a/script/micro_bench.rb +++ b/script/micro_bench.rb @@ -1,32 +1,20 @@ # frozen_string_literal: true -require 'benchmark/ips' +require "benchmark/ips" require File.expand_path("../../config/environment", __FILE__) conn = ActiveRecord::Base.connection.raw_connection Benchmark.ips do |b| - b.report("simple") do - User.first.name - end + b.report("simple") { User.first.name } - b.report("simple with select") do - User.select("name").first.name - end + b.report("simple with select") { User.select("name").first.name } - b.report("pluck with first") do - User.pluck(:name).first - end + b.report("pluck with first") { User.pluck(:name).first } - b.report("pluck with limit") do - User.limit(1).pluck(:name).first - end + b.report("pluck with limit") { User.limit(1).pluck(:name).first } - b.report("pluck with pluck_first") do - User.pluck_first(:name) - end + b.report("pluck with pluck_first") { User.pluck_first(:name) } - b.report("raw") do - conn.exec("SELECT name FROM users LIMIT 1").getvalue(0, 0) - end + b.report("raw") { conn.exec("SELECT name FROM users LIMIT 1").getvalue(0, 0) } end diff --git a/script/profile_db_generator.rb b/script/profile_db_generator.rb index c49ef6f627..8222c00809 100644 --- a/script/profile_db_generator.rb +++ b/script/profile_db_generator.rb @@ -5,7 +5,7 @@ # we want our script to generate a consistent output, to do so # we monkey patch array sample so it always uses the same rng class Array - RNG = Random.new(1098109928029800) + RNG = Random.new(1_098_109_928_029_800) def sample self[RNG.rand(size)] @@ -16,9 +16,7 @@ end def unbundled_require(gem) if defined?(::Bundler) spec_path = Dir.glob("#{Gem.dir}/specifications/#{gem}-*.gemspec").last - if spec_path.nil? - raise LoadError - end + raise LoadError if spec_path.nil? spec = Gem::Specification.load spec_path spec.activate @@ -30,13 +28,14 @@ def unbundled_require(gem) end def sentence - @gabbler ||= Gabbler.new.tap do |gabbler| - story = File.read(File.dirname(__FILE__) + "/alice.txt") - gabbler.learn(story) - end + @gabbler ||= + Gabbler.new.tap do |gabbler| + story = File.read(File.dirname(__FILE__) + "/alice.txt") + gabbler.learn(story) + end sentence = +"" - until sentence.length > 800 do + until sentence.length > 800 sentence << @gabbler.sentence sentence << "\n" end @@ -74,13 +73,13 @@ if User.count > 2 exit end -require 'optparse' +require "optparse" begin - unbundled_require 'gabbler' + unbundled_require "gabbler" rescue LoadError puts "installing gabbler gem" puts `gem install gabbler` - unbundled_require 'gabbler' + unbundled_require "gabbler" end number_of_users = 100 @@ -98,41 +97,61 @@ users = User.human_users.all puts puts "Creating 10 categories" -categories = 10.times.map do |i| - putc "." - Category.create(name: "category#{i}", text_color: "ffffff", color: "000000", user: admin_user) -end +categories = + 10.times.map do |i| + putc "." + Category.create(name: "category#{i}", text_color: "ffffff", color: "000000", user: admin_user) + end puts puts "Creating 100 topics" -topic_ids = 100.times.map do - post = PostCreator.create(admin_user, raw: sentence, title: sentence[0..50].strip, category: categories.sample.id, skip_validations: true) - putc "." - post.topic_id -end +topic_ids = + 100.times.map do + post = + PostCreator.create( + admin_user, + raw: sentence, + title: sentence[0..50].strip, + category: categories.sample.id, + skip_validations: true, + ) + putc "." + post.topic_id + end puts puts "Creating 2000 replies" 2000.times do putc "." - PostCreator.create(users.sample, raw: sentence, topic_id: topic_ids.sample, skip_validations: true) + PostCreator.create( + users.sample, + raw: sentence, + topic_id: topic_ids.sample, + skip_validations: true, + ) end puts puts "creating perf test topic" -first_post = PostCreator.create( - users.sample, - raw: sentence, - title: "I am a topic used for perf tests", - category: categories.sample.id, - skip_validations: true -) +first_post = + PostCreator.create( + users.sample, + raw: sentence, + title: "I am a topic used for perf tests", + category: categories.sample.id, + skip_validations: true, + ) puts puts "Creating 100 replies for perf test topic" 100.times do putc "." - PostCreator.create(users.sample, raw: sentence, topic_id: first_post.topic_id, skip_validations: true) + PostCreator.create( + users.sample, + raw: sentence, + topic_id: first_post.topic_id, + skip_validations: true, + ) end # no sidekiq so update some stuff diff --git a/script/redis_memory.rb b/script/redis_memory.rb index 442463bc41..b191112cef 100644 --- a/script/redis_memory.rb +++ b/script/redis_memory.rb @@ -24,7 +24,10 @@ stats = {} end puts "Top 100 keys" -stats.sort { |a, b| b[1][0] <=> a[1][0] }.first(50).each do |k, (len, type, elems)| - elems = " [#{elems}]" if elems - puts "#{k} #{type} #{len}#{elems}" -end +stats + .sort { |a, b| b[1][0] <=> a[1][0] } + .first(50) + .each do |k, (len, type, elems)| + elems = " [#{elems}]" if elems + puts "#{k} #{type} #{len}#{elems}" + end diff --git a/script/require_profiler.rb b/script/require_profiler.rb index a0135d08b7..b8f3437efb 100644 --- a/script/require_profiler.rb +++ b/script/require_profiler.rb @@ -5,12 +5,11 @@ # This is a rudimentary script that allows us to # quickly determine if any gems are slowing down startup -require 'benchmark' -require 'fileutils' +require "benchmark" +require "fileutils" module RequireProfiler class << self - attr_accessor :stats def profiling_enabled? @@ -25,10 +24,19 @@ module RequireProfiler def start(tmp_options = {}) @start_time = Time.now - [ ::Kernel, (class << ::Kernel; self; end) ].each do |klass| + [ + ::Kernel, + ( + class << ::Kernel + self + end + ), + ].each do |klass| klass.class_eval do def require_with_profiling(path, *args) - RequireProfiler.measure(path, caller, :require) { require_without_profiling(path, *args) } + RequireProfiler.measure(path, caller, :require) do + require_without_profiling(path, *args) + end end alias require_without_profiling require alias require require_with_profiling @@ -47,7 +55,14 @@ module RequireProfiler def stop @stop_time = Time.now - [ ::Kernel, (class << ::Kernel; self; end) ].each do |klass| + [ + ::Kernel, + ( + class << ::Kernel + self + end + ), + ].each do |klass| klass.class_eval do alias require require_without_profiling alias load load_without_profiling @@ -63,21 +78,20 @@ module RequireProfiler @stack ||= [] self.stats ||= {} - stat = self.stats.fetch(path) { |key| self.stats[key] = { calls: 0, time: 0, parent_time: 0 } } + stat = + self.stats.fetch(path) { |key| self.stats[key] = { calls: 0, time: 0, parent_time: 0 } } @stack << stat time = Time.now begin - output = yield # do the require or load here + output = yield # do the require or load here ensure delta = Time.now - time stat[:time] += delta stat[:calls] += 1 @stack.pop - @stack.each do |frame| - frame[:parent_time] += delta - end + @stack.each { |frame| frame[:parent_time] += delta } end output @@ -102,7 +116,6 @@ module RequireProfiler puts "GC duration: #{gc_duration_finish}" puts "GC impact: #{gc_duration_finish - gc_duration_start}" end - end end @@ -122,8 +135,9 @@ RequireProfiler.profile do end end -sorted = RequireProfiler.stats.to_a.sort { |a, b| b[1][:time] - b[1][:parent_time] <=> a[1][:time] - a[1][:parent_time] } +sorted = + RequireProfiler.stats.to_a.sort do |a, b| + b[1][:time] - b[1][:parent_time] <=> a[1][:time] - a[1][:parent_time] + end -sorted[0..120].each do |k, v| - puts "#{k} : time #{v[:time] - v[:parent_time]} " -end +sorted[0..120].each { |k, v| puts "#{k} : time #{v[:time] - v[:parent_time]} " } diff --git a/script/spawn_backup_restore.rb b/script/spawn_backup_restore.rb index 4af4f667c6..7ab3721d5d 100644 --- a/script/spawn_backup_restore.rb +++ b/script/spawn_backup_restore.rb @@ -15,11 +15,8 @@ fork do BackupRestore::Restorer.new( user_id: user_id, filename: opts[:filename], - factory: BackupRestore::Factory.new( - user_id: user_id, - client_id: opts[:client_id] - ), - disable_emails: opts.fetch(:disable_emails, true) + factory: BackupRestore::Factory.new(user_id: user_id, client_id: opts[:client_id]), + disable_emails: opts.fetch(:disable_emails, true), ).run end diff --git a/script/test_email_settings.rb b/script/test_email_settings.rb index 9ef0fc8b93..c4c9272e43 100755 --- a/script/test_email_settings.rb +++ b/script/test_email_settings.rb @@ -1,16 +1,16 @@ #!/usr/bin/env ruby # frozen_string_literal: true -require 'action_mailer' +require "action_mailer" # Make this your email address. Poor example.com gets SO MUCH MAIL YOUR_EMAIL = "nobody@example.com" # Change these to be the same settings as your Discourse environment -DISCOURSE_SMTP_ADDRESS = "smtp.example.com" # (mandatory) -@DISCOURSE_SMTP_PORT = 587 # (optional) -@DISCOURSE_SMTP_USER_NAME = "username" # (optional) -@DISCOURSE_SMTP_PASSWORD = "blah" # (optional) +DISCOURSE_SMTP_ADDRESS = "smtp.example.com" # (mandatory) +@DISCOURSE_SMTP_PORT = 587 # (optional) +@DISCOURSE_SMTP_USER_NAME = "username" # (optional) +@DISCOURSE_SMTP_PASSWORD = "blah" # (optional) #@DISCOURSE_SMTP_OPENSSL_VERIFY_MODE = "none" # (optional) none|peer|client_once|fail_if_no_peer_cert # Note that DISCOURSE_SMTP_ADDRESS should NOT BE ALLOWED to relay mail to @@ -24,16 +24,18 @@ $delivery_options = { password: @DISCOURSE_SMTP_PASSWORD || nil, address: DISCOURSE_SMTP_ADDRESS, port: @DISCOURSE_SMTP_PORT || nil, - openssl_verify_mode: @DISCOURSE_SMTP_OPENSSL_VERIFY_MODE || nil + openssl_verify_mode: @DISCOURSE_SMTP_OPENSSL_VERIFY_MODE || nil, } class EmailTestMailer < ActionMailer::Base def email_test(mailfrom, mailto) - mail(from: mailfrom, - to: mailto, - body: "Testing email settings", - subject: "Discourse email settings test", - delivery_method_options: $delivery_options) + mail( + from: mailfrom, + to: mailto, + body: "Testing email settings", + subject: "Discourse email settings test", + delivery_method_options: $delivery_options, + ) end end diff --git a/script/test_mem.rb b/script/test_mem.rb index dd64ea2de3..284e99993f 100644 --- a/script/test_mem.rb +++ b/script/test_mem.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true start = Time.now -require 'objspace' +require "objspace" require File.expand_path("../../config/environment", __FILE__) # preload stuff @@ -9,11 +9,19 @@ I18n.t(:posts) # load up all models and schema (ActiveRecord::Base.connection.tables - %w[schema_migrations]).each do |table| - table.classify.constantize.first rescue nil + begin + table.classify.constantize.first + rescue StandardError + nil + end end # router warm up -Rails.application.routes.recognize_path('abc') rescue nil +begin + Rails.application.routes.recognize_path("abc") +rescue StandardError + nil +end puts "Ruby version #{RUBY_VERSION} p#{RUBY_PATCHLEVEL}" @@ -23,8 +31,11 @@ GC.start puts "RSS: #{`ps -o rss -p #{$$}`.chomp.split("\n").last.to_i} KB" -s = ObjectSpace.each_object(String).map do |o| - ObjectSpace.memsize_of(o) + 40 # rvalue size on x64 -end +s = + ObjectSpace + .each_object(String) + .map do |o| + ObjectSpace.memsize_of(o) + 40 # rvalue size on x64 + end puts "Total strings: #{s.count} space used: #{s.sum} bytes" diff --git a/script/test_memory_leak.rb b/script/test_memory_leak.rb index 3ecf01c39e..a426d36784 100644 --- a/script/test_memory_leak.rb +++ b/script/test_memory_leak.rb @@ -5,25 +5,21 @@ # this performs a trivial operation walking all multisites and grabbing first topic / localizing # the expectation is that RSS will remain static no matter how many iterations run -if ENV['RAILS_ENV'] != "production" - exec "RAILS_ENV=production ruby #{__FILE__}" -end +exec "RAILS_ENV=production ruby #{__FILE__}" if ENV["RAILS_ENV"] != "production" -if !ENV['LD_PRELOAD'] - exec "LD_PRELOAD=/usr/lib/libjemalloc.so.1 ruby #{__FILE__}" -end +exec "LD_PRELOAD=/usr/lib/libjemalloc.so.1 ruby #{__FILE__}" if !ENV["LD_PRELOAD"] -if ENV['LD_PRELOAD'].include?("jemalloc") +if ENV["LD_PRELOAD"].include?("jemalloc") # for 3.6.0 we need a patch jemal 1.1.0 gem (1.1.1 does not support 3.6.0) # however ffi is a problem so we need to patch the gem - require 'jemal' + require "jemal" $jemalloc = true end -if ENV['LD_PRELOAD'].include?("mwrap") +if ENV["LD_PRELOAD"].include?("mwrap") $mwrap = true - require 'mwrap' + require "mwrap" end def bin_diff(current) @@ -39,7 +35,11 @@ end require File.expand_path("../../config/environment", __FILE__) -Rails.application.routes.recognize_path('abc') rescue nil +begin + Rails.application.routes.recognize_path("abc") +rescue StandardError + nil +end I18n.t(:posts) def rss @@ -47,9 +47,7 @@ def rss end def loop_sites - RailsMultisite::ConnectionManagement.each_connection do - yield - end + RailsMultisite::ConnectionManagement.each_connection { yield } end def biggest_klass(klass) @@ -57,7 +55,10 @@ def biggest_klass(klass) end def iter(warmup: false) - loop_sites { Topic.first; I18n.t('too_late_to_edit') } + loop_sites do + Topic.first + I18n.t("too_late_to_edit") + end if !warmup GC.start(full_mark: true, immediate_sweep: true) @@ -75,24 +76,17 @@ def iter(warmup: false) array_delta = biggest_klass(Array).length - $biggest_array_length puts "rss: #{rss} (#{rss_delta}) #{mwrap_delta}#{jedelta} heap_delta: #{GC.stat[:heap_live_slots] - $baseline_slots} array_delta: #{array_delta}" - if $jemalloc - bin_diff(jemal_stats) - end + bin_diff(jemal_stats) if $jemalloc end - end iter(warmup: true) -4.times do - GC.start(full_mark: true, immediate_sweep: true) -end +4.times { GC.start(full_mark: true, immediate_sweep: true) } if $jemalloc $baseline = Jemal.stats $baseline_jemalloc_active = $baseline[:active] - 4.times do - GC.start(full_mark: true, immediate_sweep: true) - end + 4.times { GC.start(full_mark: true, immediate_sweep: true) } end def render_table(array) @@ -102,33 +96,33 @@ def render_table(array) cols = array[0].length array.each do |row| - row.each_with_index do |val, i| - width[i] = [width[i].to_i, val.to_s.length].max - end + row.each_with_index { |val, i| width[i] = [width[i].to_i, val.to_s.length].max } end array[0].each_with_index do |col, i| - buffer << col.to_s.ljust(width[i], ' ') + buffer << col.to_s.ljust(width[i], " ") if i == cols - 1 buffer << "\n" else - buffer << ' | ' + buffer << " | " end end buffer << ("-" * (width.sum + width.length)) buffer << "\n" - array.drop(1).each do |row| - row.each_with_index do |val, i| - buffer << val.to_s.ljust(width[i], ' ') - if i == cols - 1 - buffer << "\n" - else - buffer << ' | ' + array + .drop(1) + .each do |row| + row.each_with_index do |val, i| + buffer << val.to_s.ljust(width[i], " ") + if i == cols - 1 + buffer << "\n" + else + buffer << " | " + end end end - end buffer end @@ -141,14 +135,20 @@ def mwrap_log report << "\n" table = [] - Mwrap.each(200000) do |loc, total, allocations, frees, age_sum, max_life| - table << [total, allocations - frees, frees == 0 ? -1 : (age_sum / frees.to_f).round(2), max_life, loc] + Mwrap.each(200_000) do |loc, total, allocations, frees, age_sum, max_life| + table << [ + total, + allocations - frees, + frees == 0 ? -1 : (age_sum / frees.to_f).round(2), + max_life, + loc, + ] end table.sort! { |a, b| b[1] <=> a[1] } table = table[0..50] - table.prepend(["total", "delta", "mean_life", "max_life", "location"]) + table.prepend(%w[total delta mean_life max_life location]) report << render_table(table) end @@ -158,15 +158,13 @@ end Mwrap.clear -if $mwrap - $mwrap_baseline = Mwrap.total_bytes_allocated - Mwrap.total_bytes_freed -end +$mwrap_baseline = Mwrap.total_bytes_allocated - Mwrap.total_bytes_freed if $mwrap $baseline_slots = GC.stat[:heap_live_slots] $baseline_rss = rss $biggest_array_length = biggest_klass(Array).length -100000.times do +100_000.times do iter if $mwrap puts mwrap_log diff --git a/script/test_pretty_text.rb b/script/test_pretty_text.rb index 29839d9629..02e8644a9e 100644 --- a/script/test_pretty_text.rb +++ b/script/test_pretty_text.rb @@ -9,7 +9,7 @@ puts PrettyText.cook "test" # JS PrettyText.cook "test" - PrettyText.v8.eval('gc()') + PrettyText.v8.eval("gc()") # if i % 500 == 0 #p PrettyText.v8.heap_stats diff --git a/script/thread_detective.rb b/script/thread_detective.rb index c654158ede..83e886d085 100644 --- a/script/thread_detective.rb +++ b/script/thread_detective.rb @@ -9,13 +9,9 @@ class ThreadDetective Thread.new { sleep 1 } end def self.start(max_threads) - @thread ||= Thread.new do - self.new.monitor(max_threads) - end + @thread ||= Thread.new { self.new.monitor(max_threads) } - @trace = TracePoint.new(:thread_begin) do |tp| - Thread.current.origin = Thread.current.inspect - end + @trace = TracePoint.new(:thread_begin) { |tp| Thread.current.origin = Thread.current.inspect } @trace.enable end @@ -52,5 +48,4 @@ class ThreadDetective sleep 1 end end - end diff --git a/script/user_simulator.rb b/script/user_simulator.rb index fb7853ac71..562c559ed1 100644 --- a/script/user_simulator.rb +++ b/script/user_simulator.rb @@ -4,31 +4,32 @@ # # by default 1 new topic every 30 sec, 1 reply to last topic every 30 secs -require 'optparse' -require 'gabbler' +require "optparse" +require "gabbler" user_id = nil def sentence - @gabbler ||= Gabbler.new.tap do |gabbler| - story = File.read(File.dirname(__FILE__) + "/alice.txt") - gabbler.learn(story) - end + @gabbler ||= + Gabbler.new.tap do |gabbler| + story = File.read(File.dirname(__FILE__) + "/alice.txt") + gabbler.learn(story) + end sentence = +"" - until sentence.length > 800 do + until sentence.length > 800 sentence << @gabbler.sentence sentence << "\n" end sentence end -OptionParser.new do |opts| - opts.banner = "Usage: ruby user_simulator.rb [options]" - opts.on("-u", "--user NUMBER", "user id") do |u| - user_id = u.to_i +OptionParser + .new do |opts| + opts.banner = "Usage: ruby user_simulator.rb [options]" + opts.on("-u", "--user NUMBER", "user id") { |u| user_id = u.to_i } end -end.parse! + .parse! unless user_id puts "user must be specified" @@ -37,19 +38,19 @@ end require File.expand_path(File.dirname(__FILE__) + "/../config/environment") -unless ["profile", "development"].include? Rails.env +unless %w[profile development].include? Rails.env puts "Bad idea to run a script that inserts random posts in any non development environment" exit end user = User.find(user_id) -last_topics = Topic.order('id desc').limit(10).pluck(:id) +last_topics = Topic.order("id desc").limit(10).pluck(:id) puts "Simulating activity for user id #{user.id}: #{user.name}" while true puts "Creating a random topic" - category = Category.where(read_restricted: false).order('random()').first + category = Category.where(read_restricted: false).order("random()").first PostCreator.create(user, raw: sentence, title: sentence[0..50].strip, category: category.id) puts "creating random reply" From 7c77cc6a580d7cb49f8c19ceee8cfdd08862259d Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 7 Jan 2023 11:59:28 +0000 Subject: [PATCH 29/53] DEV: Apply syntax_tree formatting to `config/*` --- .streerc | 1 - config/application.rb | 136 +- config/boot.rb | 35 +- config/cloud/cloud66/files/production.rb | 20 +- config/environment.rb | 3 +- config/environments/development.rb | 47 +- config/environments/production.rb | 14 +- config/environments/profile.rb | 8 +- config/environments/test.rb | 11 +- .../000-development_reload_warnings.rb | 42 +- config/initializers/000-mini_sql.rb | 2 +- config/initializers/000-post_migration.rb | 7 +- .../initializers/000-trace_pg_connections.rb | 84 +- config/initializers/000-zeitwerk.rb | 48 +- config/initializers/001-redis.rb | 4 +- config/initializers/002-rails_failover.rb | 13 +- config/initializers/004-message_bus.rb | 31 +- config/initializers/005-site_settings.rb | 5 +- config/initializers/006-ensure_login_hint.rb | 8 +- config/initializers/006-mini_profiler.rb | 101 +- config/initializers/008-rack-cors.rb | 28 +- config/initializers/012-web_hook_events.rb | 97 +- config/initializers/013-excon_defaults.rb | 2 +- .../initializers/014-track-setting-changes.rb | 26 +- config/initializers/099-anon-cache.rb | 2 +- config/initializers/099-drain_pool.rb | 1 + config/initializers/100-i18n.rb | 6 +- config/initializers/100-logster.rb | 58 +- config/initializers/100-onebox_options.rb | 4 +- config/initializers/100-push-notifications.rb | 19 +- config/initializers/100-quiet_logger.rb | 23 +- config/initializers/100-session_store.rb | 11 +- config/initializers/100-sidekiq.rb | 43 +- config/initializers/100-silence_logger.rb | 22 +- config/initializers/100-verify_config.rb | 5 +- config/initializers/100-wrap_parameters.rb | 8 +- config/initializers/101-lograge.rb | 138 +- config/initializers/200-first_middlewares.rb | 27 +- config/initializers/400-deprecations.rb | 26 +- config/initializers/assets.rb | 44 +- .../initializers/filter_parameter_logging.rb | 18 +- .../new_framework_defaults_7_0.rb | 2 +- config/locales/plurals.rb | 1 + config/puma.rb | 6 +- config/routes.rb | 1373 ++++++++++++----- config/spring.rb | 4 +- config/unicorn.conf.rb | 104 +- 47 files changed, 1627 insertions(+), 1091 deletions(-) diff --git a/.streerc b/.streerc index 612e4b246d..c385bd0b7c 100644 --- a/.streerc +++ b/.streerc @@ -2,7 +2,6 @@ --plugins=plugin/trailing_comma,disable_ternary --ignore-files=Gemfile --ignore-files=app/* ---ignore-files=config/* --ignore-files=db/* --ignore-files=lib/* --ignore-files=spec/* diff --git a/config/application.rb b/config/application.rb index 1c69f3d99c..0c31d3a327 100644 --- a/config/application.rb +++ b/config/application.rb @@ -5,12 +5,12 @@ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.7.0") exit 1 end -require File.expand_path('../boot', __FILE__) -require 'active_record/railtie' -require 'action_controller/railtie' -require 'action_view/railtie' -require 'action_mailer/railtie' -require 'sprockets/railtie' +require File.expand_path("../boot", __FILE__) +require "active_record/railtie" +require "action_controller/railtie" +require "action_view/railtie" +require "action_mailer/railtie" +require "sprockets/railtie" if !Rails.env.production? recommended = File.read(".ruby-version.sample").strip @@ -20,66 +20,59 @@ if !Rails.env.production? end # Plugin related stuff -require_relative '../lib/plugin' -require_relative '../lib/discourse_event' -require_relative '../lib/discourse_plugin_registry' +require_relative "../lib/plugin" +require_relative "../lib/discourse_event" +require_relative "../lib/discourse_plugin_registry" -require_relative '../lib/plugin_gem' +require_relative "../lib/plugin_gem" # Global config -require_relative '../app/models/global_setting' +require_relative "../app/models/global_setting" GlobalSetting.configure! if GlobalSetting.load_plugins? # Support for plugins to register custom setting providers. They can do this # by having a file, `register_provider.rb` in their root that will be run # at this point. - Dir.glob(File.join(File.dirname(__FILE__), '../plugins', '*', "register_provider.rb")) do |p| + Dir.glob(File.join(File.dirname(__FILE__), "../plugins", "*", "register_provider.rb")) do |p| require p end end GlobalSetting.load_defaults -if GlobalSetting.try(:cdn_url).present? && GlobalSetting.cdn_url !~ /^https?:\/\// +if GlobalSetting.try(:cdn_url).present? && GlobalSetting.cdn_url !~ %r{^https?://} STDERR.puts "WARNING: Your CDN URL does not begin with a protocol like `https://` - this is probably not going to work" end -if ENV['SKIP_DB_AND_REDIS'] == '1' +if ENV["SKIP_DB_AND_REDIS"] == "1" GlobalSetting.skip_db = true GlobalSetting.skip_redis = true end -if !GlobalSetting.skip_db? - require 'rails_failover/active_record' -end +require "rails_failover/active_record" if !GlobalSetting.skip_db? -if !GlobalSetting.skip_redis? - require 'rails_failover/redis' -end +require "rails_failover/redis" if !GlobalSetting.skip_redis? -require 'pry-rails' if Rails.env.development? -require 'pry-byebug' if Rails.env.development? +require "pry-rails" if Rails.env.development? +require "pry-byebug" if Rails.env.development? -require 'discourse_fonts' +require "discourse_fonts" -require_relative '../lib/ember_cli' +require_relative "../lib/ember_cli" if defined?(Bundler) bundler_groups = [:default] if !Rails.env.production? - bundler_groups = bundler_groups.concat(Rails.groups( - assets: %w(development test profile) - )) + bundler_groups = bundler_groups.concat(Rails.groups(assets: %w[development test profile])) end Bundler.require(*bundler_groups) end -require_relative '../lib/require_dependency_backward_compatibility' +require_relative "../lib/require_dependency_backward_compatibility" module Discourse class Application < Rails::Application - def config.database_configuration if Rails.env.production? GlobalSetting.database_config @@ -91,18 +84,23 @@ module Discourse # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. - require 'discourse' - require 'js_locale_helper' + require "discourse" + require "js_locale_helper" # tiny file needed by site settings - require 'highlight_js' + require "highlight_js" config.load_defaults 6.1 config.active_record.cache_versioning = false # our custom cache class doesn’t support this config.action_controller.forgery_protection_origin_check = false config.active_record.belongs_to_required_by_default = false config.active_record.legacy_connection_handling = true - config.active_record.yaml_column_permitted_classes = [Hash, HashWithIndifferentAccess, Time, Symbol] + config.active_record.yaml_column_permitted_classes = [ + Hash, + HashWithIndifferentAccess, + Time, + Symbol, + ] # we skip it cause we configure it in the initializer # the railtie for message_bus would insert it in the @@ -111,7 +109,8 @@ module Discourse config.skip_multisite_middleware = true config.skip_rails_failover_active_record_middleware = true - multisite_config_path = ENV['DISCOURSE_MULTISITE_CONFIG_PATH'] || GlobalSetting.multisite_config_path + multisite_config_path = + ENV["DISCOURSE_MULTISITE_CONFIG_PATH"] || GlobalSetting.multisite_config_path config.multisite_config_path = File.absolute_path(multisite_config_path, Rails.root) # Custom directories with classes and modules you want to be autoloadable. @@ -129,14 +128,14 @@ module Discourse # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. - config.time_zone = 'UTC' + config.time_zone = "UTC" # auto-load locales in plugins # NOTE: we load both client & server locales since some might be used by PrettyText config.i18n.load_path += Dir["#{Rails.root}/plugins/*/config/locales/*.yml"] # Configure the default encoding used in templates for Ruby 1.9. - config.encoding = 'utf-8' + config.encoding = "utf-8" # see: http://stackoverflow.com/questions/11894180/how-does-one-correctly-add-custom-sql-dml-in-migrations/11894420#11894420 config.active_record.schema_format = :sql @@ -145,7 +144,7 @@ module Discourse config.active_record.use_schema_cache_dump = false # per https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet - config.pbkdf2_iterations = 64000 + config.pbkdf2_iterations = 64_000 config.pbkdf2_algorithm = "sha256" # rack lock is nothing but trouble, get rid of it @@ -160,32 +159,39 @@ module Discourse # supports etags (post 1.7) config.middleware.delete Rack::ETag - if !(Rails.env.development? || ENV['SKIP_ENFORCE_HOSTNAME'] == "1") - require 'middleware/enforce_hostname' + if !(Rails.env.development? || ENV["SKIP_ENFORCE_HOSTNAME"] == "1") + require "middleware/enforce_hostname" config.middleware.insert_after Rack::MethodOverride, Middleware::EnforceHostname end - require 'content_security_policy/middleware' - config.middleware.swap ActionDispatch::ContentSecurityPolicy::Middleware, ContentSecurityPolicy::Middleware + require "content_security_policy/middleware" + config.middleware.swap ActionDispatch::ContentSecurityPolicy::Middleware, + ContentSecurityPolicy::Middleware - require 'middleware/discourse_public_exceptions' + require "middleware/discourse_public_exceptions" config.exceptions_app = Middleware::DiscoursePublicExceptions.new(Rails.public_path) - require 'discourse_js_processor' - require 'discourse_sourcemapping_url_processor' + require "discourse_js_processor" + require "discourse_sourcemapping_url_processor" - Sprockets.register_mime_type 'application/javascript', extensions: ['.js', '.es6', '.js.es6'], charset: :unicode - Sprockets.register_postprocessor 'application/javascript', DiscourseJsProcessor + Sprockets.register_mime_type "application/javascript", + extensions: %w[.js .es6 .js.es6], + charset: :unicode + Sprockets.register_postprocessor "application/javascript", DiscourseJsProcessor Discourse::Application.initializer :prepend_ember_assets do |app| # Needs to be in its own initializer so it runs after the append_assets_path initializer defined by Sprockets - app.config.assets.paths.unshift "#{app.config.root}/app/assets/javascripts/discourse/dist/assets" - Sprockets.unregister_postprocessor 'application/javascript', Sprockets::Rails::SourcemappingUrlProcessor - Sprockets.register_postprocessor 'application/javascript', DiscourseSourcemappingUrlProcessor + app + .config + .assets + .paths.unshift "#{app.config.root}/app/assets/javascripts/discourse/dist/assets" + Sprockets.unregister_postprocessor "application/javascript", + Sprockets::Rails::SourcemappingUrlProcessor + Sprockets.register_postprocessor "application/javascript", DiscourseSourcemappingUrlProcessor end - require 'discourse_redis' - require 'logster/redis_store' + require "discourse_redis" + require "logster/redis_store" # Use redis for our cache config.cache_store = DiscourseRedis.new_redis_store Discourse.redis = DiscourseRedis.new @@ -198,7 +204,7 @@ module Discourse # our setup does not use rack cache and instead defers to nginx config.action_dispatch.rack_cache = nil - require 'auth' + require "auth" if GlobalSetting.relative_url_root.present? config.relative_url_root = GlobalSetting.relative_url_root @@ -207,38 +213,28 @@ module Discourse if Rails.env.test? && GlobalSetting.load_plugins? Discourse.activate_plugins! elsif GlobalSetting.load_plugins? - Plugin.initialization_guard do - Discourse.activate_plugins! - end + Plugin.initialization_guard { Discourse.activate_plugins! } end # Use discourse-fonts gem to symlink fonts and generate .scss file - fonts_path = File.join(config.root, 'public/fonts') + fonts_path = File.join(config.root, "public/fonts") Discourse::Utils.atomic_ln_s(DiscourseFonts.path_for_fonts, fonts_path) - require 'stylesheet/manager' - require 'svg_sprite' + require "stylesheet/manager" + require "svg_sprite" config.after_initialize do # Load plugins - Plugin.initialization_guard do - Discourse.plugins.each(&:notify_after_initialize) - end + Plugin.initialization_guard { Discourse.plugins.each(&:notify_after_initialize) } # we got to clear the pool in case plugins connect ActiveRecord::Base.connection_handler.clear_active_connections! end - if ENV['RBTRACE'] == "1" - require 'rbtrace' - end + require "rbtrace" if ENV["RBTRACE"] == "1" - if ENV['RAILS_QUERY_LOG_TAGS'] == "1" - config.active_record.query_log_tags_enabled = true - end + config.active_record.query_log_tags_enabled = true if ENV["RAILS_QUERY_LOG_TAGS"] == "1" - config.generators do |g| - g.test_framework :rspec, fixture: false - end + config.generators { |g| g.test_framework :rspec, fixture: false } end end diff --git a/config/boot.rb b/config/boot.rb index 6866506f24..bf5d61d56d 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,48 +1,49 @@ # frozen_string_literal: true -if ENV['DISCOURSE_DUMP_HEAP'] == "1" - require 'objspace' +if ENV["DISCOURSE_DUMP_HEAP"] == "1" + require "objspace" ObjectSpace.trace_object_allocations_start end -require 'rubygems' +require "rubygems" # Set up gems listed in the Gemfile. -ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__) -require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) +require "bundler/setup" if File.exist?(ENV["BUNDLE_GEMFILE"]) -if (ENV['DISABLE_BOOTSNAP'] != '1') +if (ENV["DISABLE_BOOTSNAP"] != "1") begin - require 'bootsnap' + require "bootsnap" rescue LoadError # not a strong requirement end - if defined? Bootsnap + if defined?(Bootsnap) Bootsnap.setup( - cache_dir: 'tmp/cache', # Path to your cache - load_path_cache: true, # Should we optimize the LOAD_PATH with a cache? - compile_cache_iseq: true, # Should compile Ruby code into ISeq cache? - compile_cache_yaml: false # Skip YAML cache for now, cause we were seeing issues with it + cache_dir: "tmp/cache", # Path to your cache + load_path_cache: true, # Should we optimize the LOAD_PATH with a cache? + compile_cache_iseq: true, # Should compile Ruby code into ISeq cache? + compile_cache_yaml: false, # Skip YAML cache for now, cause we were seeing issues with it ) end end # Parallel spec system -if ENV['RAILS_ENV'] == "test" && ENV['TEST_ENV_NUMBER'] - if ENV['TEST_ENV_NUMBER'] == '' +if ENV["RAILS_ENV"] == "test" && ENV["TEST_ENV_NUMBER"] + if ENV["TEST_ENV_NUMBER"] == "" n = 1 else - n = ENV['TEST_ENV_NUMBER'].to_i + n = ENV["TEST_ENV_NUMBER"].to_i end - port = 10000 + n + port = 10_000 + n STDERR.puts "Setting up parallel test mode - starting Redis #{n} on port #{port}" `rm -rf tmp/test_data_#{n} && mkdir -p tmp/test_data_#{n}/redis` - pid = Process.spawn("redis-server --dir tmp/test_data_#{n}/redis --port #{port}", out: "/dev/null") + pid = + Process.spawn("redis-server --dir tmp/test_data_#{n}/redis --port #{port}", out: "/dev/null") ENV["DISCOURSE_REDIS_PORT"] = port.to_s ENV["RAILS_DB"] = "discourse_test_#{n}" diff --git a/config/cloud/cloud66/files/production.rb b/config/cloud/cloud66/files/production.rb index f527db5d6a..2814996b68 100644 --- a/config/cloud/cloud66/files/production.rb +++ b/config/cloud/cloud66/files/production.rb @@ -7,7 +7,7 @@ Discourse::Application.configure do config.cache_classes = true # Full error reports are disabled and caching is turned on - config.consider_all_requests_local = false + config.consider_all_requests_local = false config.action_controller.perform_caching = true # Disable Rails's static asset server (Apache or nginx will already do this) @@ -23,19 +23,20 @@ Discourse::Application.configure do config.assets.digest = true # Specifies the header that your server uses for sending files - config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx + config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for nginx # you may use other configuration here for mail eg: sendgrid config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { - address: ENV['SMTP_ADDRESS'], - port: ENV['SMTP_PORT'], - domain: ENV['SMTP_DOMAIN'], - user_name: ENV['SMTP_USERNAME'], - password: ENV['SMTP_PASSWORD'], - authentication: 'plain', - enable_starttls_auto: true } + address: ENV["SMTP_ADDRESS"], + port: ENV["SMTP_PORT"], + domain: ENV["SMTP_DOMAIN"], + user_name: ENV["SMTP_USERNAME"], + password: ENV["SMTP_PASSWORD"], + authentication: "plain", + enable_starttls_auto: true, + } #config.action_mailer.delivery_method = :sendmail #config.action_mailer.sendmail_settings = {arguments: '-i'} @@ -59,5 +60,4 @@ Discourse::Application.configure do # Discourse strongly recommend you use a CDN. # For origin pull cdns all you need to do is register an account and configure # config.action_controller.asset_host = "http://YOUR_CDN_HERE" - end diff --git a/config/environment.rb b/config/environment.rb index 7bb5d95ae9..90ea3b4fa9 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -7,6 +7,7 @@ require_relative "application" Rails.application.initialize! # When in "dev" mode, ensure we won't be sending any emails -if Rails.env.development? && ActionMailer::Base.smtp_settings.slice(:address, :port) != { address: "localhost", port: 1025 } +if Rails.env.development? && + ActionMailer::Base.smtp_settings.slice(:address, :port) != { address: "localhost", port: 1025 } fail "In development mode, you should be using mailhog otherwise you might end up sending thousands of digest emails" end diff --git a/config/environments/development.rb b/config/environments/development.rb index 3e6496f1a9..62b3dd2843 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -16,7 +16,7 @@ Discourse::Application.configure do config.active_record.use_schema_cache_dump = true # Show full error reports and disable caching - config.consider_all_requests_local = true + config.consider_all_requests_local = true config.action_controller.perform_caching = false config.action_controller.asset_host = GlobalSetting.cdn_url @@ -32,27 +32,23 @@ Discourse::Application.configure do config.assets.debug = false - config.public_file_server.headers = { - 'Access-Control-Allow-Origin' => '*' - } + config.public_file_server.headers = { "Access-Control-Allow-Origin" => "*" } # Raise an error on page load if there are pending migrations config.active_record.migration_error = :page_load - config.watchable_dirs['lib'] = [:rb] + config.watchable_dirs["lib"] = [:rb] # we recommend you use mailhog https://github.com/mailhog/MailHog config.action_mailer.smtp_settings = { address: "localhost", port: 1025 } config.action_mailer.raise_delivery_errors = true - config.log_level = ENV['DISCOURSE_DEV_LOG_LEVEL'] if ENV['DISCOURSE_DEV_LOG_LEVEL'] + config.log_level = ENV["DISCOURSE_DEV_LOG_LEVEL"] if ENV["DISCOURSE_DEV_LOG_LEVEL"] - if ENV['RAILS_VERBOSE_QUERY_LOGS'] == "1" - config.active_record.verbose_query_logs = true - end + config.active_record.verbose_query_logs = true if ENV["RAILS_VERBOSE_QUERY_LOGS"] == "1" if defined?(BetterErrors) - BetterErrors::Middleware.allow_ip! ENV['TRUSTED_IP'] if ENV['TRUSTED_IP'] + BetterErrors::Middleware.allow_ip! ENV["TRUSTED_IP"] if ENV["TRUSTED_IP"] if defined?(Unicorn) && ENV["UNICORN_WORKERS"].to_i != 1 # BetterErrors doesn't work with multiple unicorn workers. Disable it to avoid confusion @@ -60,51 +56,44 @@ Discourse::Application.configure do end end - if !ENV["DISABLE_MINI_PROFILER"] - config.load_mini_profiler = true - end + config.load_mini_profiler = true if !ENV["DISABLE_MINI_PROFILER"] - if hosts = ENV['DISCOURSE_DEV_HOSTS'] + if hosts = ENV["DISCOURSE_DEV_HOSTS"] Discourse.deprecate("DISCOURSE_DEV_HOSTS is deprecated. Use RAILS_DEVELOPMENT_HOSTS instead.") config.hosts.concat(hosts.split(",")) end - require 'middleware/turbo_dev' + require "middleware/turbo_dev" config.middleware.insert 0, Middleware::TurboDev - require 'middleware/missing_avatars' + require "middleware/missing_avatars" config.middleware.insert 1, Middleware::MissingAvatars config.enable_anon_caching = false - if RUBY_ENGINE == "ruby" - require 'rbtrace' - end + require "rbtrace" if RUBY_ENGINE == "ruby" if emails = GlobalSetting.developer_emails config.developer_emails = emails.split(",").map(&:downcase).map(&:strip) end - if ENV["DISCOURSE_SKIP_CSS_WATCHER"] != "1" && (defined?(Rails::Server) || defined?(Puma) || defined?(Unicorn)) - require 'stylesheet/watcher' + if ENV["DISCOURSE_SKIP_CSS_WATCHER"] != "1" && + (defined?(Rails::Server) || defined?(Puma) || defined?(Unicorn)) + require "stylesheet/watcher" STDERR.puts "Starting CSS change watcher" @watcher = Stylesheet::Watcher.watch end config.after_initialize do - if ENV["RAILS_COLORIZE_LOGGING"] == "1" - config.colorize_logging = true - end + config.colorize_logging = true if ENV["RAILS_COLORIZE_LOGGING"] == "1" if ENV["RAILS_VERBOSE_QUERY_LOGS"] == "1" ActiveRecord::LogSubscriber.backtrace_cleaner.add_silencer do |line| - line =~ /lib\/freedom_patches/ + line =~ %r{lib/freedom_patches} end end - if ENV["RAILS_DISABLE_ACTIVERECORD_LOGS"] == "1" - ActiveRecord::Base.logger = nil - end + ActiveRecord::Base.logger = nil if ENV["RAILS_DISABLE_ACTIVERECORD_LOGS"] == "1" - if ENV['BULLET'] + if ENV["BULLET"] Bullet.enable = true Bullet.rails_logger = true end diff --git a/config/environments/production.rb b/config/environments/production.rb index f5a5df8ff5..5b6cd92456 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -8,7 +8,7 @@ Discourse::Application.configure do config.eager_load = true # Full error reports are disabled and caching is turned on - config.consider_all_requests_local = false + config.consider_all_requests_local = false config.action_controller.perform_caching = true # Disable Rails's static asset server (Apache or nginx will already do this) @@ -34,19 +34,19 @@ Discourse::Application.configure do authentication: GlobalSetting.smtp_authentication, enable_starttls_auto: GlobalSetting.smtp_enable_start_tls, open_timeout: GlobalSetting.smtp_open_timeout, - read_timeout: GlobalSetting.smtp_read_timeout + read_timeout: GlobalSetting.smtp_read_timeout, } - settings[:openssl_verify_mode] = GlobalSetting.smtp_openssl_verify_mode if GlobalSetting.smtp_openssl_verify_mode + settings[ + :openssl_verify_mode + ] = GlobalSetting.smtp_openssl_verify_mode if GlobalSetting.smtp_openssl_verify_mode - if GlobalSetting.smtp_force_tls - settings[:tls] = true - end + settings[:tls] = true if GlobalSetting.smtp_force_tls config.action_mailer.smtp_settings = settings.compact else config.action_mailer.delivery_method = :sendmail - config.action_mailer.sendmail_settings = { arguments: '-i' } + config.action_mailer.sendmail_settings = { arguments: "-i" } end # Send deprecation notices to registered listeners diff --git a/config/environments/profile.rb b/config/environments/profile.rb index d8f55a44f5..05e526c573 100644 --- a/config/environments/profile.rb +++ b/config/environments/profile.rb @@ -11,7 +11,7 @@ Discourse::Application.configure do config.log_level = :info # Full error reports are disabled and caching is turned on - config.consider_all_requests_local = false + config.consider_all_requests_local = false config.action_controller.perform_caching = true # in profile mode we serve static assets @@ -27,7 +27,7 @@ Discourse::Application.configure do config.assets.digest = true # Specifies the header that your server uses for sending files - config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx + config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for nginx # we recommend you use mailhog https://github.com/mailhog/MailHog config.action_mailer.smtp_settings = { address: "localhost", port: 1025 } @@ -39,9 +39,7 @@ Discourse::Application.configure do config.load_mini_profiler = false # we don't need full logster support, but need to keep it working - config.after_initialize do - Logster.logger = Rails.logger - end + config.after_initialize { Logster.logger = Rails.logger } # for profiling with perftools # config.middleware.use ::Rack::PerftoolsProfiler, default_printer: 'gif' diff --git a/config/environments/test.rb b/config/environments/test.rb index 85b20535d5..e80e4f7a64 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -46,21 +46,22 @@ Discourse::Application.configure do config.eager_load = false - if ENV['RAILS_ENABLE_TEST_LOG'] + if ENV["RAILS_ENABLE_TEST_LOG"] config.logger = Logger.new(STDOUT) - config.log_level = ENV['RAILS_TEST_LOG_LEVEL'].present? ? ENV['RAILS_TEST_LOG_LEVEL'].to_sym : :info + config.log_level = + ENV["RAILS_TEST_LOG_LEVEL"].present? ? ENV["RAILS_TEST_LOG_LEVEL"].to_sym : :info else config.logger = Logger.new(nil) config.log_level = :fatal end - if defined? RspecErrorTracker + if defined?(RspecErrorTracker) config.middleware.insert_after ActionDispatch::Flash, RspecErrorTracker end config.after_initialize do SiteSetting.defaults.tap do |s| - s.set_regardless_of_locale(:s3_upload_bucket, 'bucket') + s.set_regardless_of_locale(:s3_upload_bucket, "bucket") s.set_regardless_of_locale(:min_post_length, 5) s.set_regardless_of_locale(:min_first_post_length, 5) s.set_regardless_of_locale(:min_personal_message_post_length, 10) @@ -73,7 +74,7 @@ Discourse::Application.configure do s.set_regardless_of_locale(:allow_uncategorized_topics, true) # disable plugins - if ENV['LOAD_PLUGINS'] == '1' + if ENV["LOAD_PLUGINS"] == "1" s.set_regardless_of_locale(:discourse_narrative_bot_enabled, false) end end diff --git a/config/initializers/000-development_reload_warnings.rb b/config/initializers/000-development_reload_warnings.rb index ce0260a8ee..bc1f74718f 100644 --- a/config/initializers/000-development_reload_warnings.rb +++ b/config/initializers/000-development_reload_warnings.rb @@ -8,25 +8,35 @@ if Rails.env.development? && !Rails.configuration.cache_classes && Discourse.run *Dir["#{Rails.root}/app/*"].reject { |path| path.end_with? "/assets" }, "#{Rails.root}/config", "#{Rails.root}/lib", - "#{Rails.root}/plugins" + "#{Rails.root}/plugins", ] - Listen.to(*paths, only: /\.rb$/) do |modified, added, removed| - supervisor_pid = UNICORN_DEV_SUPERVISOR_PID - auto_restart = supervisor_pid && ENV["AUTO_RESTART"] != "0" + Listen + .to(*paths, only: /\.rb$/) do |modified, added, removed| + supervisor_pid = UNICORN_DEV_SUPERVISOR_PID + auto_restart = supervisor_pid && ENV["AUTO_RESTART"] != "0" - files = modified + added + removed + files = modified + added + removed - not_autoloaded = files.filter_map do |file| - autoloaded = Rails.autoloaders.main.autoloads.key? file - Pathname.new(file).relative_path_from(Rails.root) if !autoloaded + not_autoloaded = + files.filter_map do |file| + autoloaded = Rails.autoloaders.main.autoloads.key? file + Pathname.new(file).relative_path_from(Rails.root) if !autoloaded + end + + if not_autoloaded.length > 0 + message = + ( + if auto_restart + "Restarting server..." + else + "Server restart required. Automate this by setting AUTO_RESTART=1." + end + ) + STDERR.puts "[DEV]: Edited files which are not autoloaded. #{message}" + STDERR.puts not_autoloaded.map { |path| "- #{path}".indent(7) }.join("\n") + Process.kill("USR2", supervisor_pid) if auto_restart + end end - - if not_autoloaded.length > 0 - message = auto_restart ? "Restarting server..." : "Server restart required. Automate this by setting AUTO_RESTART=1." - STDERR.puts "[DEV]: Edited files which are not autoloaded. #{message}" - STDERR.puts not_autoloaded.map { |path| "- #{path}".indent(7) }.join("\n") - Process.kill("USR2", supervisor_pid) if auto_restart - end - end.start + .start end diff --git a/config/initializers/000-mini_sql.rb b/config/initializers/000-mini_sql.rb index 15bbb7de06..53a9b7aa94 100644 --- a/config/initializers/000-mini_sql.rb +++ b/config/initializers/000-mini_sql.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true -require 'mini_sql_multisite_connection' +require "mini_sql_multisite_connection" ::DB = MiniSqlMultisiteConnection.instance diff --git a/config/initializers/000-post_migration.rb b/config/initializers/000-post_migration.rb index 31d5370e97..2029d9a9cd 100644 --- a/config/initializers/000-post_migration.rb +++ b/config/initializers/000-post_migration.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true unless Discourse.skip_post_deployment_migrations? - ActiveRecord::Tasks::DatabaseTasks.migrations_paths << Rails.root.join( - Discourse::DB_POST_MIGRATE_PATH - ).to_s + ActiveRecord::Tasks::DatabaseTasks.migrations_paths << Rails + .root + .join(Discourse::DB_POST_MIGRATE_PATH) + .to_s end diff --git a/config/initializers/000-trace_pg_connections.rb b/config/initializers/000-trace_pg_connections.rb index ccfa760008..6a04daf399 100644 --- a/config/initializers/000-trace_pg_connections.rb +++ b/config/initializers/000-trace_pg_connections.rb @@ -15,31 +15,32 @@ # Warning: this could create some very large files! if ENV["TRACE_PG_CONNECTIONS"] - PG::Connection.prepend(Module.new do - TRACE_DIR = "tmp/pgtrace" + PG::Connection.prepend( + Module.new do + TRACE_DIR = "tmp/pgtrace" - def initialize(*args) - super(*args).tap do - next if ENV["TRACE_PG_CONNECTIONS"] == "SIDEKIQ" && !Sidekiq.server? - FileUtils.mkdir_p(TRACE_DIR) - @trace_filename = "#{TRACE_DIR}/#{Process.pid}_#{self.object_id}.txt" - trace File.new(@trace_filename, "w") + def initialize(*args) + super(*args).tap do + next if ENV["TRACE_PG_CONNECTIONS"] == "SIDEKIQ" && !Sidekiq.server? + FileUtils.mkdir_p(TRACE_DIR) + @trace_filename = "#{TRACE_DIR}/#{Process.pid}_#{self.object_id}.txt" + trace File.new(@trace_filename, "w") + end + @access_log_mutex = Mutex.new + @accessor_thread = nil end - @access_log_mutex = Mutex.new - @accessor_thread = nil - end - def close - super.tap do - next if ENV["TRACE_PG_CONNECTIONS"] == "SIDEKIQ" && !Sidekiq.server? - File.delete(@trace_filename) + def close + super.tap do + next if ENV["TRACE_PG_CONNECTIONS"] == "SIDEKIQ" && !Sidekiq.server? + File.delete(@trace_filename) + end end - end - def log_access(&blk) - @access_log_mutex.synchronize do - if !@accessor_thread.nil? - Rails.logger.error <<~TEXT + def log_access(&blk) + @access_log_mutex.synchronize do + if !@accessor_thread.nil? + Rails.logger.error <<~TEXT PG Clash: A connection is being accessed from two locations #{@accessor_thread} was using the connection. Backtrace: @@ -51,37 +52,38 @@ if ENV["TRACE_PG_CONNECTIONS"] #{Thread.current&.backtrace&.join("\n")} TEXT - if ENV["ON_PG_CLASH"] == "byebug" - require "byebug" - byebug # rubocop:disable Lint/Debugger + if ENV["ON_PG_CLASH"] == "byebug" + require "byebug" + byebug # rubocop:disable Lint/Debugger + end end + @accessor_thread = Thread.current end - @accessor_thread = Thread.current + yield + ensure + @access_log_mutex.synchronize { @accessor_thread = nil } end - yield - ensure - @access_log_mutex.synchronize do - @accessor_thread = nil - end - end - - end) + end, + ) class PG::Connection - LOG_ACCESS_METHODS = [:exec, :sync_exec, :async_exec, - :sync_exec_params, :async_exec_params, - :sync_prepare, :async_prepare, - :sync_exec_prepared, :async_exec_prepared, - ] + LOG_ACCESS_METHODS = %i[ + exec + sync_exec + async_exec + sync_exec_params + async_exec_params + sync_prepare + async_prepare + sync_exec_prepared + async_exec_prepared + ] LOG_ACCESS_METHODS.each do |method| new_method = "#{method}_without_logging".to_sym alias_method new_method, method - define_method(method) do |*args, &blk| - log_access { send(new_method, *args, &blk) } - end + define_method(method) { |*args, &blk| log_access { send(new_method, *args, &blk) } } end end - end diff --git a/config/initializers/000-zeitwerk.rb b/config/initializers/000-zeitwerk.rb index 130ba2d5c7..13131cbb01 100644 --- a/config/initializers/000-zeitwerk.rb +++ b/config/initializers/000-zeitwerk.rb @@ -11,7 +11,7 @@ module DiscourseInflector def self.camelize(basename, abspath) return basename.camelize if abspath.ends_with?("onceoff.rb") - return 'Jobs' if abspath.ends_with?("jobs/base.rb") + return "Jobs" if abspath.ends_with?("jobs/base.rb") @overrides[basename] || basename.camelize end @@ -26,27 +26,29 @@ Rails.autoloaders.each do |autoloader| # We have filenames that do not follow Zeitwerk's camelization convention. Maintain an inflections for these files # for now until we decide to fix them one day. autoloader.inflector.inflect( - 'canonical_url' => 'CanonicalURL', - 'clean_up_unmatched_ips' => 'CleanUpUnmatchedIPs', - 'homepage_constraint' => 'HomePageConstraint', - 'ip_addr' => 'IPAddr', - 'onpdiff' => 'ONPDiff', - 'pop3_polling_enabled_setting_validator' => 'POP3PollingEnabledSettingValidator', - 'version' => 'Discourse', - 'onceoff' => 'Jobs', - 'regular' => 'Jobs', - 'scheduled' => 'Jobs', - 'google_oauth2_authenticator' => 'GoogleOAuth2Authenticator', - 'omniauth_strategies' => 'OmniAuthStrategies', - 'csrf_token_verifier' => 'CSRFTokenVerifier', - 'html' => 'HTML', - 'json' => 'JSON', - 'ssrf_detector' => 'SSRFDetector', - 'http' => 'HTTP', + "canonical_url" => "CanonicalURL", + "clean_up_unmatched_ips" => "CleanUpUnmatchedIPs", + "homepage_constraint" => "HomePageConstraint", + "ip_addr" => "IPAddr", + "onpdiff" => "ONPDiff", + "pop3_polling_enabled_setting_validator" => "POP3PollingEnabledSettingValidator", + "version" => "Discourse", + "onceoff" => "Jobs", + "regular" => "Jobs", + "scheduled" => "Jobs", + "google_oauth2_authenticator" => "GoogleOAuth2Authenticator", + "omniauth_strategies" => "OmniAuthStrategies", + "csrf_token_verifier" => "CSRFTokenVerifier", + "html" => "HTML", + "json" => "JSON", + "ssrf_detector" => "SSRFDetector", + "http" => "HTTP", ) end -Rails.autoloaders.main.ignore("lib/tasks", - "lib/generators", - "lib/freedom_patches", - "lib/i18n/backend", - "lib/unicorn_logstash_patch.rb") +Rails.autoloaders.main.ignore( + "lib/tasks", + "lib/generators", + "lib/freedom_patches", + "lib/i18n/backend", + "lib/unicorn_logstash_patch.rb", +) diff --git a/config/initializers/001-redis.rb b/config/initializers/001-redis.rb index d61f467b28..1384e44467 100644 --- a/config/initializers/001-redis.rb +++ b/config/initializers/001-redis.rb @@ -1,12 +1,12 @@ # frozen_string_literal: true -if Rails.env.development? && ENV['DISCOURSE_FLUSH_REDIS'] +if Rails.env.development? && ENV["DISCOURSE_FLUSH_REDIS"] puts "Flushing redis (development mode)" Discourse.redis.flushdb end begin - if Gem::Version.new(Discourse.redis.info['redis_version']) < Gem::Version.new("6.2.0") + if Gem::Version.new(Discourse.redis.info["redis_version"]) < Gem::Version.new("6.2.0") STDERR.puts "Discourse requires Redis 6.2.0 or up" exit 1 end diff --git a/config/initializers/002-rails_failover.rb b/config/initializers/002-rails_failover.rb index 3f1463fdd8..276e6d951b 100644 --- a/config/initializers/002-rails_failover.rb +++ b/config/initializers/002-rails_failover.rb @@ -14,16 +14,12 @@ if defined?(RailsFailover::Redis) Discourse.request_refresh! MessageBus.keepalive_interval = message_bus_keepalive_interval - ObjectSpace.each_object(DistributedCache) do |cache| - cache.clear - end + ObjectSpace.each_object(DistributedCache) { |cache| cache.clear } SiteSetting.refresh! end - if Rails.logger.respond_to? :chained - RailsFailover::Redis.logger = Rails.logger.chained.first - end + RailsFailover::Redis.logger = Rails.logger.chained.first if Rails.logger.respond_to? :chained end if defined?(RailsFailover::ActiveRecord) @@ -73,10 +69,7 @@ if defined?(RailsFailover::ActiveRecord) end RailsFailover::ActiveRecord.register_force_reading_role_callback do - Discourse.redis.exists?( - Discourse::PG_READONLY_MODE_KEY, - Discourse::PG_FORCE_READONLY_MODE_KEY - ) + Discourse.redis.exists?(Discourse::PG_READONLY_MODE_KEY, Discourse::PG_FORCE_READONLY_MODE_KEY) rescue => e if !e.is_a?(Redis::CannotConnectError) Rails.logger.warn "#{e.class} #{e.message}: #{e.backtrace.join("\n")}" diff --git a/config/initializers/004-message_bus.rb b/config/initializers/004-message_bus.rb index 46a531e3f7..2fa74be763 100644 --- a/config/initializers/004-message_bus.rb +++ b/config/initializers/004-message_bus.rb @@ -30,7 +30,8 @@ def setup_message_bus_env(env) extra_headers = { "Access-Control-Allow-Origin" => Discourse.base_url_no_prefix, "Access-Control-Allow-Methods" => "GET, POST", - "Access-Control-Allow-Headers" => "X-SILENCE-LOGGER, X-Shared-Session-Key, Dont-Chunk, Discourse-Present", + "Access-Control-Allow-Headers" => + "X-SILENCE-LOGGER, X-Shared-Session-Key, Dont-Chunk, Discourse-Present", "Access-Control-Max-Age" => "7200", } @@ -40,7 +41,7 @@ def setup_message_bus_env(env) rescue Discourse::InvalidAccess => e # this is bad we need to remove the cookie if e.opts[:delete_cookie].present? - extra_headers['Set-Cookie'] = '_t=del; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT' + extra_headers["Set-Cookie"] = "_t=del; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT" end rescue => e Discourse.warn_exception(e, message: "Unexpected error in Message Bus", env: env) @@ -51,23 +52,22 @@ def setup_message_bus_env(env) raise Discourse::InvalidAccess if !user_id && SiteSetting.login_required is_admin = !!(user && user.admin?) - group_ids = if is_admin - # special rule, admin is allowed access to all groups - Group.pluck(:id) - elsif user - user.groups.pluck('groups.id') - end + group_ids = + if is_admin + # special rule, admin is allowed access to all groups + Group.pluck(:id) + elsif user + user.groups.pluck("groups.id") + end - if env[Auth::DefaultCurrentUserProvider::BAD_TOKEN] - extra_headers['Discourse-Logged-Out'] = '1' - end + extra_headers["Discourse-Logged-Out"] = "1" if env[Auth::DefaultCurrentUserProvider::BAD_TOKEN] hash = { extra_headers: extra_headers, user_id: user_id, group_ids: group_ids, is_admin: is_admin, - site_id: RailsMultisite::ConnectionManagement.current_db + site_id: RailsMultisite::ConnectionManagement.current_db, } env["__mb"] = hash end @@ -99,7 +99,7 @@ MessageBus.on_middleware_error do |env, e| if Discourse::InvalidAccess === e [403, {}, ["Invalid Access"]] elsif RateLimiter::LimitExceeded === e - [429, { 'Retry-After' => e.available_in.to_s }, [e.description]] + [429, { "Retry-After" => e.available_in.to_s }, [e.description]] end end @@ -119,8 +119,9 @@ end MessageBus.backend_instance.max_backlog_size = GlobalSetting.message_bus_max_backlog_size MessageBus.backend_instance.clear_every = GlobalSetting.message_bus_clear_every -MessageBus.long_polling_enabled = GlobalSetting.enable_long_polling.nil? ? true : GlobalSetting.enable_long_polling -MessageBus.long_polling_interval = GlobalSetting.long_polling_interval || 25000 +MessageBus.long_polling_enabled = + GlobalSetting.enable_long_polling.nil? ? true : GlobalSetting.enable_long_polling +MessageBus.long_polling_interval = GlobalSetting.long_polling_interval || 25_000 if Rails.env == "test" || $0 =~ /rake$/ # disable keepalive in testing diff --git a/config/initializers/005-site_settings.rb b/config/initializers/005-site_settings.rb index 9286affcbf..c05c4709f7 100644 --- a/config/initializers/005-site_settings.rb +++ b/config/initializers/005-site_settings.rb @@ -6,7 +6,7 @@ Discourse.git_version if GlobalSetting.skip_redis? - require 'site_settings/local_process_provider' + require "site_settings/local_process_provider" Rails.cache = Discourse.cache Rails.application.config.to_prepare do SiteSetting.provider = SiteSettings::LocalProcessProvider.new @@ -19,7 +19,8 @@ Rails.application.config.to_prepare do begin SiteSetting.refresh! - unless String === SiteSetting.push_api_secret_key && SiteSetting.push_api_secret_key.length == 32 + unless String === SiteSetting.push_api_secret_key && + SiteSetting.push_api_secret_key.length == 32 SiteSetting.push_api_secret_key = SecureRandom.hex end rescue ActiveRecord::StatementInvalid diff --git a/config/initializers/006-ensure_login_hint.rb b/config/initializers/006-ensure_login_hint.rb index 06debac756..830ed10d7f 100644 --- a/config/initializers/006-ensure_login_hint.rb +++ b/config/initializers/006-ensure_login_hint.rb @@ -5,17 +5,15 @@ return if GlobalSetting.skip_db? Rails.application.config.to_prepare do # Some sanity checking so we don't count on an unindexed column on boot begin - if ActiveRecord::Base.connection.table_exists?(:users) && - User.limit(20).count < 20 && - User.where(admin: true).human_users.count == 0 - + if ActiveRecord::Base.connection.table_exists?(:users) && User.limit(20).count < 20 && + User.where(admin: true).human_users.count == 0 notice = if GlobalSetting.developer_emails.blank? "Congratulations, you installed Discourse! Unfortunately, no administrator emails were defined during setup, so finalizing the configuration may be difficult." else emails = GlobalSetting.developer_emails.split(",") if emails.length > 1 - emails = emails[0..-2].join(', ') << " or #{emails[-1]} " + emails = emails[0..-2].join(", ") << " or #{emails[-1]} " else emails = emails[0] end diff --git a/config/initializers/006-mini_profiler.rb b/config/initializers/006-mini_profiler.rb index 9642894352..2972a98110 100644 --- a/config/initializers/006-mini_profiler.rb +++ b/config/initializers/006-mini_profiler.rb @@ -1,12 +1,13 @@ # frozen_string_literal: true # If Mini Profiler is included via gem -if Rails.configuration.respond_to?(:load_mini_profiler) && Rails.configuration.load_mini_profiler && RUBY_ENGINE == "ruby" - require 'rack-mini-profiler' - require 'stackprof' +if Rails.configuration.respond_to?(:load_mini_profiler) && Rails.configuration.load_mini_profiler && + RUBY_ENGINE == "ruby" + require "rack-mini-profiler" + require "stackprof" begin - require 'memory_profiler' + require "memory_profiler" rescue => e STDERR.put "#{e} failed to require mini profiler" end @@ -20,55 +21,56 @@ if defined?(Rack::MiniProfiler) && defined?(Rack::MiniProfiler::Config) # raw_connection means results are not namespaced # # namespacing gets complex, cause mini profiler is in the rack chain way before multisite - Rack::MiniProfiler.config.storage_instance = Rack::MiniProfiler::RedisStore.new( - connection: DiscourseRedis.new(nil, namespace: false) - ) + Rack::MiniProfiler.config.storage_instance = + Rack::MiniProfiler::RedisStore.new(connection: DiscourseRedis.new(nil, namespace: false)) Rack::MiniProfiler.config.snapshot_every_n_requests = GlobalSetting.mini_profiler_snapshots_period - Rack::MiniProfiler.config.snapshots_transport_destination_url = GlobalSetting.mini_profiler_snapshots_transport_url - Rack::MiniProfiler.config.snapshots_transport_auth_key = GlobalSetting.mini_profiler_snapshots_transport_auth_key + Rack::MiniProfiler.config.snapshots_transport_destination_url = + GlobalSetting.mini_profiler_snapshots_transport_url + Rack::MiniProfiler.config.snapshots_transport_auth_key = + GlobalSetting.mini_profiler_snapshots_transport_auth_key Rack::MiniProfiler.config.skip_paths = [ - /^\/message-bus/, - /^\/extra-locales/, - /topics\/timings/, + %r{^/message-bus}, + %r{^/extra-locales}, + %r{topics/timings}, /assets/, - /\/user_avatar\//, - /\/letter_avatar\//, - /\/letter_avatar_proxy\//, - /\/highlight-js\//, - /\/svg-sprite\//, + %r{/user_avatar/}, + %r{/letter_avatar/}, + %r{/letter_avatar_proxy/}, + %r{/highlight-js/}, + %r{/svg-sprite/}, /qunit/, - /srv\/status/, + %r{srv/status}, /commits-widget/, - /^\/cdn_asset/, - /^\/logs/, - /^\/site_customizations/, - /^\/uploads/, - /^\/secure-media-uploads/, - /^\/secure-uploads/, - /^\/javascripts\//, - /^\/images\//, - /^\/stylesheets\//, - /^\/favicon\/proxied/, - /^\/theme-javascripts/ + %r{^/cdn_asset}, + %r{^/logs}, + %r{^/site_customizations}, + %r{^/uploads}, + %r{^/secure-media-uploads}, + %r{^/secure-uploads}, + %r{^/javascripts/}, + %r{^/images/}, + %r{^/stylesheets/}, + %r{^/favicon/proxied}, + %r{^/theme-javascripts}, ] # we DO NOT WANT mini-profiler loading on anything but real desktops and laptops # so let's rule out all handheld, tablet, and mobile devices - Rack::MiniProfiler.config.pre_authorize_cb = lambda do |env| - env['HTTP_USER_AGENT'] !~ /iPad|iPhone|Android/ - end + Rack::MiniProfiler.config.pre_authorize_cb = + lambda { |env| env["HTTP_USER_AGENT"] !~ /iPad|iPhone|Android/ } # without a user provider our results will use the ip address for namespacing # with a load balancer in front this becomes really bad as some results can # be stored associated with ip1 as the user and retrieved using ip2 causing 404s - Rack::MiniProfiler.config.user_provider = lambda do |env| - request = Rack::Request.new(env) - id = request.cookies["_t"] || request.ip || "unknown" - id = id.to_s - # some security, lets not have these tokens floating about - Digest::MD5.hexdigest(id) - end + Rack::MiniProfiler.config.user_provider = + lambda do |env| + request = Rack::Request.new(env) + id = request.cookies["_t"] || request.ip || "unknown" + id = id.to_s + # some security, lets not have these tokens floating about + Digest::MD5.hexdigest(id) + end # Cookie path should be set to the base path so Discourse's session cookie path # does not get clobbered. @@ -77,15 +79,15 @@ if defined?(Rack::MiniProfiler) && defined?(Rack::MiniProfiler::Config) Rack::MiniProfiler.config.position = "right" Rack::MiniProfiler.config.backtrace_ignores ||= [] - Rack::MiniProfiler.config.backtrace_ignores << /lib\/rack\/message_bus.rb/ - Rack::MiniProfiler.config.backtrace_ignores << /config\/initializers\/silence_logger/ - Rack::MiniProfiler.config.backtrace_ignores << /config\/initializers\/quiet_logger/ + Rack::MiniProfiler.config.backtrace_ignores << %r{lib/rack/message_bus.rb} + Rack::MiniProfiler.config.backtrace_ignores << %r{config/initializers/silence_logger} + Rack::MiniProfiler.config.backtrace_ignores << %r{config/initializers/quiet_logger} - Rack::MiniProfiler.config.backtrace_includes = [/^\/?(app|config|lib|test|plugins)/] + Rack::MiniProfiler.config.backtrace_includes = [%r{^/?(app|config|lib|test|plugins)}] Rack::MiniProfiler.config.max_traces_to_show = 100 if Rails.env.development? - Rack::MiniProfiler.counter_method(Redis::Client, :call) { 'redis' } + Rack::MiniProfiler.counter_method(Redis::Client, :call) { "redis" } # Rack::MiniProfiler.counter_method(ActiveRecord::QueryMethods, 'build_arel') # Rack::MiniProfiler.counter_method(Array, 'uniq') # require "#{Rails.root}/vendor/backports/notification" @@ -115,10 +117,11 @@ if defined?(Rack::MiniProfiler) && defined?(Rack::MiniProfiler::Config) end if ENV["PRINT_EXCEPTIONS"] - trace = TracePoint.new(:raise) do |tp| - puts tp.raised_exception - puts tp.raised_exception.backtrace.join("\n") - puts - end + trace = + TracePoint.new(:raise) do |tp| + puts tp.raised_exception + puts tp.raised_exception.backtrace.join("\n") + puts + end trace.enable end diff --git a/config/initializers/008-rack-cors.rb b/config/initializers/008-rack-cors.rb index b03fb2568f..d499a38cc8 100644 --- a/config/initializers/008-rack-cors.rb +++ b/config/initializers/008-rack-cors.rb @@ -6,18 +6,17 @@ class Discourse::Cors def initialize(app, options = nil) @app = app if GlobalSetting.enable_cors && GlobalSetting.cors_origin.present? - @global_origins = GlobalSetting.cors_origin.split(',').map { |x| x.strip.chomp('/') } + @global_origins = GlobalSetting.cors_origin.split(",").map { |x| x.strip.chomp("/") } end end def call(env) - cors_origins = @global_origins || [] - cors_origins += SiteSetting.cors_origins.split('|') if SiteSetting.cors_origins.present? + cors_origins += SiteSetting.cors_origins.split("|") if SiteSetting.cors_origins.present? cors_origins = cors_origins.presence - if env['REQUEST_METHOD'] == ('OPTIONS') && env['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] - return [200, Discourse::Cors.apply_headers(cors_origins, env, {}), []] + if env["REQUEST_METHOD"] == ("OPTIONS") && env["HTTP_ACCESS_CONTROL_REQUEST_METHOD"] + return 200, Discourse::Cors.apply_headers(cors_origins, env, {}), [] end env[Discourse::Cors::ORIGINS_ENV] = cors_origins if cors_origins @@ -31,21 +30,24 @@ class Discourse::Cors end def self.apply_headers(cors_origins, env, headers) - request_method = env['REQUEST_METHOD'] + request_method = env["REQUEST_METHOD"] - if env['REQUEST_PATH'] =~ /\/(javascripts|assets)\// && Discourse.is_cdn_request?(env, request_method) + if env["REQUEST_PATH"] =~ %r{/(javascripts|assets)/} && + Discourse.is_cdn_request?(env, request_method) Discourse.apply_cdn_headers(headers) elsif cors_origins origin = nil - if origin = env['HTTP_ORIGIN'] + if origin = env["HTTP_ORIGIN"] origin = nil unless cors_origins.include?(origin) end - headers['Access-Control-Allow-Origin'] = origin || cors_origins[0] - headers['Access-Control-Allow-Headers'] = 'Content-Type, Cache-Control, X-Requested-With, X-CSRF-Token, Discourse-Present, User-Api-Key, User-Api-Client-Id, Authorization' - headers['Access-Control-Allow-Credentials'] = 'true' - headers['Access-Control-Allow-Methods'] = 'POST, PUT, GET, OPTIONS, DELETE' - headers['Access-Control-Max-Age'] = '7200' + headers["Access-Control-Allow-Origin"] = origin || cors_origins[0] + headers[ + "Access-Control-Allow-Headers" + ] = "Content-Type, Cache-Control, X-Requested-With, X-CSRF-Token, Discourse-Present, User-Api-Key, User-Api-Client-Id, Authorization" + headers["Access-Control-Allow-Credentials"] = "true" + headers["Access-Control-Allow-Methods"] = "POST, PUT, GET, OPTIONS, DELETE" + headers["Access-Control-Max-Age"] = "7200" end headers diff --git a/config/initializers/012-web_hook_events.rb b/config/initializers/012-web_hook_events.rb index fa08cd3aea..520f4ecd6f 100644 --- a/config/initializers/012-web_hook_events.rb +++ b/config/initializers/012-web_hook_events.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true -%i( - topic_recovered -).each do |event| - DiscourseEvent.on(event) do |topic, _| - WebHook.enqueue_topic_hooks(event, topic) - end +%i[topic_recovered].each do |event| + DiscourseEvent.on(event) { |topic, _| WebHook.enqueue_topic_hooks(event, topic) } end DiscourseEvent.on(:topic_status_updated) do |topic, status| @@ -16,18 +12,12 @@ DiscourseEvent.on(:topic_created) do |topic, _, _| WebHook.enqueue_topic_hooks(:topic_created, topic) end -%i( - post_created - post_recovered -).each do |event| - DiscourseEvent.on(event) do |post, _, _| - WebHook.enqueue_post_hooks(event, post) - end +%i[post_created post_recovered].each do |event| + DiscourseEvent.on(event) { |post, _, _| WebHook.enqueue_post_hooks(event, post) } end DiscourseEvent.on(:post_edited) do |post, topic_changed| unless post.topic&.trashed? - # if we are editing the OP and the topic is changed, do not send # the post_edited event -- this event is sent separately because # when we update the OP in the UI we send two API calls in this order: @@ -42,49 +32,30 @@ DiscourseEvent.on(:post_edited) do |post, topic_changed| end end -%i( +%i[ user_logged_out user_created user_logged_in user_approved user_updated user_confirmed_email -).each do |event| - DiscourseEvent.on(event) do |user| - WebHook.enqueue_object_hooks(:user, user, event) - end +].each do |event| + DiscourseEvent.on(event) { |user| WebHook.enqueue_object_hooks(:user, user, event) } end -%i( - group_created - group_updated -).each do |event| - DiscourseEvent.on(event) do |group| - WebHook.enqueue_object_hooks(:group, group, event) - end +%i[group_created group_updated].each do |event| + DiscourseEvent.on(event) { |group| WebHook.enqueue_object_hooks(:group, group, event) } end -%i( - category_created - category_updated -).each do |event| - DiscourseEvent.on(event) do |category| - WebHook.enqueue_object_hooks(:category, category, event) - end +%i[category_created category_updated].each do |event| + DiscourseEvent.on(event) { |category| WebHook.enqueue_object_hooks(:category, category, event) } end -%i( - tag_created - tag_updated -).each do |event| - DiscourseEvent.on(event) do |tag| - WebHook.enqueue_object_hooks(:tag, tag, event, TagSerializer) - end +%i[tag_created tag_updated].each do |event| + DiscourseEvent.on(event) { |tag| WebHook.enqueue_object_hooks(:tag, tag, event, TagSerializer) } end -%i( - user_badge_granted -).each do |event| +%i[user_badge_granted].each do |event| # user_badge_revoked DiscourseEvent.on(event) do |badge, user_id| ub = UserBadge.find_by(badge: badge, user_id: user_id) @@ -92,30 +63,43 @@ end end end -%i( - reviewable_created - reviewable_score_updated -).each do |event| +%i[reviewable_created reviewable_score_updated].each do |event| DiscourseEvent.on(event) do |reviewable| WebHook.enqueue_object_hooks(:reviewable, reviewable, event, reviewable.serializer) end end DiscourseEvent.on(:reviewable_transitioned_to) do |status, reviewable| - WebHook.enqueue_object_hooks(:reviewable, reviewable, :reviewable_transitioned_to, reviewable.serializer) + WebHook.enqueue_object_hooks( + :reviewable, + reviewable, + :reviewable_transitioned_to, + reviewable.serializer, + ) end DiscourseEvent.on(:notification_created) do |notification| - WebHook.enqueue_object_hooks(:notification, notification, :notification_created, NotificationSerializer) + WebHook.enqueue_object_hooks( + :notification, + notification, + :notification_created, + NotificationSerializer, + ) end DiscourseEvent.on(:user_added_to_group) do |user, group, options| group_user = GroupUser.find_by(user: user, group: group) - WebHook.enqueue_object_hooks(:group_user, group_user, :user_added_to_group, WebHookGroupUserSerializer) + WebHook.enqueue_object_hooks( + :group_user, + group_user, + :user_added_to_group, + WebHookGroupUserSerializer, + ) end DiscourseEvent.on(:user_promoted) do |payload| - user_id, new_trust_level, old_trust_level = payload.values_at(:user_id, :new_trust_level, :old_trust_level) + user_id, new_trust_level, old_trust_level = + payload.values_at(:user_id, :new_trust_level, :old_trust_level) next if new_trust_level < old_trust_level @@ -130,8 +114,13 @@ DiscourseEvent.on(:like_created) do |post_action| category_id = topic&.category_id tag_ids = topic&.tag_ids - WebHook.enqueue_object_hooks(:like, - post_action, :post_liked, WebHookLikeSerializer, - group_ids: group_ids, category_id: category_id, tag_ids: tag_ids + WebHook.enqueue_object_hooks( + :like, + post_action, + :post_liked, + WebHookLikeSerializer, + group_ids: group_ids, + category_id: category_id, + tag_ids: tag_ids, ) end diff --git a/config/initializers/013-excon_defaults.rb b/config/initializers/013-excon_defaults.rb index 40389051db..302dbda499 100644 --- a/config/initializers/013-excon_defaults.rb +++ b/config/initializers/013-excon_defaults.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true -require 'excon' +require "excon" Excon::DEFAULTS[:omit_default_port] = true diff --git a/config/initializers/014-track-setting-changes.rb b/config/initializers/014-track-setting-changes.rb index ae4705bb21..410b8091d7 100644 --- a/config/initializers/014-track-setting-changes.rb +++ b/config/initializers/014-track-setting-changes.rb @@ -6,10 +6,11 @@ DiscourseEvent.on(:site_setting_changed) do |name, old_value, new_value| # Enabling `must_approve_users` on an existing site is odd, so we assume that the # existing users are approved. if name == :must_approve_users && new_value == true - - User.where(approved: false) + User + .where(approved: false) .joins("LEFT JOIN reviewables r ON r.target_id = users.id") - .where(r: { id: nil }).update_all(approved: true) + .where(r: { id: nil }) + .update_all(approved: true) end if name == :emoji_set @@ -19,31 +20,28 @@ DiscourseEvent.on(:site_setting_changed) do |name, old_value, new_value| after = "/images/emoji/#{new_value}/" Scheduler::Defer.later("Fix Emoji Links") do - DB.exec("UPDATE posts SET cooked = REPLACE(cooked, :before, :after) WHERE cooked LIKE :like", + DB.exec( + "UPDATE posts SET cooked = REPLACE(cooked, :before, :after) WHERE cooked LIKE :like", before: before, after: after, - like: "%#{before}%" + like: "%#{before}%", ) end end - Stylesheet::Manager.clear_color_scheme_cache! if [:base_font, :heading_font].include?(name) + Stylesheet::Manager.clear_color_scheme_cache! if %i[base_font heading_font].include?(name) - Report.clear_cache(:storage_stats) if [:backup_location, :s3_backup_bucket].include?(name) + Report.clear_cache(:storage_stats) if %i[backup_location s3_backup_bucket].include?(name) if name == :slug_generation_method - Scheduler::Defer.later("Null topic slug") do - Topic.update_all(slug: nil) - end + Scheduler::Defer.later("Null topic slug") { Topic.update_all(slug: nil) } end - Jobs.enqueue(:update_s3_inventory) if [:enable_s3_inventory, :s3_upload_bucket].include?(name) + Jobs.enqueue(:update_s3_inventory) if %i[enable_s3_inventory s3_upload_bucket].include?(name) SvgSprite.expire_cache if name.to_s.include?("_icon") - if SiteIconManager::WATCHED_SETTINGS.include?(name) - SiteIconManager.ensure_optimized! - end + SiteIconManager.ensure_optimized! if SiteIconManager::WATCHED_SETTINGS.include?(name) # Make sure medium and high priority thresholds were calculated. if name == :reviewable_low_priority_threshold && Reviewable.min_score_for_priority(:medium) > 0 diff --git a/config/initializers/099-anon-cache.rb b/config/initializers/099-anon-cache.rb index 32bca43206..ee5e240d93 100644 --- a/config/initializers/099-anon-cache.rb +++ b/config/initializers/099-anon-cache.rb @@ -9,7 +9,7 @@ enabled = Rails.env.production? end -if !ENV['DISCOURSE_DISABLE_ANON_CACHE'] && enabled +if !ENV["DISCOURSE_DISABLE_ANON_CACHE"] && enabled # in an ideal world this is position 0, but mobile detection uses ... session and request and params Rails.configuration.middleware.insert_after ActionDispatch::Flash, Middleware::AnonymousCache end diff --git a/config/initializers/099-drain_pool.rb b/config/initializers/099-drain_pool.rb index e69de29bb2..8b13789179 100644 --- a/config/initializers/099-drain_pool.rb +++ b/config/initializers/099-drain_pool.rb @@ -0,0 +1 @@ + diff --git a/config/initializers/100-i18n.rb b/config/initializers/100-i18n.rb index 4165bd75b6..473e2f4933 100644 --- a/config/initializers/100-i18n.rb +++ b/config/initializers/100-i18n.rb @@ -2,8 +2,8 @@ # order: after 02-freedom_patches.rb -require 'i18n/backend/discourse_i18n' -require 'i18n/backend/fallback_locale_list' +require "i18n/backend/discourse_i18n" +require "i18n/backend/fallback_locale_list" # Requires the `translate_accelerator.rb` freedom patch to be loaded Rails.application.reloader.to_prepare do @@ -11,7 +11,7 @@ Rails.application.reloader.to_prepare do I18n.fallbacks = I18n::Backend::FallbackLocaleList.new I18n.config.missing_interpolation_argument_handler = proc { throw(:exception) } I18n.reload! - I18n.init_accelerator!(overrides_enabled: ENV['DISABLE_TRANSLATION_OVERRIDES'] != '1') + I18n.init_accelerator!(overrides_enabled: ENV["DISABLE_TRANSLATION_OVERRIDES"] != "1") unless Rails.env.test? MessageBus.subscribe("/i18n-flush") do diff --git a/config/initializers/100-logster.rb b/config/initializers/100-logster.rb index 5047a01a63..8bbbc2a7a1 100644 --- a/config/initializers/100-logster.rb +++ b/config/initializers/100-logster.rb @@ -2,9 +2,7 @@ if GlobalSetting.skip_redis? Rails.application.reloader.to_prepare do - if Rails.logger.respond_to? :chained - Rails.logger = Rails.logger.chained.first - end + Rails.logger = Rails.logger.chained.first if Rails.logger.respond_to? :chained end return end @@ -39,9 +37,7 @@ if Rails.env.production? # https://github.com/rails/rails/blob/f2caed1e/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb#L39-L42 /^ActionController::RoutingError \(No route matches/, /^ActionDispatch::Http::MimeNegotiation::InvalidType/, - /^PG::Error: ERROR:\s+duplicate key/, - /^ActionController::UnknownFormat/, /^ActionController::UnknownHttpMethod/, /^AbstractController::ActionNotFound/, @@ -51,29 +47,21 @@ if Rails.env.production? # Column: # /(?m).*?Line: (?:\D|0).*?Column: (?:\D|0)/, - # suppress empty JS errors (covers MSIE 9, etc) /^(Syntax|Script) error.*Line: (0|1)\b/m, - # CSRF errors are not providing enough data # suppress unconditionally for now /^Can't verify CSRF token authenticity.$/, - # Yandex bot triggers this JS error a lot /^Uncaught ReferenceError: I18n is not defined/, - # related to browser plugins somehow, we don't care /Error calling method on NPObject/, - # 404s can be dealt with elsewhere /^ActiveRecord::RecordNotFound/, - # bad asset requested, no need to log /^ActionController::BadRequest/, - # we can't do anything about invalid parameters /Rack::QueryParser::InvalidParameterError/, - # we handle this cleanly in the message bus middleware # no point logging to logster /RateLimiter::LimitExceeded.*/m, @@ -98,33 +86,37 @@ store.redis_raw_connection = redis.without_namespace severities = [Logger::WARN, Logger::ERROR, Logger::FATAL, Logger::UNKNOWN] RailsMultisite::ConnectionManagement.each_connection do - error_rate_per_minute = SiteSetting.alert_admins_if_errors_per_minute rescue 0 + error_rate_per_minute = + begin + SiteSetting.alert_admins_if_errors_per_minute + rescue StandardError + 0 + end if (error_rate_per_minute || 0) > 0 store.register_rate_limit_per_minute(severities, error_rate_per_minute) do |rate| - MessageBus.publish("/logs_error_rate_exceeded", - { - rate: rate, - duration: 'minute', - publish_at: Time.current.to_i - }, - group_ids: [Group::AUTO_GROUPS[:admins]] - ) + MessageBus.publish( + "/logs_error_rate_exceeded", + { rate: rate, duration: "minute", publish_at: Time.current.to_i }, + group_ids: [Group::AUTO_GROUPS[:admins]], + ) end end - error_rate_per_hour = SiteSetting.alert_admins_if_errors_per_hour rescue 0 + error_rate_per_hour = + begin + SiteSetting.alert_admins_if_errors_per_hour + rescue StandardError + 0 + end if (error_rate_per_hour || 0) > 0 store.register_rate_limit_per_hour(severities, error_rate_per_hour) do |rate| - MessageBus.publish("/logs_error_rate_exceeded", - { - rate: rate, - duration: 'hour', - publish_at: Time.current.to_i, - }, - group_ids: [Group::AUTO_GROUPS[:admins]] - ) + MessageBus.publish( + "/logs_error_rate_exceeded", + { rate: rate, duration: "hour", publish_at: Time.current.to_i }, + group_ids: [Group::AUTO_GROUPS[:admins]], + ) end end end @@ -137,13 +129,13 @@ if Rails.configuration.multisite end Logster.config.project_directories = [ - { path: Rails.root.to_s, url: "https://github.com/discourse/discourse", main_app: true } + { path: Rails.root.to_s, url: "https://github.com/discourse/discourse", main_app: true }, ] Discourse.plugins.each do |plugin| next if !plugin.metadata.url Logster.config.project_directories << { path: "#{Rails.root.to_s}/plugins/#{plugin.directory_name}", - url: plugin.metadata.url + url: plugin.metadata.url, } end diff --git a/config/initializers/100-onebox_options.rb b/config/initializers/100-onebox_options.rb index 3d2e4a2f05..70f0a468e8 100644 --- a/config/initializers/100-onebox_options.rb +++ b/config/initializers/100-onebox_options.rb @@ -6,13 +6,13 @@ Rails.application.config.to_prepare do twitter_client: TwitterApi, redirect_limit: 3, user_agent: "Discourse Forum Onebox v#{Discourse::VERSION::STRING}", - allowed_ports: [80, 443, SiteSetting.port.to_i] + allowed_ports: [80, 443, SiteSetting.port.to_i], } else Onebox.options = { twitter_client: TwitterApi, redirect_limit: 3, - user_agent: "Discourse Forum Onebox v#{Discourse::VERSION::STRING}" + user_agent: "Discourse Forum Onebox v#{Discourse::VERSION::STRING}", } end end diff --git a/config/initializers/100-push-notifications.rb b/config/initializers/100-push-notifications.rb index 056e4bb56e..1261c465c6 100644 --- a/config/initializers/100-push-notifications.rb +++ b/config/initializers/100-push-notifications.rb @@ -3,13 +3,11 @@ return if GlobalSetting.skip_db? Rails.application.config.to_prepare do - require 'web-push' + require "web-push" def generate_vapid_key? - SiteSetting.vapid_public_key.blank? || - SiteSetting.vapid_private_key.blank? || - SiteSetting.vapid_public_key_bytes.blank? || - SiteSetting.vapid_base_url != Discourse.base_url + SiteSetting.vapid_public_key.blank? || SiteSetting.vapid_private_key.blank? || + SiteSetting.vapid_public_key_bytes.blank? || SiteSetting.vapid_base_url != Discourse.base_url end SiteSetting.vapid_base_url = Discourse.base_url if SiteSetting.vapid_base_url.blank? @@ -19,15 +17,12 @@ Rails.application.config.to_prepare do SiteSetting.vapid_public_key = vapid_key.public_key SiteSetting.vapid_private_key = vapid_key.private_key - SiteSetting.vapid_public_key_bytes = Base64.urlsafe_decode64(SiteSetting.vapid_public_key).bytes.join("|") + SiteSetting.vapid_public_key_bytes = + Base64.urlsafe_decode64(SiteSetting.vapid_public_key).bytes.join("|") SiteSetting.vapid_base_url = Discourse.base_url - if ActiveRecord::Base.connection.table_exists?(:push_subscriptions) - PushSubscription.delete_all - end + PushSubscription.delete_all if ActiveRecord::Base.connection.table_exists?(:push_subscriptions) end - DiscourseEvent.on(:user_logged_out) do |user| - PushNotificationPusher.clear_subscriptions(user) - end + DiscourseEvent.on(:user_logged_out) { |user| PushNotificationPusher.clear_subscriptions(user) } end diff --git a/config/initializers/100-quiet_logger.rb b/config/initializers/100-quiet_logger.rb index f73826f108..ef15649986 100644 --- a/config/initializers/100-quiet_logger.rb +++ b/config/initializers/100-quiet_logger.rb @@ -1,30 +1,23 @@ # frozen_string_literal: true -Rails.application.config.assets.configure do |env| - env.logger = Logger.new('/dev/null') -end +Rails.application.config.assets.configure { |env| env.logger = Logger.new("/dev/null") } module DiscourseRackQuietAssetsLogger def call(env) override = false - if (env['PATH_INFO'].index("/assets/") == 0) || - (env['PATH_INFO'].index("/stylesheets") == 0) || - (env['PATH_INFO'].index("/svg-sprite") == 0) || - (env['PATH_INFO'].index("/manifest") == 0) || - (env['PATH_INFO'].index("/service-worker") == 0) || - (env['PATH_INFO'].index("mini-profiler-resources") == 0) || - (env['PATH_INFO'].index("/srv/status") == 0) + if (env["PATH_INFO"].index("/assets/") == 0) || (env["PATH_INFO"].index("/stylesheets") == 0) || + (env["PATH_INFO"].index("/svg-sprite") == 0) || + (env["PATH_INFO"].index("/manifest") == 0) || + (env["PATH_INFO"].index("/service-worker") == 0) || + (env["PATH_INFO"].index("mini-profiler-resources") == 0) || + (env["PATH_INFO"].index("/srv/status") == 0) if ::Logster::Logger === Rails.logger override = true Rails.logger.override_level = Logger::ERROR end end - super(env).tap do - if override - Rails.logger.override_level = nil - end - end + super(env).tap { Rails.logger.override_level = nil if override } end end diff --git a/config/initializers/100-session_store.rb b/config/initializers/100-session_store.rb index b2ce5d47b4..b06e28dd63 100644 --- a/config/initializers/100-session_store.rb +++ b/config/initializers/100-session_store.rb @@ -4,8 +4,15 @@ Rails.application.config.session_store( :discourse_cookie_store, - key: '_forum_session', - path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root + key: "_forum_session", + path: + ( + if (Rails.application.config.relative_url_root.nil?) + "/" + else + Rails.application.config.relative_url_root + end + ), ) Rails.application.config.to_prepare do diff --git a/config/initializers/100-sidekiq.rb b/config/initializers/100-sidekiq.rb index 79422e0b79..3ce1927722 100644 --- a/config/initializers/100-sidekiq.rb +++ b/config/initializers/100-sidekiq.rb @@ -1,22 +1,17 @@ # frozen_string_literal: true require "sidekiq/pausable" -require 'sidekiq_logster_reporter' +require "sidekiq_logster_reporter" -Sidekiq.configure_client do |config| - config.redis = Discourse.sidekiq_redis_config -end +Sidekiq.configure_client { |config| config.redis = Discourse.sidekiq_redis_config } Sidekiq.configure_server do |config| config.redis = Discourse.sidekiq_redis_config - config.server_middleware do |chain| - chain.add Sidekiq::Pausable - end + config.server_middleware { |chain| chain.add Sidekiq::Pausable } end if Sidekiq.server? - module Sidekiq class CLI private @@ -34,13 +29,17 @@ if Sidekiq.server? # warm up AR RailsMultisite::ConnectionManagement.safe_each_connection do (ActiveRecord::Base.connection.tables - %w[schema_migrations versions]).each do |table| - table.classify.constantize.first rescue nil + begin + table.classify.constantize.first + rescue StandardError + nil + end end end scheduler_hostname = ENV["UNICORN_SCHEDULER_HOSTNAME"] - if !scheduler_hostname || scheduler_hostname.split(',').include?(Discourse.os_hostname) + if !scheduler_hostname || scheduler_hostname.split(",").include?(Discourse.os_hostname) begin MiniScheduler.start(workers: GlobalSetting.mini_scheduler_workers) rescue MiniScheduler::DistributedMutex::Timeout @@ -57,9 +56,11 @@ else # Instead, this patch adds a dedicated logger instance and patches # the #add method to forward messages to Rails.logger. Sidekiq.logger = Logger.new(nil) - Sidekiq.logger.define_singleton_method(:add) do |severity, message = nil, progname = nil, &blk| - Rails.logger.add(severity, message, progname, &blk) - end + Sidekiq + .logger + .define_singleton_method(:add) do |severity, message = nil, progname = nil, &blk| + Rails.logger.add(severity, message, progname, &blk) + end end Sidekiq.error_handlers.clear @@ -69,28 +70,20 @@ Sidekiq.strict_args! Rails.application.config.to_prepare do # Ensure that scheduled jobs are loaded before mini_scheduler is configured. - if Rails.env.development? - Dir.glob("#{Rails.root}/app/jobs/scheduled/*.rb") do |f| - require(f) - end - end + Dir.glob("#{Rails.root}/app/jobs/scheduled/*.rb") { |f| require(f) } if Rails.env.development? MiniScheduler.configure do |config| config.redis = Discourse.redis - config.job_exception_handler do |ex, context| - Discourse.handle_job_exception(ex, context) - end + config.job_exception_handler { |ex, context| Discourse.handle_job_exception(ex, context) } - config.job_ran do |stat| - DiscourseEvent.trigger(:scheduled_job_ran, stat) - end + config.job_ran { |stat| DiscourseEvent.trigger(:scheduled_job_ran, stat) } config.skip_schedule { Sidekiq.paused? } config.before_sidekiq_web_request do RailsMultisite::ConnectionManagement.establish_connection( - db: RailsMultisite::ConnectionManagement::DEFAULT + db: RailsMultisite::ConnectionManagement::DEFAULT, ) end end diff --git a/config/initializers/100-silence_logger.rb b/config/initializers/100-silence_logger.rb index 3a900d24e3..e01b29816f 100644 --- a/config/initializers/100-silence_logger.rb +++ b/config/initializers/100-silence_logger.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class SilenceLogger < Rails::Rack::Logger - PATH_INFO = 'PATH_INFO' - HTTP_X_SILENCE_LOGGER = 'HTTP_X_SILENCE_LOGGER' + PATH_INFO = "PATH_INFO" + HTTP_X_SILENCE_LOGGER = "HTTP_X_SILENCE_LOGGER" def initialize(app, opts = {}) @app = app @@ -17,11 +17,9 @@ class SilenceLogger < Rails::Rack::Logger path_info = env[PATH_INFO] override = false - if env[HTTP_X_SILENCE_LOGGER] || - @opts[:silenced].include?(path_info) || - path_info.start_with?('/logs') || - path_info.start_with?('/user_avatar') || - path_info.start_with?('/letter_avatar') + if env[HTTP_X_SILENCE_LOGGER] || @opts[:silenced].include?(path_info) || + path_info.start_with?("/logs") || path_info.start_with?("/user_avatar") || + path_info.start_with?("/letter_avatar") if ::Logster::Logger === Rails.logger override = true Rails.logger.override_level = Logger::WARN @@ -35,10 +33,10 @@ class SilenceLogger < Rails::Rack::Logger end end -silenced = [ - "/mini-profiler-resources/results", - "/mini-profiler-resources/includes.js", - "/mini-profiler-resources/includes.css", - "/mini-profiler-resources/jquery.tmpl.js" +silenced = %w[ + /mini-profiler-resources/results + /mini-profiler-resources/includes.js + /mini-profiler-resources/includes.css + /mini-profiler-resources/jquery.tmpl.js ] Rails.configuration.middleware.swap Rails::Rack::Logger, SilenceLogger, silenced: silenced diff --git a/config/initializers/100-verify_config.rb b/config/initializers/100-verify_config.rb index c2e7c63e66..5d779b6aac 100644 --- a/config/initializers/100-verify_config.rb +++ b/config/initializers/100-verify_config.rb @@ -3,8 +3,7 @@ # Check that the app is configured correctly. Raise some helpful errors if something is wrong. if defined?(Rails::Server) && Rails.env.production? # Only run these checks when starting up a production server - - if ['localhost', 'production.localhost'].include?(Discourse.current_hostname) + if %w[localhost production.localhost].include?(Discourse.current_hostname) puts <<~TEXT Discourse.current_hostname = '#{Discourse.current_hostname}' @@ -18,7 +17,7 @@ if defined?(Rails::Server) && Rails.env.production? # Only run these checks when raise "Invalid host_names in database.yml" end - if !Dir.glob(File.join(Rails.root, 'public', 'assets', 'application*.js')).present? + if !Dir.glob(File.join(Rails.root, "public", "assets", "application*.js")).present? puts <<~TEXT Assets have not been precompiled. Please run the following command diff --git a/config/initializers/100-wrap_parameters.rb b/config/initializers/100-wrap_parameters.rb index 85b2d84061..26c901dede 100644 --- a/config/initializers/100-wrap_parameters.rb +++ b/config/initializers/100-wrap_parameters.rb @@ -6,11 +6,7 @@ # is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. -ActiveSupport.on_load(:action_controller) do - wrap_parameters format: [:json] -end +ActiveSupport.on_load(:action_controller) { wrap_parameters format: [:json] } # Disable root element in JSON by default. -ActiveSupport.on_load(:active_record) do - self.include_root_in_json = false -end +ActiveSupport.on_load(:active_record) { self.include_root_in_json = false } diff --git a/config/initializers/101-lograge.rb b/config/initializers/101-lograge.rb index 311c64c753..3baac8b4b4 100644 --- a/config/initializers/101-lograge.rb +++ b/config/initializers/101-lograge.rb @@ -1,8 +1,9 @@ # frozen_string_literal: true Rails.application.config.to_prepare do - if (Rails.env.production? && SiteSetting.logging_provider == 'lograge') || (ENV["ENABLE_LOGRAGE"] == "1") - require 'lograge' + if (Rails.env.production? && SiteSetting.logging_provider == "lograge") || + (ENV["ENABLE_LOGRAGE"] == "1") + require "lograge" if Rails.configuration.multisite Rails.logger.formatter = ActiveSupport::Logger::SimpleFormatter.new @@ -11,20 +12,20 @@ Rails.application.config.to_prepare do Rails.application.configure do config.lograge.enabled = true - Lograge.ignore(lambda do |event| - # this is our hijack magic status, - # no point logging this cause we log again - # direct from hijack - event.payload[:status] == 418 - end) + Lograge.ignore( + lambda do |event| + # this is our hijack magic status, + # no point logging this cause we log again + # direct from hijack + event.payload[:status] == 418 + end, + ) config.lograge.custom_payload do |controller| begin username = begin - if controller.respond_to?(:current_user) - controller.current_user&.username - end + controller.current_user&.username if controller.respond_to?(:current_user) rescue Discourse::InvalidAccess, Discourse::ReadOnly, ActiveRecord::ReadOnlyError nil end @@ -36,78 +37,77 @@ Rails.application.config.to_prepare do nil end - { - ip: ip, - username: username - } + { ip: ip, username: username } rescue => e - Rails.logger.warn("Failed to append custom payload: #{e.message}\n#{e.backtrace.join("\n")}") + Rails.logger.warn( + "Failed to append custom payload: #{e.message}\n#{e.backtrace.join("\n")}", + ) {} end end - config.lograge.custom_options = lambda do |event| - begin - exceptions = %w(controller action format id) + config.lograge.custom_options = + lambda do |event| + begin + exceptions = %w[controller action format id] - params = event.payload[:params].except(*exceptions) + params = event.payload[:params].except(*exceptions) - if (file = params[:file]) && file.respond_to?(:headers) - params[:file] = file.headers + if (file = params[:file]) && file.respond_to?(:headers) + params[:file] = file.headers + end + + if (files = params[:files]) && files.respond_to?(:map) + params[:files] = files.map { |f| f.respond_to?(:headers) ? f.headers : f } + end + + output = { + params: params.to_query, + database: RailsMultisite::ConnectionManagement.current_db, + } + + if data = (Thread.current[:_method_profiler] || event.payload[:timings]) + sql = data[:sql] + + if sql + output[:db] = sql[:duration] * 1000 + output[:db_calls] = sql[:calls] + end + + redis = data[:redis] + + if redis + output[:redis] = redis[:duration] * 1000 + output[:redis_calls] = redis[:calls] + end + + net = data[:net] + + if net + output[:net] = net[:duration] * 1000 + output[:net_calls] = net[:calls] + end + end + + output + rescue RateLimiter::LimitExceeded + # no idea who this is, but they are limited + {} + rescue => e + Rails.logger.warn( + "Failed to append custom options: #{e.message}\n#{e.backtrace.join("\n")}", + ) + {} end - - if (files = params[:files]) && files.respond_to?(:map) - params[:files] = files.map do |f| - f.respond_to?(:headers) ? f.headers : f - end - end - - output = { - params: params.to_query, - database: RailsMultisite::ConnectionManagement.current_db, - } - - if data = (Thread.current[:_method_profiler] || event.payload[:timings]) - sql = data[:sql] - - if sql - output[:db] = sql[:duration] * 1000 - output[:db_calls] = sql[:calls] - end - - redis = data[:redis] - - if redis - output[:redis] = redis[:duration] * 1000 - output[:redis_calls] = redis[:calls] - end - - net = data[:net] - - if net - output[:net] = net[:duration] * 1000 - output[:net_calls] = net[:calls] - end - end - - output - rescue RateLimiter::LimitExceeded - # no idea who this is, but they are limited - {} - rescue => e - Rails.logger.warn("Failed to append custom options: #{e.message}\n#{e.backtrace.join("\n")}") - {} end - end if ENV["LOGSTASH_URI"] config.lograge.formatter = Lograge::Formatters::Logstash.new - require 'discourse_logstash_logger' + require "discourse_logstash_logger" - config.lograge.logger = DiscourseLogstashLogger.logger( - uri: ENV['LOGSTASH_URI'], type: :rails - ) + config.lograge.logger = + DiscourseLogstashLogger.logger(uri: ENV["LOGSTASH_URI"], type: :rails) # Remove ActiveSupport::Logger from the chain and replace with Lograge's # logger diff --git a/config/initializers/200-first_middlewares.rb b/config/initializers/200-first_middlewares.rb index 4617bd519d..ea011a6f8a 100644 --- a/config/initializers/200-first_middlewares.rb +++ b/config/initializers/200-first_middlewares.rb @@ -11,13 +11,11 @@ Rails.configuration.middleware.unshift(MessageBus::Rack::Middleware) # no reason to track this in development, that is 300+ redis calls saved per # page view (we serve all assets out of thin in development) -if Rails.env != 'development' || ENV['TRACK_REQUESTS'] - require 'middleware/request_tracker' +if Rails.env != "development" || ENV["TRACK_REQUESTS"] + require "middleware/request_tracker" Rails.configuration.middleware.unshift Middleware::RequestTracker - if GlobalSetting.enable_performance_http_headers - MethodProfiler.ensure_discourse_instrumentation! - end + MethodProfiler.ensure_discourse_instrumentation! if GlobalSetting.enable_performance_http_headers end if Rails.env.test? @@ -30,23 +28,27 @@ if Rails.env.test? super(env) end end - Rails.configuration.middleware.unshift TestMultisiteMiddleware, RailsMultisite::DiscoursePatches.config + Rails.configuration.middleware.unshift TestMultisiteMiddleware, + RailsMultisite::DiscoursePatches.config elsif Rails.configuration.multisite assets_hostnames = GlobalSetting.cdn_hostnames if assets_hostnames.empty? - assets_hostnames = - Discourse::Application.config.database_configuration[Rails.env]["host_names"] + assets_hostnames = Discourse::Application.config.database_configuration[Rails.env]["host_names"] end RailsMultisite::ConnectionManagement.asset_hostnames = assets_hostnames # Multisite needs to be first, because the request tracker and message bus rely on it - Rails.configuration.middleware.unshift RailsMultisite::Middleware, RailsMultisite::DiscoursePatches.config + Rails.configuration.middleware.unshift RailsMultisite::Middleware, + RailsMultisite::DiscoursePatches.config Rails.configuration.middleware.delete ActionDispatch::Executor if defined?(RailsFailover::ActiveRecord) && Rails.configuration.active_record_rails_failover - Rails.configuration.middleware.insert_after(RailsMultisite::Middleware, RailsFailover::ActiveRecord::Middleware) + Rails.configuration.middleware.insert_after( + RailsMultisite::Middleware, + RailsFailover::ActiveRecord::Middleware, + ) end if Rails.env.development? @@ -57,5 +59,8 @@ elsif Rails.configuration.multisite end end elsif defined?(RailsFailover::ActiveRecord) && Rails.configuration.active_record_rails_failover - Rails.configuration.middleware.insert_before(MessageBus::Rack::Middleware, RailsFailover::ActiveRecord::Middleware) + Rails.configuration.middleware.insert_before( + MessageBus::Rack::Middleware, + RailsFailover::ActiveRecord::Middleware, + ) end diff --git a/config/initializers/400-deprecations.rb b/config/initializers/400-deprecations.rb index 39373182d1..29d636f584 100644 --- a/config/initializers/400-deprecations.rb +++ b/config/initializers/400-deprecations.rb @@ -2,18 +2,32 @@ if !GlobalSetting.skip_redis? if GlobalSetting.respond_to?(:redis_slave_host) && GlobalSetting.redis_slave_host.present? - Discourse.deprecate("redis_slave_host is deprecated, use redis_replica_host instead", drop_from: "2.8") + Discourse.deprecate( + "redis_slave_host is deprecated, use redis_replica_host instead", + drop_from: "2.8", + ) end if GlobalSetting.respond_to?(:redis_slave_port) && GlobalSetting.redis_slave_port.present? - Discourse.deprecate("redis_slave_port is deprecated, use redis_replica_port instead", drop_from: "2.8") + Discourse.deprecate( + "redis_slave_port is deprecated, use redis_replica_port instead", + drop_from: "2.8", + ) end - if GlobalSetting.respond_to?(:message_bus_redis_slave_host) && GlobalSetting.message_bus_redis_slave_host.present? - Discourse.deprecate("message_bus_redis_slave_host is deprecated, use message_bus_redis_replica_host", drop_from: "2.8") + if GlobalSetting.respond_to?(:message_bus_redis_slave_host) && + GlobalSetting.message_bus_redis_slave_host.present? + Discourse.deprecate( + "message_bus_redis_slave_host is deprecated, use message_bus_redis_replica_host", + drop_from: "2.8", + ) end - if GlobalSetting.respond_to?(:message_bus_redis_slave_port) && GlobalSetting.message_bus_redis_slave_port.present? - Discourse.deprecate("message_bus_redis_slave_port is deprecated, use message_bus_redis_replica_port", drop_from: "2.8") + if GlobalSetting.respond_to?(:message_bus_redis_slave_port) && + GlobalSetting.message_bus_redis_slave_port.present? + Discourse.deprecate( + "message_bus_redis_slave_port is deprecated, use message_bus_redis_replica_port", + drop_from: "2.8", + ) end end diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb index 98564a17cf..191f6acc28 100644 --- a/config/initializers/assets.rb +++ b/config/initializers/assets.rb @@ -17,11 +17,13 @@ Rails.application.config.assets.paths << "#{Rails.root}/public/javascripts" # folder are already added. # explicitly precompile any images in plugins ( /assets/images ) path -Rails.application.config.assets.precompile += [lambda do |filename, path| - path =~ /assets\/images/ && !%w(.js .css).include?(File.extname(filename)) -end] +Rails.application.config.assets.precompile += [ + lambda do |filename, path| + path =~ %r{assets/images} && !%w[.js .css].include?(File.extname(filename)) + end, +] -Rails.application.config.assets.precompile += %w{ +Rails.application.config.assets.precompile += %w[ discourse.js vendor.js admin.js @@ -49,15 +51,21 @@ Rails.application.config.assets.precompile += %w{ embed-application.js scripts/discourse-test-listen-boot scripts/discourse-boot -} +] -Rails.application.config.assets.precompile += EmberCli.assets.map { |name| name.sub('.js', '.map') } +Rails.application.config.assets.precompile += EmberCli.assets.map { |name| name.sub(".js", ".map") } # Precompile all available locales unless GlobalSetting.try(:omit_base_locales) - Dir.glob("#{Rails.root}/app/assets/javascripts/locales/*.js.erb").each do |file| - Rails.application.config.assets.precompile << "locales/#{file.match(/([a-z_A-Z]+\.js)\.erb$/)[1]}" - end + Dir + .glob("#{Rails.root}/app/assets/javascripts/locales/*.js.erb") + .each do |file| + Rails + .application + .config + .assets + .precompile << "locales/#{file.match(/([a-z_A-Z]+\.js)\.erb$/)[1]}" + end end # out of the box sprockets 3 grabs loose files that are hanging in assets, @@ -65,18 +73,16 @@ end Rails.application.config.assets.precompile.delete(Sprockets::Railtie::LOOSE_APP_ASSETS) # We don't want application from node_modules, only from the root -Rails.application.config.assets.precompile.delete(/(?:\/|\\|\A)application\.(css|js)$/) -Rails.application.config.assets.precompile += ['application.js'] +Rails.application.config.assets.precompile.delete(%r{(?:/|\\|\A)application\.(css|js)$}) +Rails.application.config.assets.precompile += ["application.js"] start_path = ::Rails.root.join("app/assets").to_s -exclude = ['.es6', '.hbs', '.hbr', '.js', '.css', '.lock', '.json', '.log', '.html', ''] +exclude = [".es6", ".hbs", ".hbr", ".js", ".css", ".lock", ".json", ".log", ".html", ""] Rails.application.config.assets.precompile << lambda do |logical_path, filename| - filename.start_with?(start_path) && - !filename.include?("/node_modules/") && - !filename.include?("/dist/") && - !exclude.include?(File.extname(logical_path)) + filename.start_with?(start_path) && !filename.include?("/node_modules/") && + !filename.include?("/dist/") && !exclude.include?(File.extname(logical_path)) end -Discourse.find_plugin_js_assets(include_disabled: true).each do |file| - Rails.application.config.assets.precompile << "#{file}.js" -end +Discourse + .find_plugin_js_assets(include_disabled: true) + .each { |file| Rails.application.config.assets.precompile << "#{file}.js" } diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index af4c108bf6..44f7fffd74 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -3,13 +3,13 @@ # Be sure to restart your server when you modify this file. # Configure sensitive parameters which will be filtered from the log file. -Rails.application.config.filter_parameters += [ - :password, - :pop3_polling_password, - :api_key, - :s3_secret_access_key, - :twitter_consumer_secret, - :facebook_app_secret, - :github_client_secret, - :second_factor_token, +Rails.application.config.filter_parameters += %i[ + password + pop3_polling_password + api_key + s3_secret_access_key + twitter_consumer_secret + facebook_app_secret + github_client_secret + second_factor_token ] diff --git a/config/initializers/new_framework_defaults_7_0.rb b/config/initializers/new_framework_defaults_7_0.rb index 7ee8ffa85f..fdbf91e62f 100644 --- a/config/initializers/new_framework_defaults_7_0.rb +++ b/config/initializers/new_framework_defaults_7_0.rb @@ -102,5 +102,5 @@ Rails.application.config.action_dispatch.default_headers = { "X-Content-Type-Options" => "nosniff", "X-Download-Options" => "noopen", "X-Permitted-Cross-Domain-Policies" => "none", - "Referrer-Policy" => "strict-origin-when-cross-origin" + "Referrer-Policy" => "strict-origin-when-cross-origin", } diff --git a/config/locales/plurals.rb b/config/locales/plurals.rb index 4ffacf4408..babf8cf131 100644 --- a/config/locales/plurals.rb +++ b/config/locales/plurals.rb @@ -3,6 +3,7 @@ # source: https://github.com/svenfuchs/i18n/blob/master/test/test_data/locales/plurals.rb +# stree-ignore { af: { i18n: { plural: { keys: [:one, :other], rule: lambda { |n| n == 1 ? :one : :other } } } }, am: { i18n: { plural: { keys: [:one, :other], rule: lambda { |n| [0, 1].include?(n) ? :one : :other } } } }, diff --git a/config/puma.rb b/config/puma.rb index 0fea9a2487..c349148f57 100644 --- a/config/puma.rb +++ b/config/puma.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -if ENV['RAILS_ENV'] == 'production' - +if ENV["RAILS_ENV"] == "production" # First, you need to change these below to your situation. - APP_ROOT = ENV["APP_ROOT"] || '/home/discourse/discourse' + APP_ROOT = ENV["APP_ROOT"] || "/home/discourse/discourse" num_workers = ENV["NUM_WEBS"].to_i > 0 ? ENV["NUM_WEBS"].to_i : 4 # Second, you can choose how many threads that you are going to run at same time. @@ -16,5 +15,4 @@ if ENV['RAILS_ENV'] == 'production' pidfile "#{APP_ROOT}/tmp/pids/puma.pid" state_path "#{APP_ROOT}/tmp/pids/puma.state" preload_app! - end diff --git a/config/routes.rb b/config/routes.rb index baf501a6f9..abdf514ef8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,16 +4,27 @@ require "sidekiq/web" require "mini_scheduler/web" # The following constants have been replaced with `RouteFormat` and are deprecated. -USERNAME_ROUTE_FORMAT = /[%\w.\-]+?/ unless defined? USERNAME_ROUTE_FORMAT -BACKUP_ROUTE_FORMAT = /.+\.(sql\.gz|tar\.gz|tgz)/i unless defined? BACKUP_ROUTE_FORMAT +USERNAME_ROUTE_FORMAT = /[%\w.\-]+?/ unless defined?(USERNAME_ROUTE_FORMAT) +BACKUP_ROUTE_FORMAT = /.+\.(sql\.gz|tar\.gz|tgz)/i unless defined?(BACKUP_ROUTE_FORMAT) Discourse::Application.routes.draw do - def patch(*) end # Disable PATCH requests + def patch(*) + end # Disable PATCH requests - scope path: nil, constraints: { format: /(json|html|\*\/\*)/ } do - relative_url_root = (defined?(Rails.configuration.relative_url_root) && Rails.configuration.relative_url_root) ? Rails.configuration.relative_url_root + '/' : '/' + scope path: nil, constraints: { format: %r{(json|html|\*/\*)} } do + relative_url_root = + ( + if ( + defined?(Rails.configuration.relative_url_root) && + Rails.configuration.relative_url_root + ) + Rails.configuration.relative_url_root + "/" + else + "/" + end + ) - match "/404", to: "exceptions#not_found", via: [:get, :post] + match "/404", to: "exceptions#not_found", via: %i[get post] get "/404-body" => "exceptions#not_found_body" get "/bootstrap" => "bootstrap#index" @@ -22,8 +33,8 @@ Discourse::Application.routes.draw do end post "webhooks/aws" => "webhooks#aws" - post "webhooks/mailgun" => "webhooks#mailgun" - post "webhooks/mailjet" => "webhooks#mailjet" + post "webhooks/mailgun" => "webhooks#mailgun" + post "webhooks/mailjet" => "webhooks#mailjet" post "webhooks/mandrill" => "webhooks#mandrill" get "webhooks/mandrill" => "webhooks#mandrill_head" post "webhooks/postmark" => "webhooks#postmark" @@ -32,7 +43,7 @@ Discourse::Application.routes.draw do scope path: nil, format: true, constraints: { format: :xml } do resources :sitemap, only: [:index] - get "/sitemap_:page" => "sitemap#page", page: /[1-9][0-9]*/ + get "/sitemap_:page" => "sitemap#page", :page => /[1-9][0-9]*/ get "/sitemap_recent" => "sitemap#recent" get "/news" => "sitemap#news" end @@ -43,15 +54,13 @@ Discourse::Application.routes.draw do mount Logster::Web => "/logs" else # only allow sidekiq in master site - mount Sidekiq::Web => "/sidekiq", constraints: AdminConstraint.new(require_master: true) - mount Logster::Web => "/logs", constraints: AdminConstraint.new + mount Sidekiq::Web => "/sidekiq", :constraints => AdminConstraint.new(require_master: true) + mount Logster::Web => "/logs", :constraints => AdminConstraint.new end end resources :about do - collection do - get "live_post_counts" - end + collection { get "live_post_counts" } end get "finish-installation" => "finish_installation#index" @@ -76,25 +85,23 @@ Discourse::Application.routes.draw do get "emoji" end - get "site/basic-info" => 'site#basic_info' - get "site/statistics" => 'site#statistics' + get "site/basic-info" => "site#basic_info" + get "site/statistics" => "site#statistics" get "srv/status" => "forums#status" get "wizard" => "wizard#index" - get 'wizard/steps' => 'steps#index' - get 'wizard/steps/:id' => "wizard#index" - put 'wizard/steps/:id' => "steps#update" + get "wizard/steps" => "steps#index" + get "wizard/steps/:id" => "wizard#index" + put "wizard/steps/:id" => "steps#update" namespace :admin, constraints: StaffConstraint.new do get "" => "admin#index" - get 'plugins' => 'plugins#index' + get "plugins" => "plugins#index" resources :site_settings, constraints: AdminConstraint.new do - collection do - get "category/:id" => "site_settings#index" - end + collection { get "category/:id" => "site_settings#index" } put "user_count" => "site_settings#user_count" end @@ -111,13 +118,11 @@ Discourse::Application.routes.draw do end end resources :groups, except: [:create], constraints: AdminConstraint.new do - collection do - put "automatic_membership_count" => "groups#automatic_membership_count" - end + collection { put "automatic_membership_count" => "groups#automatic_membership_count" } end - get "groups/:type" => "groups#show", constraints: AdminConstraint.new - get "groups/:type/:id" => "groups#show", constraints: AdminConstraint.new + get "groups/:type" => "groups#show", :constraints => AdminConstraint.new + get "groups/:type/:id" => "groups#show", :constraints => AdminConstraint.new resources :users, id: RouteFormat.username, except: [:show] do collection do @@ -145,8 +150,8 @@ Discourse::Application.routes.draw do put "trust_level" put "trust_level_lock" put "primary_group" - post "groups" => "users#add_group", constraints: AdminConstraint.new - delete "groups/:group_id" => "users#remove_group", constraints: AdminConstraint.new + post "groups" => "users#add_group", :constraints => AdminConstraint.new + delete "groups/:group_id" => "users#remove_group", :constraints => AdminConstraint.new get "badges" get "leader_requirements" => "users#tl3_requirements" get "tl3_requirements" @@ -156,12 +161,16 @@ Discourse::Application.routes.draw do put "disable_second_factor" delete "sso_record" end - get "users/:id.json" => 'users#show', defaults: { format: 'json' } - get 'users/:id/:username' => 'users#show', constraints: { username: RouteFormat.username }, as: :user_show - get 'users/:id/:username/badges' => 'users#show' - get 'users/:id/:username/tl3_requirements' => 'users#show' + get "users/:id.json" => "users#show", :defaults => { format: "json" } + get "users/:id/:username" => "users#show", + :constraints => { + username: RouteFormat.username, + }, + :as => :user_show + get "users/:id/:username/badges" => "users#show" + get "users/:id/:username/tl3_requirements" => "users#show" - post "users/sync_sso" => "users#sync_sso", constraints: AdminConstraint.new + post "users/sync_sso" => "users#sync_sso", :constraints => AdminConstraint.new resources :impersonate, constraints: AdminConstraint.new @@ -186,28 +195,29 @@ Discourse::Application.routes.draw do end scope "/logs" do - resources :staff_action_logs, only: [:index] - get 'staff_action_logs/:id/diff' => 'staff_action_logs#diff' - resources :screened_emails, only: [:index, :destroy] - resources :screened_ip_addresses, only: [:index, :create, :update, :destroy] - resources :screened_urls, only: [:index] - resources :search_logs, only: [:index] - get 'search_logs/term/' => 'search_logs#term' + resources :staff_action_logs, only: [:index] + get "staff_action_logs/:id/diff" => "staff_action_logs#diff" + resources :screened_emails, only: %i[index destroy] + resources :screened_ip_addresses, only: %i[index create update destroy] + resources :screened_urls, only: [:index] + resources :search_logs, only: [:index] + get "search_logs/term/" => "search_logs#term" end get "/logs" => "staff_action_logs#index" # alias - get '/logs/watched_words', to: redirect(relative_url_root + 'admin/customize/watched_words') - get '/logs/watched_words/*path', to: redirect(relative_url_root + 'admin/customize/watched_words/%{path}') + get "/logs/watched_words", to: redirect(relative_url_root + "admin/customize/watched_words") + get "/logs/watched_words/*path", + to: redirect(relative_url_root + "admin/customize/watched_words/%{path}") - get "customize" => "color_schemes#index", constraints: AdminConstraint.new - get "customize/themes" => "themes#index", constraints: AdminConstraint.new - get "customize/colors" => "color_schemes#index", constraints: AdminConstraint.new - get "customize/colors/:id" => "color_schemes#index", constraints: AdminConstraint.new - get "customize/permalinks" => "permalinks#index", constraints: AdminConstraint.new - get "customize/embedding" => "embedding#show", constraints: AdminConstraint.new - put "customize/embedding" => "embedding#update", constraints: AdminConstraint.new + get "customize" => "color_schemes#index", :constraints => AdminConstraint.new + get "customize/themes" => "themes#index", :constraints => AdminConstraint.new + get "customize/colors" => "color_schemes#index", :constraints => AdminConstraint.new + get "customize/colors/:id" => "color_schemes#index", :constraints => AdminConstraint.new + get "customize/permalinks" => "permalinks#index", :constraints => AdminConstraint.new + get "customize/embedding" => "embedding#show", :constraints => AdminConstraint.new + put "customize/embedding" => "embedding#update", :constraints => AdminConstraint.new resources :themes, constraints: AdminConstraint.new do member do @@ -225,33 +235,42 @@ Discourse::Application.routes.draw do resources :user_fields, constraints: AdminConstraint.new resources :emojis, constraints: AdminConstraint.new - get 'themes/:id/:target/:field_name/edit' => 'themes#index' - get 'themes/:id' => 'themes#index' + get "themes/:id/:target/:field_name/edit" => "themes#index" + get "themes/:id" => "themes#index" get "themes/:id/export" => "themes#export" # They have periods in their URLs often: - get 'site_texts' => 'site_texts#index' - get 'site_texts/:id.json' => 'site_texts#show', constraints: { id: /[\w.\-\+\%\&]+/i } - get 'site_texts/:id' => 'site_texts#show', constraints: { id: /[\w.\-\+\%\&]+/i } - put 'site_texts/:id.json' => 'site_texts#update', constraints: { id: /[\w.\-\+\%\&]+/i } - put 'site_texts/:id' => 'site_texts#update', constraints: { id: /[\w.\-\+\%\&]+/i } - delete 'site_texts/:id.json' => 'site_texts#revert', constraints: { id: /[\w.\-\+\%\&]+/i } - delete 'site_texts/:id' => 'site_texts#revert', constraints: { id: /[\w.\-\+\%\&]+/i } + get "site_texts" => "site_texts#index" + get "site_texts/:id.json" => "site_texts#show", :constraints => { id: /[\w.\-\+\%\&]+/i } + get "site_texts/:id" => "site_texts#show", :constraints => { id: /[\w.\-\+\%\&]+/i } + put "site_texts/:id.json" => "site_texts#update", :constraints => { id: /[\w.\-\+\%\&]+/i } + put "site_texts/:id" => "site_texts#update", :constraints => { id: /[\w.\-\+\%\&]+/i } + delete "site_texts/:id.json" => "site_texts#revert", + :constraints => { + id: /[\w.\-\+\%\&]+/i, + } + delete "site_texts/:id" => "site_texts#revert", :constraints => { id: /[\w.\-\+\%\&]+/i } - get 'reseed' => 'site_texts#get_reseed_options' - post 'reseed' => 'site_texts#reseed' + get "reseed" => "site_texts#get_reseed_options" + post "reseed" => "site_texts#reseed" - get 'email_templates' => 'email_templates#index' - get 'email_templates/(:id)' => 'email_templates#show', constraints: { id: /[0-9a-z_.]+/ } - put 'email_templates/(:id)' => 'email_templates#update', constraints: { id: /[0-9a-z_.]+/ } - delete 'email_templates/(:id)' => 'email_templates#revert', constraints: { id: /[0-9a-z_.]+/ } + get "email_templates" => "email_templates#index" + get "email_templates/(:id)" => "email_templates#show", :constraints => { id: /[0-9a-z_.]+/ } + put "email_templates/(:id)" => "email_templates#update", + :constraints => { + id: /[0-9a-z_.]+/, + } + delete "email_templates/(:id)" => "email_templates#revert", + :constraints => { + id: /[0-9a-z_.]+/, + } - get 'robots' => 'robots_txt#show' - put 'robots.json' => 'robots_txt#update' - delete 'robots.json' => 'robots_txt#reset' + get "robots" => "robots_txt#show" + put "robots.json" => "robots_txt#update" + delete "robots.json" => "robots_txt#reset" - resource :email_style, only: [:show, :update] - get 'email_style/:field' => 'email_styles#show', constraints: { field: /html|css/ } + resource :email_style, only: %i[show update] + get "email_style/:field" => "email_styles#show", :constraints => { field: /html|css/ } end resources :embeddable_hosts, constraints: AdminConstraint.new @@ -259,7 +278,7 @@ Discourse::Application.routes.draw do resources :permalinks, constraints: AdminConstraint.new scope "/customize" do - resources :watched_words, only: [:index, :create, :update, :destroy] do + resources :watched_words, only: %i[index create update destroy] do collection do get "action/:id" => "watched_words#index" get "action/:id/download" => "watched_words#download" @@ -280,17 +299,13 @@ Discourse::Application.routes.draw do put "dashboard/mark-new-features-as-seen" => "dashboard#mark_new_features_as_seen" resources :dashboard, only: [:index] do - collection do - get "problems" - end + collection { get "problems" } end resources :api, only: [:index], constraints: AdminConstraint.new do collection do - resources :keys, controller: 'api', only: [:index, :show, :update, :create, :destroy] do - collection do - get 'scopes' => 'api#scopes' - end + resources :keys, controller: "api", only: %i[index show update create destroy] do + collection { get "scopes" => "api#scopes" } member do post "revoke" => "api#revoke_key" @@ -299,26 +314,27 @@ Discourse::Application.routes.draw do end resources :web_hooks - get 'web_hook_events/:id' => 'web_hooks#list_events', as: :web_hook_events - get 'web_hooks/:id/events/bulk' => 'web_hooks#bulk_events' - post 'web_hooks/:web_hook_id/events/:event_id/redeliver' => 'web_hooks#redeliver_event' - post 'web_hooks/:id/ping' => 'web_hooks#ping' + get "web_hook_events/:id" => "web_hooks#list_events", :as => :web_hook_events + get "web_hooks/:id/events/bulk" => "web_hooks#bulk_events" + post "web_hooks/:web_hook_id/events/:event_id/redeliver" => "web_hooks#redeliver_event" + post "web_hooks/:id/ping" => "web_hooks#ping" end end - resources :backups, only: [:index, :create], constraints: AdminConstraint.new do + resources :backups, only: %i[index create], constraints: AdminConstraint.new do member do - get "" => "backups#show", constraints: { id: RouteFormat.backup } - put "" => "backups#email", constraints: { id: RouteFormat.backup } - delete "" => "backups#destroy", constraints: { id: RouteFormat.backup } - post "restore" => "backups#restore", constraints: { id: RouteFormat.backup } + get "" => "backups#show", :constraints => { id: RouteFormat.backup } + put "" => "backups#email", :constraints => { id: RouteFormat.backup } + delete "" => "backups#destroy", :constraints => { id: RouteFormat.backup } + post "restore" => "backups#restore", :constraints => { id: RouteFormat.backup } end collection do # multipart uploads - post "create-multipart" => "backups#create_multipart", format: :json - post "complete-multipart" => "backups#complete_multipart", format: :json - post "abort-multipart" => "backups#abort_multipart", format: :json - post "batch-presign-multipart-parts" => "backups#batch_presign_multipart_parts", format: :json + post "create-multipart" => "backups#create_multipart", :format => :json + post "complete-multipart" => "backups#complete_multipart", :format => :json + post "abort-multipart" => "backups#abort_multipart", :format => :json + post "batch-presign-multipart-parts" => "backups#batch_presign_multipart_parts", + :format => :json get "logs" => "backups#logs" get "status" => "backups#status" @@ -340,39 +356,41 @@ Discourse::Application.routes.draw do post "preview" => "badges#preview" end end - end # admin namespace - get "email/unsubscribe/:key" => "email#unsubscribe", as: "email_unsubscribe" - get "email/unsubscribed" => "email#unsubscribed", as: "email_unsubscribed" - post "email/unsubscribe/:key" => "email#perform_unsubscribe", as: "email_perform_unsubscribe" + get "email/unsubscribe/:key" => "email#unsubscribe", :as => "email_unsubscribe" + get "email/unsubscribed" => "email#unsubscribed", :as => "email_unsubscribed" + post "email/unsubscribe/:key" => "email#perform_unsubscribe", :as => "email_perform_unsubscribe" get "extra-locales/:bundle" => "extra_locales#show" - resources :session, id: RouteFormat.username, only: [:create, :destroy, :become] do - if !Rails.env.production? - get 'become' - end + resources :session, id: RouteFormat.username, only: %i[create destroy become] do + get "become" if !Rails.env.production? - collection do - post "forgot_password" - end + collection { post "forgot_password" } end get "review" => "reviewables#index" # For ember app - get "review/:reviewable_id" => "reviewables#show", constraints: { reviewable_id: /\d+/ } - get "review/:reviewable_id/explain" => "reviewables#explain", constraints: { reviewable_id: /\d+/ } + get "review/:reviewable_id" => "reviewables#show", :constraints => { reviewable_id: /\d+/ } + get "review/:reviewable_id/explain" => "reviewables#explain", + :constraints => { + reviewable_id: /\d+/, + } get "review/count" => "reviewables#count" get "review/topics" => "reviewables#topics" get "review/settings" => "reviewables#settings" - get "review/user-menu-list" => "reviewables#user_menu_list", format: :json + get "review/user-menu-list" => "reviewables#user_menu_list", :format => :json put "review/settings" => "reviewables#settings" - put "review/:reviewable_id/perform/:action_id" => "reviewables#perform", constraints: { - reviewable_id: /\d+/, - action_id: /[a-z\_]+/ - } - put "review/:reviewable_id" => "reviewables#update", constraints: { reviewable_id: /\d+/ } - delete "review/:reviewable_id" => "reviewables#destroy", constraints: { reviewable_id: /\d+/ } + put "review/:reviewable_id/perform/:action_id" => "reviewables#perform", + :constraints => { + reviewable_id: /\d+/, + action_id: /[a-z\_]+/, + } + put "review/:reviewable_id" => "reviewables#update", :constraints => { reviewable_id: /\d+/ } + delete "review/:reviewable_id" => "reviewables#destroy", + :constraints => { + reviewable_id: /\d+/, + } resources :reviewable_claimed_topics @@ -384,8 +402,8 @@ Discourse::Application.routes.draw do get "session/hp" => "session#get_honeypot_value" get "session/email-login/:token" => "session#email_login_info" post "session/email-login/:token" => "session#email_login" - get "session/otp/:token" => "session#one_time_password", constraints: { token: /[0-9a-f]+/ } - post "session/otp/:token" => "session#one_time_password", constraints: { token: /[0-9a-f]+/ } + get "session/otp/:token" => "session#one_time_password", :constraints => { token: /[0-9a-f]+/ } + post "session/otp/:token" => "session#one_time_password", :constraints => { token: /[0-9a-f]+/ } get "session/2fa" => "session#second_factor_auth_show" post "session/2fa" => "session#second_factor_auth_perform" if Rails.env.test? @@ -398,30 +416,31 @@ Discourse::Application.routes.draw do resources :static post "login" => "static#enter" - get "login" => "static#show", id: "login" - get "password-reset" => "static#show", id: "password_reset" - get "faq" => "static#show", id: "faq" - get "tos" => "static#show", id: "tos", as: 'tos' - get "privacy" => "static#show", id: "privacy", as: 'privacy' - get "signup" => "static#show", id: "signup" - get "login-preferences" => "static#show", id: "login" + get "login" => "static#show", :id => "login" + get "password-reset" => "static#show", :id => "password_reset" + get "faq" => "static#show", :id => "faq" + get "tos" => "static#show", :id => "tos", :as => "tos" + get "privacy" => "static#show", :id => "privacy", :as => "privacy" + get "signup" => "static#show", :id => "signup" + get "login-preferences" => "static#show", :id => "login" - %w{guidelines rules conduct}.each do |faq_alias| - get faq_alias => "static#show", id: "guidelines", as: faq_alias + %w[guidelines rules conduct].each do |faq_alias| + get faq_alias => "static#show", :id => "guidelines", :as => faq_alias end - get "my/*path", to: 'users#my_redirect' - get ".well-known/change-password", to: redirect(relative_url_root + 'my/preferences/security', status: 302) + get "my/*path", to: "users#my_redirect" + get ".well-known/change-password", + to: redirect(relative_url_root + "my/preferences/security", status: 302) - get "user-cards" => "users#cards", format: :json - get "directory-columns" => "directory_columns#index", format: :json - get "edit-directory-columns" => "edit_directory_columns#index", format: :json - put "edit-directory-columns" => "edit_directory_columns#update", format: :json + get "user-cards" => "users#cards", :format => :json + get "directory-columns" => "directory_columns#index", :format => :json + get "edit-directory-columns" => "edit_directory_columns#index", :format => :json + put "edit-directory-columns" => "edit_directory_columns#update", :format => :json - %w{users u}.each_with_index do |root_path, index| - get "#{root_path}" => "users#index", constraints: { format: 'html' } + %w[users u].each_with_index do |root_path, index| + get "#{root_path}" => "users#index", :constraints => { format: "html" } - resources :users, except: [:index, :new, :show, :update, :destroy], path: root_path do + resources :users, except: %i[index new show update destroy], path: root_path do collection do get "check_username" get "check_email" @@ -431,8 +450,10 @@ Discourse::Application.routes.draw do post "#{root_path}/second_factors" => "users#list_second_factors" put "#{root_path}/second_factor" => "users#update_second_factor" - post "#{root_path}/create_second_factor_security_key" => "users#create_second_factor_security_key" - post "#{root_path}/register_second_factor_security_key" => "users#register_second_factor_security_key" + post "#{root_path}/create_second_factor_security_key" => + "users#create_second_factor_security_key" + post "#{root_path}/register_second_factor_security_key" => + "users#register_second_factor_security_key" put "#{root_path}/security_key" => "users#update_security_key" post "#{root_path}/create_second_factor_totp" => "users#create_second_factor_totp" post "#{root_path}/enable_second_factor_totp" => "users#enable_second_factor_totp" @@ -446,19 +467,40 @@ Discourse::Application.routes.draw do put "#{root_path}/admin-login" => "users#admin_login" post "#{root_path}/toggle-anon" => "users#toggle_anon" post "#{root_path}/read-faq" => "users#read_faq" - get "#{root_path}/recent-searches" => "users#recent_searches", constraints: { format: 'json' } - delete "#{root_path}/recent-searches" => "users#reset_recent_searches", constraints: { format: 'json' } + get "#{root_path}/recent-searches" => "users#recent_searches", + :constraints => { + format: "json", + } + delete "#{root_path}/recent-searches" => "users#reset_recent_searches", + :constraints => { + format: "json", + } get "#{root_path}/search/users" => "users#search_users" - get({ "#{root_path}/account-created/" => "users#account_created" }.merge(index == 1 ? { as: :users_account_created } : { as: :old_account_created })) + get( + { "#{root_path}/account-created/" => "users#account_created" }.merge( + index == 1 ? { as: :users_account_created } : { as: :old_account_created }, + ), + ) get "#{root_path}/account-created/resent" => "users#account_created" get "#{root_path}/account-created/edit-email" => "users#account_created" - get({ "#{root_path}/password-reset/:token" => "users#password_reset_show" }.merge(index == 1 ? { as: :password_reset_token } : {})) - get "#{root_path}/confirm-email-token/:token" => "users#confirm_email_token", constraints: { format: 'json' } + get( + { "#{root_path}/password-reset/:token" => "users#password_reset_show" }.merge( + index == 1 ? { as: :password_reset_token } : {}, + ), + ) + get "#{root_path}/confirm-email-token/:token" => "users#confirm_email_token", + :constraints => { + format: "json", + } put "#{root_path}/password-reset/:token" => "users#password_reset_update" get "#{root_path}/activate-account/:token" => "users#activate_account" - put({ "#{root_path}/activate-account/:token" => "users#perform_account_activation" }.merge(index == 1 ? { as: 'perform_activate_account' } : {})) + put( + { "#{root_path}/activate-account/:token" => "users#perform_account_activation" }.merge( + index == 1 ? { as: "perform_activate_account" } : {}, + ), + ) get "#{root_path}/confirm-old-email/:token" => "users_email#show_confirm_old_email" put "#{root_path}/confirm-old-email" => "users_email#confirm_old_email" @@ -466,105 +508,415 @@ Discourse::Application.routes.draw do get "#{root_path}/confirm-new-email/:token" => "users_email#show_confirm_new_email" put "#{root_path}/confirm-new-email" => "users_email#confirm_new_email" - get({ - "#{root_path}/confirm-admin/:token" => "users#confirm_admin", - constraints: { token: /[0-9a-f]+/ } - }.merge(index == 1 ? { as: 'confirm_admin' } : {})) - post "#{root_path}/confirm-admin/:token" => "users#confirm_admin", constraints: { token: /[0-9a-f]+/ } - get "#{root_path}/:username/private-messages" => "user_actions#private_messages", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/private-messages/:filter" => "user_actions#private_messages", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/messages" => "user_actions#private_messages", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/messages/:filter" => "user_actions#private_messages", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/messages/group/:group_name" => "user_actions#private_messages", constraints: { username: RouteFormat.username, group_name: RouteFormat.username } - get "#{root_path}/:username/messages/group/:group_name/:filter" => "user_actions#private_messages", constraints: { username: RouteFormat.username, group_name: RouteFormat.username } - get "#{root_path}/:username/messages/tags/:tag_id" => "list#private_messages_tag", constraints: { username: RouteFormat.username } - get "#{root_path}/:username.json" => "users#show", constraints: { username: RouteFormat.username }, defaults: { format: :json } - get({ "#{root_path}/:username" => "users#show", constraints: { username: RouteFormat.username } }.merge(index == 1 ? { as: 'user' } : {})) - put "#{root_path}/:username" => "users#update", constraints: { username: RouteFormat.username }, defaults: { format: :json } - get "#{root_path}/:username/emails" => "users#check_emails", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/sso-email" => "users#check_sso_email", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/sso-payload" => "users#check_sso_payload", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/email" => "users_email#index", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/account" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/security" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/profile" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/emails" => "users#preferences", constraints: { username: RouteFormat.username } - put "#{root_path}/:username/preferences/primary-email" => "users#update_primary_email", format: :json, constraints: { username: RouteFormat.username } - delete "#{root_path}/:username/preferences/email" => "users#destroy_email", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/notifications" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/tracking" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/categories" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/users" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/tags" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/interface" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/sidebar" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/apps" => "users#preferences", constraints: { username: RouteFormat.username } - post "#{root_path}/:username/preferences/email" => "users_email#create", constraints: { username: RouteFormat.username } - put "#{root_path}/:username/preferences/email" => "users_email#update", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/badge_title" => "users#preferences", constraints: { username: RouteFormat.username } - put "#{root_path}/:username/preferences/badge_title" => "users#badge_title", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/username" => "users#preferences", constraints: { username: RouteFormat.username } - put "#{root_path}/:username/preferences/username" => "users#username", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/second-factor" => "users#preferences", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/preferences/second-factor-backup" => "users#preferences", constraints: { username: RouteFormat.username } - delete "#{root_path}/:username/preferences/user_image" => "users#destroy_user_image", constraints: { username: RouteFormat.username } - put "#{root_path}/:username/preferences/avatar/pick" => "users#pick_avatar", constraints: { username: RouteFormat.username } - put "#{root_path}/:username/preferences/avatar/select" => "users#select_avatar", constraints: { username: RouteFormat.username } - post "#{root_path}/:username/preferences/revoke-account" => "users#revoke_account", constraints: { username: RouteFormat.username } - post "#{root_path}/:username/preferences/revoke-auth-token" => "users#revoke_auth_token", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/staff-info" => "users#staff_info", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/summary" => "users#summary", constraints: { username: RouteFormat.username } - put "#{root_path}/:username/notification_level" => "users#notification_level", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/invited" => "users#invited", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/invited/:filter" => "users#invited", constraints: { username: RouteFormat.username } + get( + { + "#{root_path}/confirm-admin/:token" => "users#confirm_admin", + :constraints => { + token: /[0-9a-f]+/, + }, + }.merge(index == 1 ? { as: "confirm_admin" } : {}), + ) + post "#{root_path}/confirm-admin/:token" => "users#confirm_admin", + :constraints => { + token: /[0-9a-f]+/, + } + get "#{root_path}/:username/private-messages" => "user_actions#private_messages", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/private-messages/:filter" => "user_actions#private_messages", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/messages" => "user_actions#private_messages", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/messages/:filter" => "user_actions#private_messages", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/messages/group/:group_name" => "user_actions#private_messages", + :constraints => { + username: RouteFormat.username, + group_name: RouteFormat.username, + } + get "#{root_path}/:username/messages/group/:group_name/:filter" => + "user_actions#private_messages", + :constraints => { + username: RouteFormat.username, + group_name: RouteFormat.username, + } + get "#{root_path}/:username/messages/tags/:tag_id" => "list#private_messages_tag", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username.json" => "users#show", + :constraints => { + username: RouteFormat.username, + }, + :defaults => { + format: :json, + } + get( + { + "#{root_path}/:username" => "users#show", + :constraints => { + username: RouteFormat.username, + }, + }.merge(index == 1 ? { as: "user" } : {}), + ) + put "#{root_path}/:username" => "users#update", + :constraints => { + username: RouteFormat.username, + }, + :defaults => { + format: :json, + } + get "#{root_path}/:username/emails" => "users#check_emails", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/sso-email" => "users#check_sso_email", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/sso-payload" => "users#check_sso_payload", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/email" => "users_email#index", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/account" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/security" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/profile" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/emails" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + put "#{root_path}/:username/preferences/primary-email" => "users#update_primary_email", + :format => :json, + :constraints => { + username: RouteFormat.username, + } + delete "#{root_path}/:username/preferences/email" => "users#destroy_email", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/notifications" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/tracking" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/categories" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/users" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/tags" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/interface" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/sidebar" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/apps" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + post "#{root_path}/:username/preferences/email" => "users_email#create", + :constraints => { + username: RouteFormat.username, + } + put "#{root_path}/:username/preferences/email" => "users_email#update", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/badge_title" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + put "#{root_path}/:username/preferences/badge_title" => "users#badge_title", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/username" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + put "#{root_path}/:username/preferences/username" => "users#username", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/second-factor" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/preferences/second-factor-backup" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } + delete "#{root_path}/:username/preferences/user_image" => "users#destroy_user_image", + :constraints => { + username: RouteFormat.username, + } + put "#{root_path}/:username/preferences/avatar/pick" => "users#pick_avatar", + :constraints => { + username: RouteFormat.username, + } + put "#{root_path}/:username/preferences/avatar/select" => "users#select_avatar", + :constraints => { + username: RouteFormat.username, + } + post "#{root_path}/:username/preferences/revoke-account" => "users#revoke_account", + :constraints => { + username: RouteFormat.username, + } + post "#{root_path}/:username/preferences/revoke-auth-token" => "users#revoke_auth_token", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/staff-info" => "users#staff_info", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/summary" => "users#summary", + :constraints => { + username: RouteFormat.username, + } + put "#{root_path}/:username/notification_level" => "users#notification_level", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/invited" => "users#invited", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/invited/:filter" => "users#invited", + :constraints => { + username: RouteFormat.username, + } post "#{root_path}/action/send_activation_email" => "users#send_activation_email" - get "#{root_path}/:username/summary" => "users#show", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/activity/topics.rss" => "list#user_topics_feed", format: :rss, constraints: { username: RouteFormat.username } - get "#{root_path}/:username/activity.rss" => "posts#user_posts_feed", format: :rss, constraints: { username: RouteFormat.username } - get "#{root_path}/:username/activity.json" => "posts#user_posts_feed", format: :json, constraints: { username: RouteFormat.username } - get "#{root_path}/:username/activity" => "users#show", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/activity/:filter" => "users#show", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/badges" => "users#badges", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/bookmarks" => "users#bookmarks", constraints: { username: RouteFormat.username, format: /(json|ics)/ } - get "#{root_path}/:username/user-menu-bookmarks" => "users#user_menu_bookmarks", constraints: { username: RouteFormat.username, format: :json } - get "#{root_path}/:username/user-menu-private-messages" => "users#user_menu_messages", constraints: { username: RouteFormat.username, format: :json } - get "#{root_path}/:username/notifications" => "users#show", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/notifications/:filter" => "users#show", constraints: { username: RouteFormat.username } - delete "#{root_path}/:username" => "users#destroy", constraints: { username: RouteFormat.username } - get "#{root_path}/by-external/:external_id" => "users#show", constraints: { external_id: /[^\/]+/ } - get "#{root_path}/by-external/:external_provider/:external_id" => "users#show", constraints: { external_id: /[^\/]+/ } - get "#{root_path}/:username/flagged-posts" => "users#show", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/deleted-posts" => "users#show", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/topic-tracking-state" => "users#topic_tracking_state", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/private-message-topic-tracking-state" => "users#private_message_topic_tracking_state", constraints: { username: RouteFormat.username } + get "#{root_path}/:username/summary" => "users#show", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/activity/topics.rss" => "list#user_topics_feed", + :format => :rss, + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/activity.rss" => "posts#user_posts_feed", + :format => :rss, + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/activity.json" => "posts#user_posts_feed", + :format => :json, + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/activity" => "users#show", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/activity/:filter" => "users#show", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/badges" => "users#badges", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/bookmarks" => "users#bookmarks", + :constraints => { + username: RouteFormat.username, + format: /(json|ics)/, + } + get "#{root_path}/:username/user-menu-bookmarks" => "users#user_menu_bookmarks", + :constraints => { + username: RouteFormat.username, + format: :json, + } + get "#{root_path}/:username/user-menu-private-messages" => "users#user_menu_messages", + :constraints => { + username: RouteFormat.username, + format: :json, + } + get "#{root_path}/:username/notifications" => "users#show", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/notifications/:filter" => "users#show", + :constraints => { + username: RouteFormat.username, + } + delete "#{root_path}/:username" => "users#destroy", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/by-external/:external_id" => "users#show", + :constraints => { + external_id: %r{[^/]+}, + } + get "#{root_path}/by-external/:external_provider/:external_id" => "users#show", + :constraints => { + external_id: %r{[^/]+}, + } + get "#{root_path}/:username/flagged-posts" => "users#show", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/deleted-posts" => "users#show", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/topic-tracking-state" => "users#topic_tracking_state", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/private-message-topic-tracking-state" => + "users#private_message_topic_tracking_state", + :constraints => { + username: RouteFormat.username, + } get "#{root_path}/:username/profile-hidden" => "users#profile_hidden" - put "#{root_path}/:username/feature-topic" => "users#feature_topic", constraints: { username: RouteFormat.username } - put "#{root_path}/:username/clear-featured-topic" => "users#clear_featured_topic", constraints: { username: RouteFormat.username } - get "#{root_path}/:username/card.json" => "users#show_card", format: :json, constraints: { username: RouteFormat.username } + put "#{root_path}/:username/feature-topic" => "users#feature_topic", + :constraints => { + username: RouteFormat.username, + } + put "#{root_path}/:username/clear-featured-topic" => "users#clear_featured_topic", + :constraints => { + username: RouteFormat.username, + } + get "#{root_path}/:username/card.json" => "users#show_card", + :format => :json, + :constraints => { + username: RouteFormat.username, + } end - get "user-badges/:username.json" => "user_badges#username", constraints: { username: RouteFormat.username }, defaults: { format: :json } - get "user-badges/:username" => "user_badges#username", constraints: { username: RouteFormat.username } + get "user-badges/:username.json" => "user_badges#username", + :constraints => { + username: RouteFormat.username, + }, + :defaults => { + format: :json, + } + get "user-badges/:username" => "user_badges#username", + :constraints => { + username: RouteFormat.username, + } - post "user_avatar/:username/refresh_gravatar" => "user_avatars#refresh_gravatar", constraints: { username: RouteFormat.username } - get "letter_avatar/:username/:size/:version.png" => "user_avatars#show_letter", constraints: { hostname: /[\w\.-]+/, size: /\d+/, username: RouteFormat.username, format: :png } - get "user_avatar/:hostname/:username/:size/:version.png" => "user_avatars#show", constraints: { hostname: /[\w\.-]+/, size: /\d+/, username: RouteFormat.username, format: :png } + post "user_avatar/:username/refresh_gravatar" => "user_avatars#refresh_gravatar", + :constraints => { + username: RouteFormat.username, + } + get "letter_avatar/:username/:size/:version.png" => "user_avatars#show_letter", + :constraints => { + hostname: /[\w\.-]+/, + size: /\d+/, + username: RouteFormat.username, + format: :png, + } + get "user_avatar/:hostname/:username/:size/:version.png" => "user_avatars#show", + :constraints => { + hostname: /[\w\.-]+/, + size: /\d+/, + username: RouteFormat.username, + format: :png, + } - get "letter_avatar_proxy/:version/letter/:letter/:color/:size.png" => "user_avatars#show_proxy_letter", constraints: { format: :png } + get "letter_avatar_proxy/:version/letter/:letter/:color/:size.png" => + "user_avatars#show_proxy_letter", + :constraints => { + format: :png, + } - get "svg-sprite/:hostname/svg-:theme_id-:version.js" => "svg_sprite#show", constraints: { hostname: /[\w\.-]+/, version: /\h{40}/, theme_id: /([0-9]+)?/, format: :js } - get "svg-sprite/search/:keyword" => "svg_sprite#search", format: false, constraints: { keyword: /[-a-z0-9\s\%]+/ } - get "svg-sprite/picker-search" => "svg_sprite#icon_picker_search", defaults: { format: :json } - get "svg-sprite/:hostname/icon(/:color)/:name.svg" => "svg_sprite#svg_icon", constraints: { hostname: /[\w\.-]+/, name: /[-a-z0-9\s\%]+/, color: /(\h{3}{1,2})/, format: :svg } + get "svg-sprite/:hostname/svg-:theme_id-:version.js" => "svg_sprite#show", + :constraints => { + hostname: /[\w\.-]+/, + version: /\h{40}/, + theme_id: /([0-9]+)?/, + format: :js, + } + get "svg-sprite/search/:keyword" => "svg_sprite#search", + :format => false, + :constraints => { + keyword: /[-a-z0-9\s\%]+/, + } + get "svg-sprite/picker-search" => "svg_sprite#icon_picker_search", + :defaults => { + format: :json, + } + get "svg-sprite/:hostname/icon(/:color)/:name.svg" => "svg_sprite#svg_icon", + :constraints => { + hostname: /[\w\.-]+/, + name: /[-a-z0-9\s\%]+/, + color: /(\h{3}{1,2})/, + format: :svg, + } - get "highlight-js/:hostname/:version.js" => "highlight_js#show", constraints: { hostname: /[\w\.-]+/, format: :js } + get "highlight-js/:hostname/:version.js" => "highlight_js#show", + :constraints => { + hostname: /[\w\.-]+/, + format: :js, + } - get "stylesheets/:name" => "stylesheets#show_source_map", constraints: { name: /[-a-z0-9_]+/, format: /css\.map/ }, format: true - get "stylesheets/:name" => "stylesheets#show", constraints: { name: /[-a-z0-9_]+/, format: "css" }, format: true - get "color-scheme-stylesheet/:id(/:theme_id)" => "stylesheets#color_scheme", constraints: { format: :json } - get "theme-javascripts/:digest" => "theme_javascripts#show", constraints: { digest: /\h{40}/, format: :js }, format: true - get "theme-javascripts/:digest" => "theme_javascripts#show_map", constraints: { digest: /\h{40}/, format: :map }, format: true + get "stylesheets/:name" => "stylesheets#show_source_map", + :constraints => { + name: /[-a-z0-9_]+/, + format: /css\.map/, + }, + :format => true + get "stylesheets/:name" => "stylesheets#show", + :constraints => { + name: /[-a-z0-9_]+/, + format: "css", + }, + :format => true + get "color-scheme-stylesheet/:id(/:theme_id)" => "stylesheets#color_scheme", + :constraints => { + format: :json, + } + get "theme-javascripts/:digest" => "theme_javascripts#show", + :constraints => { + digest: /\h{40}/, + format: :js, + }, + :format => true + get "theme-javascripts/:digest" => "theme_javascripts#show_map", + :constraints => { + digest: /\h{40}/, + format: :map, + }, + :format => true get "theme-javascripts/tests/:theme_id-:digest.js" => "theme_javascripts#show_tests" post "uploads/lookup-metadata" => "uploads#metadata" @@ -572,65 +924,115 @@ Discourse::Application.routes.draw do post "uploads/lookup-urls" => "uploads#lookup_urls" # direct to s3 uploads - post "uploads/generate-presigned-put" => "uploads#generate_presigned_put", format: :json - post "uploads/complete-external-upload" => "uploads#complete_external_upload", format: :json + post "uploads/generate-presigned-put" => "uploads#generate_presigned_put", :format => :json + post "uploads/complete-external-upload" => "uploads#complete_external_upload", :format => :json # multipart uploads - post "uploads/create-multipart" => "uploads#create_multipart", format: :json - post "uploads/complete-multipart" => "uploads#complete_multipart", format: :json - post "uploads/abort-multipart" => "uploads#abort_multipart", format: :json - post "uploads/batch-presign-multipart-parts" => "uploads#batch_presign_multipart_parts", format: :json + post "uploads/create-multipart" => "uploads#create_multipart", :format => :json + post "uploads/complete-multipart" => "uploads#complete_multipart", :format => :json + post "uploads/abort-multipart" => "uploads#abort_multipart", :format => :json + post "uploads/batch-presign-multipart-parts" => "uploads#batch_presign_multipart_parts", + :format => :json # used to download original images - get "uploads/:site/:sha(.:extension)" => "uploads#show", constraints: { site: /\w+/, sha: /\h{40}/, extension: /[a-z0-9\._]+/i } - get "uploads/short-url/:base62(.:extension)" => "uploads#show_short", constraints: { site: /\w+/, base62: /[a-zA-Z0-9]+/, extension: /[a-zA-Z0-9\._-]+/i }, as: :upload_short + get "uploads/:site/:sha(.:extension)" => "uploads#show", + :constraints => { + site: /\w+/, + sha: /\h{40}/, + extension: /[a-z0-9\._]+/i, + } + get "uploads/short-url/:base62(.:extension)" => "uploads#show_short", + :constraints => { + site: /\w+/, + base62: /[a-zA-Z0-9]+/, + extension: /[a-zA-Z0-9\._-]+/i, + }, + :as => :upload_short # used to download attachments - get "uploads/:site/original/:tree:sha(.:extension)" => "uploads#show", constraints: { site: /\w+/, tree: /([a-z0-9]+\/)+/i, sha: /\h{40}/, extension: /[a-z0-9\._]+/i } + get "uploads/:site/original/:tree:sha(.:extension)" => "uploads#show", + :constraints => { + site: /\w+/, + tree: %r{([a-z0-9]+/)+}i, + sha: /\h{40}/, + extension: /[a-z0-9\._]+/i, + } if Rails.env.test? - get "uploads/:site/test_:index/original/:tree:sha(.:extension)" => "uploads#show", constraints: { site: /\w+/, index: /\d+/, tree: /([a-z0-9]+\/)+/i, sha: /\h{40}/, extension: /[a-z0-9\._]+/i } + get "uploads/:site/test_:index/original/:tree:sha(.:extension)" => "uploads#show", + :constraints => { + site: /\w+/, + index: /\d+/, + tree: %r{([a-z0-9]+/)+}i, + sha: /\h{40}/, + extension: /[a-z0-9\._]+/i, + } end # used to download attachments (old route) - get "uploads/:site/:id/:sha" => "uploads#show", constraints: { site: /\w+/, id: /\d+/, sha: /\h{16}/, format: /.*/ } + get "uploads/:site/:id/:sha" => "uploads#show", + :constraints => { + site: /\w+/, + id: /\d+/, + sha: /\h{16}/, + format: /.*/, + } # NOTE: secure-media-uploads is the old form, all new URLs generated for # secure uploads will be secure-uploads, this is left in for backwards # compat without needing to rebake all posts for each site. - get "secure-media-uploads/*path(.:extension)" => "uploads#_show_secure_deprecated", constraints: { extension: /[a-z0-9\._]+/i } - get "secure-uploads/*path(.:extension)" => "uploads#show_secure", constraints: { extension: /[a-z0-9\._]+/i } + get "secure-media-uploads/*path(.:extension)" => "uploads#_show_secure_deprecated", + :constraints => { + extension: /[a-z0-9\._]+/i, + } + get "secure-uploads/*path(.:extension)" => "uploads#show_secure", + :constraints => { + extension: /[a-z0-9\._]+/i, + } - get "posts" => "posts#latest", id: "latest_posts", constraints: { format: /(json|rss)/ } - get "private-posts" => "posts#latest", id: "private_posts", constraints: { format: /(json|rss)/ } + get "posts" => "posts#latest", :id => "latest_posts", :constraints => { format: /(json|rss)/ } + get "private-posts" => "posts#latest", + :id => "private_posts", + :constraints => { + format: /(json|rss)/, + } get "posts/by_number/:topic_id/:post_number" => "posts#by_number" get "posts/by-date/:topic_id/:date" => "posts#by_date" get "posts/:id/reply-history" => "posts#reply_history" - get "posts/:id/reply-ids" => "posts#reply_ids" + get "posts/:id/reply-ids" => "posts#reply_ids" get "posts/:id/reply-ids/all" => "posts#all_reply_ids" - get "posts/:username/deleted" => "posts#deleted_posts", constraints: { username: RouteFormat.username } - get "posts/:username/flagged" => "posts#flagged_posts", constraints: { username: RouteFormat.username } - get "posts/:username/pending" => "posts#pending", constraints: { username: RouteFormat.username } + get "posts/:username/deleted" => "posts#deleted_posts", + :constraints => { + username: RouteFormat.username, + } + get "posts/:username/flagged" => "posts#flagged_posts", + :constraints => { + username: RouteFormat.username, + } + get "posts/:username/pending" => "posts#pending", + :constraints => { + username: RouteFormat.username, + } - %w{groups g}.each do |root_path| + %w[groups g].each do |root_path| resources :groups, id: RouteFormat.username, path: root_path do - get "posts.rss" => "groups#posts_feed", format: :rss - get "mentions.rss" => "groups#mentions_feed", format: :rss + get "posts.rss" => "groups#posts_feed", :format => :rss + get "mentions.rss" => "groups#mentions_feed", :format => :rss - get 'members' - get 'posts' - get 'mentions' - get 'counts' - get 'mentionable' - get 'messageable' - get 'logs' => 'groups#histories' - post 'test_email_settings' + get "members" + get "posts" + get "mentions" + get "counts" + get "mentionable" + get "messageable" + get "logs" => "groups#histories" + post "test_email_settings" collection do - get "check-name" => 'groups#check_name' - get 'custom/new' => 'groups#new', constraints: StaffConstraint.new + get "check-name" => "groups#check_name" + get "custom/new" => "groups#new", :constraints => StaffConstraint.new get "search" => "groups#search" end member do - %w{ + %w[ activity activity/:filter requests @@ -646,9 +1048,7 @@ Discourse::Application.routes.draw do manage/categories manage/tags manage/logs - }.each do |path| - get path => 'groups#show' - end + ].each { |path| get path => "groups#show" } get "permissions" => "groups#permissions" put "members" => "groups#add_members" @@ -665,8 +1065,8 @@ Discourse::Application.routes.draw do resources :associated_groups, only: %i[index], constraints: AdminConstraint.new # aliases so old API code works - delete "admin/groups/:id/members" => "groups#remove_member", constraints: AdminConstraint.new - put "admin/groups/:id/members" => "groups#add_members", constraints: AdminConstraint.new + delete "admin/groups/:id/members" => "groups#remove_member", :constraints => AdminConstraint.new + put "admin/groups/:id/members" => "groups#add_members", :constraints => AdminConstraint.new resources :posts do delete "bookmark", to: "posts#destroy_bookmark" @@ -678,10 +1078,10 @@ Discourse::Application.routes.draw do put "notice" get "replies" get "revisions/latest" => "posts#latest_revision" - get "revisions/:revision" => "posts#revisions", constraints: { revision: /\d+/ } - put "revisions/:revision/hide" => "posts#hide_revision", constraints: { revision: /\d+/ } - put "revisions/:revision/show" => "posts#show_revision", constraints: { revision: /\d+/ } - put "revisions/:revision/revert" => "posts#revert", constraints: { revision: /\d+/ } + get "revisions/:revision" => "posts#revisions", :constraints => { revision: /\d+/ } + put "revisions/:revision/hide" => "posts#hide_revision", :constraints => { revision: /\d+/ } + put "revisions/:revision/show" => "posts#show_revision", :constraints => { revision: /\d+/ } + put "revisions/:revision/revert" => "posts#revert", :constraints => { revision: /\d+/ } put "recover" collection do delete "destroy_many" @@ -695,23 +1095,29 @@ Discourse::Application.routes.draw do resources :notifications, except: :show do collection do - put 'mark-read' => 'notifications#mark_read' + put "mark-read" => "notifications#mark_read" # creating an alias cause the api was extended to mark a single notification # this allows us to cleanly target it - put 'read' => 'notifications#mark_read' + put "read" => "notifications#mark_read" end end - match "/auth/failure", to: "users/omniauth_callbacks#failure", via: [:get, :post] + match "/auth/failure", to: "users/omniauth_callbacks#failure", via: %i[get post] get "/auth/:provider", to: "users/omniauth_callbacks#confirm_request" - match "/auth/:provider/callback", to: "users/omniauth_callbacks#complete", via: [:get, :post] - get "/associate/:token", to: "users/associate_accounts#connect_info", constraints: { token: /\h{32}/ } - post "/associate/:token", to: "users/associate_accounts#connect", constraints: { token: /\h{32}/ } + match "/auth/:provider/callback", to: "users/omniauth_callbacks#complete", via: %i[get post] + get "/associate/:token", + to: "users/associate_accounts#connect_info", + constraints: { + token: /\h{32}/, + } + post "/associate/:token", + to: "users/associate_accounts#connect", + constraints: { + token: /\h{32}/, + } resources :clicks do - collection do - post "track" - end + collection { post "track" } end get "excerpt" => "excerpt#show" @@ -727,17 +1133,17 @@ Discourse::Application.routes.draw do resources :user_actions resources :badges, only: [:index] - get "/badges/:id(/:slug)" => "badges#show", constraints: { format: /(json|html|rss)/ } - resources :user_badges, only: [:index, :create, :destroy] do - put "toggle_favorite" => "user_badges#toggle_favorite", constraints: { format: :json } + get "/badges/:id(/:slug)" => "badges#show", :constraints => { format: /(json|html|rss)/ } + resources :user_badges, only: %i[index create destroy] do + put "toggle_favorite" => "user_badges#toggle_favorite", :constraints => { format: :json } end - get '/c', to: redirect(relative_url_root + 'categories') + get "/c", to: redirect(relative_url_root + "categories") - resources :categories, except: [:show, :new, :edit] + resources :categories, except: %i[show new edit] post "categories/reorder" => "categories#reorder" - scope path: 'category/:category_id' do + scope path: "category/:category_id" do post "/move" => "categories#move" post "/notifications" => "categories#set_notifications" put "/slug" => "categories#update_slug" @@ -752,11 +1158,14 @@ Discourse::Application.routes.draw do get "c/:id/visible_groups" => "categories#visible_groups" get "c/*category_slug/find_by_slug" => "categories#find_by_slug" - get "c/*category_slug/edit(/:tab)" => "categories#find_by_slug", constraints: { format: 'html' } - get "/new-category" => "categories#show", constraints: { format: 'html' } + get "c/*category_slug/edit(/:tab)" => "categories#find_by_slug", + :constraints => { + format: "html", + } + get "/new-category" => "categories#show", :constraints => { format: "html" } - get "c/*category_slug_path_with_id.rss" => "list#category_feed", format: :rss - scope path: 'c/*category_slug_path_with_id' do + get "c/*category_slug_path_with_id.rss" => "list#category_feed", :format => :rss + scope path: "c/*category_slug_path_with_id" do get "/none" => "list#category_none_latest" TopTopic.periods.each do |period| @@ -765,12 +1174,16 @@ Discourse::Application.routes.draw do end Discourse.filters.each do |filter| - get "/none/l/#{filter}" => "list#category_none_#{filter}", as: "category_none_#{filter}" - get "/l/#{filter}" => "list#category_#{filter}", as: "category_#{filter}" + get "/none/l/#{filter}" => "list#category_none_#{filter}", :as => "category_none_#{filter}" + get "/l/#{filter}" => "list#category_#{filter}", :as => "category_#{filter}" end - get "/all" => "list#category_default", as: "category_all", constraints: { format: 'html' } - get "/" => "list#category_default", as: "category_default" + get "/all" => "list#category_default", + :as => "category_all", + :constraints => { + format: "html", + } + get "/" => "list#category_default", :as => "category_default" end get "hashtags" => "hashtags#lookup" @@ -783,12 +1196,10 @@ Discourse::Application.routes.draw do end Discourse.anonymous_filters.each do |filter| - get "#{filter}.rss" => "list##{filter}_feed", format: :rss + get "#{filter}.rss" => "list##{filter}_feed", :format => :rss end - Discourse.filters.each do |filter| - get "#{filter}" => "list##{filter}" - end + Discourse.filters.each { |filter| get "#{filter}" => "list##{filter}" } get "search/query" => "search#query" get "search" => "search#show" @@ -796,7 +1207,7 @@ Discourse::Application.routes.draw do # Topics resource get "t/:id" => "topics#show" - put "t/:topic_id" => "topics#update", constraints: { topic_id: /\d+/ } + put "t/:topic_id" => "topics#update", :constraints => { topic_id: /\d+/ } delete "t/:id" => "topics#destroy" put "t/:id/archive-message" => "topics#archive_message" put "t/:id/move-to-inbox" => "topics#move_to_inbox" @@ -805,89 +1216,191 @@ Discourse::Application.routes.draw do put "t/:id/shared-draft" => "topics#update_shared_draft" put "t/:id/reset-bump-date" => "topics#reset_bump_date" put "topics/bulk" - put "topics/reset-new" => 'topics#reset_new' - put "topics/pm-reset-new" => 'topics#private_message_reset_new' + put "topics/reset-new" => "topics#reset_new" + put "topics/pm-reset-new" => "topics#private_message_reset_new" post "topics/timings" - get 'topics/similar_to' => 'similar_topics#index' + get "topics/similar_to" => "similar_topics#index" resources :similar_topics get "topics/feature_stats" scope "/topics", username: RouteFormat.username do - get "created-by/:username" => "list#topics_by", as: "topics_by", defaults: { format: :json } - get "private-messages/:username" => "list#private_messages", as: "topics_private_messages", defaults: { format: :json } - get "private-messages-sent/:username" => "list#private_messages_sent", as: "topics_private_messages_sent", defaults: { format: :json } - get "private-messages-archive/:username" => "list#private_messages_archive", as: "topics_private_messages_archive", defaults: { format: :json } - get "private-messages-unread/:username" => "list#private_messages_unread", as: "topics_private_messages_unread", defaults: { format: :json } - get "private-messages-tags/:username/:tag_id.json" => "list#private_messages_tag", as: "topics_private_messages_tag", defaults: { format: :json } - get "private-messages-new/:username" => "list#private_messages_new", as: "topics_private_messages_new", defaults: { format: :json } - get "private-messages-warnings/:username" => "list#private_messages_warnings", as: "topics_private_messages_warnings", defaults: { format: :json } - get "groups/:group_name" => "list#group_topics", as: "group_topics", group_name: RouteFormat.username + get "created-by/:username" => "list#topics_by", + :as => "topics_by", + :defaults => { + format: :json, + } + get "private-messages/:username" => "list#private_messages", + :as => "topics_private_messages", + :defaults => { + format: :json, + } + get "private-messages-sent/:username" => "list#private_messages_sent", + :as => "topics_private_messages_sent", + :defaults => { + format: :json, + } + get "private-messages-archive/:username" => "list#private_messages_archive", + :as => "topics_private_messages_archive", + :defaults => { + format: :json, + } + get "private-messages-unread/:username" => "list#private_messages_unread", + :as => "topics_private_messages_unread", + :defaults => { + format: :json, + } + get "private-messages-tags/:username/:tag_id.json" => "list#private_messages_tag", + :as => "topics_private_messages_tag", + :defaults => { + format: :json, + } + get "private-messages-new/:username" => "list#private_messages_new", + :as => "topics_private_messages_new", + :defaults => { + format: :json, + } + get "private-messages-warnings/:username" => "list#private_messages_warnings", + :as => "topics_private_messages_warnings", + :defaults => { + format: :json, + } + get "groups/:group_name" => "list#group_topics", + :as => "group_topics", + :group_name => RouteFormat.username scope "/private-messages-group/:username", group_name: RouteFormat.username do - get ":group_name.json" => "list#private_messages_group", as: "topics_private_messages_group" - get ":group_name/archive.json" => "list#private_messages_group_archive", as: "topics_private_messages_group_archive" - get ":group_name/new.json" => "list#private_messages_group_new", as: "topics_private_messages_group_new" - get ":group_name/unread.json" => "list#private_messages_group_unread", as: "topics_private_messages_group_unread" + get ":group_name.json" => "list#private_messages_group", + :as => "topics_private_messages_group" + get ":group_name/archive.json" => "list#private_messages_group_archive", + :as => "topics_private_messages_group_archive" + get ":group_name/new.json" => "list#private_messages_group_new", + :as => "topics_private_messages_group_new" + get ":group_name/unread.json" => "list#private_messages_group_unread", + :as => "topics_private_messages_group_unread" end end - get 'embed/topics' => 'embed#topics' - get 'embed/comments' => 'embed#comments' - get 'embed/count' => 'embed#count' - get 'embed/info' => 'embed#info' + get "embed/topics" => "embed#topics" + get "embed/comments" => "embed#comments" + get "embed/count" => "embed#count" + get "embed/info" => "embed#info" get "new-topic" => "new_topic#index" get "new-message" => "new_topic#index" # Topic routes get "t/id_for/:slug" => "topics#id_for_slug" - get "t/external_id/:external_id" => "topics#show_by_external_id", format: :json, constraints: { external_id: /[\w-]+/ } - get "t/:slug/:topic_id/print" => "topics#show", format: :html, print: 'true', constraints: { topic_id: /\d+/ } - get "t/:slug/:topic_id/wordpress" => "topics#wordpress", constraints: { topic_id: /\d+/ } - get "t/:topic_id/wordpress" => "topics#wordpress", constraints: { topic_id: /\d+/ } - get "t/:slug/:topic_id/moderator-liked" => "topics#moderator_liked", constraints: { topic_id: /\d+/ } - get "t/:slug/:topic_id/summary" => "topics#show", defaults: { summary: true }, constraints: { topic_id: /\d+/ } - get "t/:topic_id/summary" => "topics#show", constraints: { topic_id: /\d+/ } - put "t/:slug/:topic_id" => "topics#update", constraints: { topic_id: /\d+/ } - put "t/:slug/:topic_id/star" => "topics#star", constraints: { topic_id: /\d+/ } - put "t/:topic_id/star" => "topics#star", constraints: { topic_id: /\d+/ } - put "t/:slug/:topic_id/status" => "topics#status", constraints: { topic_id: /\d+/ } - put "t/:topic_id/status" => "topics#status", constraints: { topic_id: /\d+/ } - put "t/:topic_id/clear-pin" => "topics#clear_pin", constraints: { topic_id: /\d+/ } - put "t/:topic_id/re-pin" => "topics#re_pin", constraints: { topic_id: /\d+/ } - put "t/:topic_id/mute" => "topics#mute", constraints: { topic_id: /\d+/ } - put "t/:topic_id/unmute" => "topics#unmute", constraints: { topic_id: /\d+/ } - post "t/:topic_id/timer" => "topics#timer", constraints: { topic_id: /\d+/ } - put "t/:topic_id/make-banner" => "topics#make_banner", constraints: { topic_id: /\d+/ } - put "t/:topic_id/remove-banner" => "topics#remove_banner", constraints: { topic_id: /\d+/ } - put "t/:topic_id/remove-allowed-user" => "topics#remove_allowed_user", constraints: { topic_id: /\d+/ } - put "t/:topic_id/remove-allowed-group" => "topics#remove_allowed_group", constraints: { topic_id: /\d+/ } - put "t/:topic_id/recover" => "topics#recover", constraints: { topic_id: /\d+/ } - get "t/:topic_id/:post_number" => "topics#show", constraints: { topic_id: /\d+/, post_number: /\d+/ } - get "t/:topic_id/last" => "topics#show", post_number: 99999999, constraints: { topic_id: /\d+/ } - get "t/:slug/:topic_id.rss" => "topics#feed", format: :rss, constraints: { topic_id: /\d+/ } - get "t/:slug/:topic_id" => "topics#show", constraints: { topic_id: /\d+/ } - get "t/:slug/:topic_id/:post_number" => "topics#show", constraints: { topic_id: /\d+/, post_number: /\d+/ } - get "t/:slug/:topic_id/last" => "topics#show", post_number: 99999999, constraints: { topic_id: /\d+/ } - get "t/:topic_id/posts" => "topics#posts", constraints: { topic_id: /\d+/ }, format: :json - get "t/:topic_id/post_ids" => "topics#post_ids", constraints: { topic_id: /\d+/ }, format: :json - get "t/:topic_id/excerpts" => "topics#excerpts", constraints: { topic_id: /\d+/ }, format: :json - post "t/:topic_id/timings" => "topics#timings", constraints: { topic_id: /\d+/ } - post "t/:topic_id/invite" => "topics#invite", constraints: { topic_id: /\d+/ } - post "t/:topic_id/invite-group" => "topics#invite_group", constraints: { topic_id: /\d+/ } - post "t/:topic_id/move-posts" => "topics#move_posts", constraints: { topic_id: /\d+/ } - post "t/:topic_id/merge-topic" => "topics#merge_topic", constraints: { topic_id: /\d+/ } - post "t/:topic_id/change-owner" => "topics#change_post_owners", constraints: { topic_id: /\d+/ } - put "t/:topic_id/change-timestamp" => "topics#change_timestamps", constraints: { topic_id: /\d+/ } - delete "t/:topic_id/timings" => "topics#destroy_timings", constraints: { topic_id: /\d+/ } - put "t/:topic_id/bookmark" => "topics#bookmark", constraints: { topic_id: /\d+/ } - put "t/:topic_id/remove_bookmarks" => "topics#remove_bookmarks", constraints: { topic_id: /\d+/ } - put "t/:topic_id/tags" => "topics#update_tags", constraints: { topic_id: /\d+/ } - put "t/:topic_id/slow_mode" => "topics#set_slow_mode", constraints: { topic_id: /\d+/ } + get "t/external_id/:external_id" => "topics#show_by_external_id", + :format => :json, + :constraints => { + external_id: /[\w-]+/, + } + get "t/:slug/:topic_id/print" => "topics#show", + :format => :html, + :print => "true", + :constraints => { + topic_id: /\d+/, + } + get "t/:slug/:topic_id/wordpress" => "topics#wordpress", :constraints => { topic_id: /\d+/ } + get "t/:topic_id/wordpress" => "topics#wordpress", :constraints => { topic_id: /\d+/ } + get "t/:slug/:topic_id/moderator-liked" => "topics#moderator_liked", + :constraints => { + topic_id: /\d+/, + } + get "t/:slug/:topic_id/summary" => "topics#show", + :defaults => { + summary: true, + }, + :constraints => { + topic_id: /\d+/, + } + get "t/:topic_id/summary" => "topics#show", :constraints => { topic_id: /\d+/ } + put "t/:slug/:topic_id" => "topics#update", :constraints => { topic_id: /\d+/ } + put "t/:slug/:topic_id/star" => "topics#star", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/star" => "topics#star", :constraints => { topic_id: /\d+/ } + put "t/:slug/:topic_id/status" => "topics#status", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/status" => "topics#status", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/clear-pin" => "topics#clear_pin", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/re-pin" => "topics#re_pin", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/mute" => "topics#mute", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/unmute" => "topics#unmute", :constraints => { topic_id: /\d+/ } + post "t/:topic_id/timer" => "topics#timer", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/make-banner" => "topics#make_banner", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/remove-banner" => "topics#remove_banner", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/remove-allowed-user" => "topics#remove_allowed_user", + :constraints => { + topic_id: /\d+/, + } + put "t/:topic_id/remove-allowed-group" => "topics#remove_allowed_group", + :constraints => { + topic_id: /\d+/, + } + put "t/:topic_id/recover" => "topics#recover", :constraints => { topic_id: /\d+/ } + get "t/:topic_id/:post_number" => "topics#show", + :constraints => { + topic_id: /\d+/, + post_number: /\d+/, + } + get "t/:topic_id/last" => "topics#show", + :post_number => 99_999_999, + :constraints => { + topic_id: /\d+/, + } + get "t/:slug/:topic_id.rss" => "topics#feed", + :format => :rss, + :constraints => { + topic_id: /\d+/, + } + get "t/:slug/:topic_id" => "topics#show", :constraints => { topic_id: /\d+/ } + get "t/:slug/:topic_id/:post_number" => "topics#show", + :constraints => { + topic_id: /\d+/, + post_number: /\d+/, + } + get "t/:slug/:topic_id/last" => "topics#show", + :post_number => 99_999_999, + :constraints => { + topic_id: /\d+/, + } + get "t/:topic_id/posts" => "topics#posts", :constraints => { topic_id: /\d+/ }, :format => :json + get "t/:topic_id/post_ids" => "topics#post_ids", + :constraints => { + topic_id: /\d+/, + }, + :format => :json + get "t/:topic_id/excerpts" => "topics#excerpts", + :constraints => { + topic_id: /\d+/, + }, + :format => :json + post "t/:topic_id/timings" => "topics#timings", :constraints => { topic_id: /\d+/ } + post "t/:topic_id/invite" => "topics#invite", :constraints => { topic_id: /\d+/ } + post "t/:topic_id/invite-group" => "topics#invite_group", :constraints => { topic_id: /\d+/ } + post "t/:topic_id/move-posts" => "topics#move_posts", :constraints => { topic_id: /\d+/ } + post "t/:topic_id/merge-topic" => "topics#merge_topic", :constraints => { topic_id: /\d+/ } + post "t/:topic_id/change-owner" => "topics#change_post_owners", + :constraints => { + topic_id: /\d+/, + } + put "t/:topic_id/change-timestamp" => "topics#change_timestamps", + :constraints => { + topic_id: /\d+/, + } + delete "t/:topic_id/timings" => "topics#destroy_timings", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/bookmark" => "topics#bookmark", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/remove_bookmarks" => "topics#remove_bookmarks", + :constraints => { + topic_id: /\d+/, + } + put "t/:topic_id/tags" => "topics#update_tags", :constraints => { topic_id: /\d+/ } + put "t/:topic_id/slow_mode" => "topics#set_slow_mode", :constraints => { topic_id: /\d+/ } - post "t/:topic_id/notifications" => "topics#set_notifications" , constraints: { topic_id: /\d+/ } + post "t/:topic_id/notifications" => "topics#set_notifications", + :constraints => { + topic_id: /\d+/, + } get "p/:post_id(/:user_id)" => "posts#short_link" get "/posts/:id/cooked" => "posts#cooked" @@ -897,7 +1410,7 @@ Discourse::Application.routes.draw do get "raw/:topic_id(/:post_number)" => "posts#markdown_num" resources :invites, except: [:show] - get "/invites/:id" => "invites#show", constraints: { format: :html } + get "/invites/:id" => "invites#show", :constraints => { format: :html } put "/invites/:id" => "invites#update" post "invites/upload_csv" => "invites#upload_csv" @@ -905,13 +1418,11 @@ Discourse::Application.routes.draw do post "invites/reinvite" => "invites#resend_invite" post "invites/reinvite-all" => "invites#resend_all_invites" delete "invites" => "invites#destroy" - put "invites/show/:id" => "invites#perform_accept_invitation", as: 'perform_accept_invite' + put "invites/show/:id" => "invites#perform_accept_invitation", :as => "perform_accept_invite" get "invites/retrieve" => "invites#retrieve" resources :export_csv do - collection do - post "export_entity" => "export_csv#export_entity" - end + collection { post "export_entity" => "export_csv#export_entity" } end get "onebox" => "onebox#show" @@ -921,95 +1432,129 @@ Discourse::Application.routes.draw do get "message-bus/poll" => "message_bus#poll" - resources :drafts, only: [:index, :create, :show, :destroy] + resources :drafts, only: %i[index create show destroy] - get "/service-worker.js" => "static#service_worker_asset", format: :js - if service_worker_asset = Rails.application.assets_manifest.assets['service-worker.js'] + get "/service-worker.js" => "static#service_worker_asset", :format => :js + if service_worker_asset = Rails.application.assets_manifest.assets["service-worker.js"] # https://developers.google.com/web/fundamentals/codelabs/debugging-service-workers/ # Normally the browser will wait until a user closes all tabs that contain the # current site before updating to a new Service Worker. # Support the old Service Worker path to avoid routing error filling up the # logs. - get service_worker_asset => "static#service_worker_asset", format: :js + get service_worker_asset => "static#service_worker_asset", :format => :js end - get "cdn_asset/:site/*path" => "static#cdn_asset", format: false, constraints: { format: /.*/ } - get "brotli_asset/*path" => "static#brotli_asset", format: false, constraints: { format: /.*/ } + get "cdn_asset/:site/*path" => "static#cdn_asset", + :format => false, + :constraints => { + format: /.*/, + } + get "brotli_asset/*path" => "static#brotli_asset", + :format => false, + :constraints => { + format: /.*/, + } - get "favicon/proxied" => "static#favicon", format: false + get "favicon/proxied" => "static#favicon", :format => false get "robots.txt" => "robots_txt#index" get "robots-builder.json" => "robots_txt#builder" get "offline.html" => "offline#index" - get "manifest.webmanifest" => "metadata#manifest", as: :manifest + get "manifest.webmanifest" => "metadata#manifest", :as => :manifest get "manifest.json" => "metadata#manifest" get ".well-known/assetlinks.json" => "metadata#app_association_android" - get "apple-app-site-association" => "metadata#app_association_ios", format: false - get "opensearch" => "metadata#opensearch", constraints: { format: :xml } + get "apple-app-site-association" => "metadata#app_association_ios", :format => false + get "opensearch" => "metadata#opensearch", :constraints => { format: :xml } - scope '/tag/:tag_id' do + scope "/tag/:tag_id" do constraints format: :json do - get '/' => 'tags#show', as: 'tag_show' - get '/info' => 'tags#info' - get '/notifications' => 'tags#notifications' - put '/notifications' => 'tags#update_notifications' - put '/' => 'tags#update' - delete '/' => 'tags#destroy' - post '/synonyms' => 'tags#create_synonyms' - delete '/synonyms/:synonym_id' => 'tags#destroy_synonym' + get "/" => "tags#show", :as => "tag_show" + get "/info" => "tags#info" + get "/notifications" => "tags#notifications" + put "/notifications" => "tags#update_notifications" + put "/" => "tags#update" + delete "/" => "tags#destroy" + post "/synonyms" => "tags#create_synonyms" + delete "/synonyms/:synonym_id" => "tags#destroy_synonym" Discourse.filters.each do |filter| - get "/l/#{filter}" => "tags#show_#{filter}", as: "tag_show_#{filter}" + get "/l/#{filter}" => "tags#show_#{filter}", :as => "tag_show_#{filter}" end end constraints format: :rss do - get '/' => 'tags#tag_feed' + get "/" => "tags#tag_feed" end end scope "/tags" do - get '/' => 'tags#index' - get '/filter/list' => 'tags#index' - get '/filter/search' => 'tags#search' - get '/personal_messages/:username' => 'tags#personal_messages', constraints: { username: RouteFormat.username } - post '/upload' => 'tags#upload' - get '/unused' => 'tags#list_unused' - delete '/unused' => 'tags#destroy_unused' + get "/" => "tags#index" + get "/filter/list" => "tags#index" + get "/filter/search" => "tags#search" + get "/personal_messages/:username" => "tags#personal_messages", + :constraints => { + username: RouteFormat.username, + } + post "/upload" => "tags#upload" + get "/unused" => "tags#list_unused" + delete "/unused" => "tags#destroy_unused" - constraints(tag_id: /[^\/]+?/, format: /json|rss/) do - scope path: '/c/*category_slug_path_with_id' do + constraints(tag_id: %r{[^/]+?}, format: /json|rss/) do + scope path: "/c/*category_slug_path_with_id" do Discourse.filters.each do |filter| - get "/none/:tag_id/l/#{filter}" => "tags#show_#{filter}", as: "tag_category_none_show_#{filter}", defaults: { no_subcategories: true } - get "/all/:tag_id/l/#{filter}" => "tags#show_#{filter}", as: "tag_category_all_show_#{filter}", defaults: { no_subcategories: false } + get "/none/:tag_id/l/#{filter}" => "tags#show_#{filter}", + :as => "tag_category_none_show_#{filter}", + :defaults => { + no_subcategories: true, + } + get "/all/:tag_id/l/#{filter}" => "tags#show_#{filter}", + :as => "tag_category_all_show_#{filter}", + :defaults => { + no_subcategories: false, + } end - get '/none/:tag_id' => 'tags#show', as: 'tag_category_none_show', defaults: { no_subcategories: true } - get '/all/:tag_id' => 'tags#show', as: 'tag_category_all_show', defaults: { no_subcategories: false } + get "/none/:tag_id" => "tags#show", + :as => "tag_category_none_show", + :defaults => { + no_subcategories: true, + } + get "/all/:tag_id" => "tags#show", + :as => "tag_category_all_show", + :defaults => { + no_subcategories: false, + } Discourse.filters.each do |filter| - get "/:tag_id/l/#{filter}" => "tags#show_#{filter}", as: "tag_category_show_#{filter}" + get "/:tag_id/l/#{filter}" => "tags#show_#{filter}", + :as => "tag_category_show_#{filter}" end - get '/:tag_id' => 'tags#show', as: 'tag_category_show' + get "/:tag_id" => "tags#show", :as => "tag_category_show" end - get '/intersection/:tag_id/*additional_tag_ids' => 'tags#show', as: 'tag_intersection' + get "/intersection/:tag_id/*additional_tag_ids" => "tags#show", :as => "tag_intersection" end - get '*tag_id', to: redirect(relative_url_root + 'tag/%{tag_id}') + get "*tag_id", to: redirect(relative_url_root + "tag/%{tag_id}") end resources :tag_groups, constraints: StaffConstraint.new, except: [:edit] - get '/tag_groups/filter/search' => 'tag_groups#search', format: :json + get "/tag_groups/filter/search" => "tag_groups#search", :format => :json Discourse.filters.each do |filter| - root to: "list##{filter}", constraints: HomePageConstraint.new("#{filter}"), as: "list_#{filter}" + root to: "list##{filter}", + constraints: HomePageConstraint.new("#{filter}"), + as: "list_#{filter}" end # special case for categories - root to: "categories#index", constraints: HomePageConstraint.new("categories"), as: "categories_index" + root to: "categories#index", + constraints: HomePageConstraint.new("categories"), + as: "categories_index" - root to: 'finish_installation#index', constraints: HomePageConstraint.new("finish_installation"), as: 'installation_redirect' + root to: "finish_installation#index", + constraints: HomePageConstraint.new("finish_installation"), + as: "installation_redirect" get "/user-api-key/new" => "user_api_keys#new" post "/user-api-key" => "user_api_keys#create" @@ -1019,7 +1564,7 @@ Discourse::Application.routes.draw do post "/user-api-key/otp" => "user_api_keys#create_otp" get "/safe-mode" => "safe_mode#index" - post "/safe-mode" => "safe_mode#enter", as: "safe_mode_enter" + post "/safe-mode" => "safe_mode#enter", :as => "safe_mode_enter" get "/theme-qunit" => "qunit#theme" @@ -1028,7 +1573,7 @@ Discourse::Application.routes.draw do resources :csp_reports, only: [:create] - get "/permalink-check", to: 'permalinks#check' + get "/permalink-check", to: "permalinks#check" post "/do-not-disturb" => "do_not_disturb#create" delete "/do-not-disturb" => "do_not_disturb#destroy" @@ -1040,6 +1585,6 @@ Discourse::Application.routes.draw do put "user-status" => "user_status#set" delete "user-status" => "user_status#clear" - get "*url", to: 'permalinks#show', constraints: PermalinkConstraint.new + get "*url", to: "permalinks#show", constraints: PermalinkConstraint.new end end diff --git a/config/spring.rb b/config/spring.rb index 68b1b41a33..8d12edf4a8 100644 --- a/config/spring.rb +++ b/config/spring.rb @@ -7,7 +7,5 @@ # spring binstub rails # spring binstub rake # spring binstub rspec -Spring.after_fork do - Discourse.after_fork -end +Spring.after_fork { Discourse.after_fork } Spring::Commands::Rake.environment_matchers["spec"] = "test" diff --git a/config/unicorn.conf.rb b/config/unicorn.conf.rb index e69979adfe..2e0b22f48f 100644 --- a/config/unicorn.conf.rb +++ b/config/unicorn.conf.rb @@ -3,9 +3,9 @@ # See http://unicorn.bogomips.org/Unicorn/Configurator.html if (ENV["LOGSTASH_UNICORN_URI"] || "").length > 0 - require_relative '../lib/discourse_logstash_logger' - require_relative '../lib/unicorn_logstash_patch' - logger DiscourseLogstashLogger.logger(uri: ENV['LOGSTASH_UNICORN_URI'], type: :unicorn) + require_relative "../lib/discourse_logstash_logger" + require_relative "../lib/unicorn_logstash_patch" + logger DiscourseLogstashLogger.logger(uri: ENV["LOGSTASH_UNICORN_URI"], type: :unicorn) end discourse_path = File.expand_path(File.expand_path(File.dirname(__FILE__)) + "/../") @@ -16,11 +16,10 @@ worker_processes (ENV["UNICORN_WORKERS"] || 3).to_i working_directory discourse_path # listen "#{discourse_path}/tmp/sockets/unicorn.sock" -listen ENV["UNICORN_LISTENER"] || "#{(ENV["UNICORN_BIND_ALL"] ? "" : "127.0.0.1:")}#{(ENV["UNICORN_PORT"] || 3000).to_i}" +listen ENV["UNICORN_LISTENER"] || + "#{(ENV["UNICORN_BIND_ALL"] ? "" : "127.0.0.1:")}#{(ENV["UNICORN_PORT"] || 3000).to_i}" -if !File.exist?("#{discourse_path}/tmp/pids") - FileUtils.mkdir_p("#{discourse_path}/tmp/pids") -end +FileUtils.mkdir_p("#{discourse_path}/tmp/pids") if !File.exist?("#{discourse_path}/tmp/pids") # feel free to point this anywhere accessible on the filesystem pid (ENV["UNICORN_PID_PATH"] || "#{discourse_path}/tmp/pids/unicorn.pid") @@ -52,7 +51,6 @@ check_client_connection false initialized = false before_fork do |server, worker| - unless initialized Discourse.preload_rails! @@ -67,7 +65,7 @@ before_fork do |server, worker| initialized = true - supervisor = ENV['UNICORN_SUPERVISOR_PID'].to_i + supervisor = ENV["UNICORN_SUPERVISOR_PID"].to_i if supervisor > 0 Thread.new do while true @@ -80,14 +78,12 @@ before_fork do |server, worker| end end - sidekiqs = ENV['UNICORN_SIDEKIQS'].to_i + sidekiqs = ENV["UNICORN_SIDEKIQS"].to_i if sidekiqs > 0 server.logger.info "starting #{sidekiqs} supervised sidekiqs" - require 'demon/sidekiq' - Demon::Sidekiq.after_fork do - DiscourseEvent.trigger(:sidekiq_fork_started) - end + require "demon/sidekiq" + Demon::Sidekiq.after_fork { DiscourseEvent.trigger(:sidekiq_fork_started) } Demon::Sidekiq.start(sidekiqs) @@ -98,13 +94,14 @@ before_fork do |server, worker| # Trap USR1, so we can re-issue to sidekiq workers # but chain the default unicorn implementation as well - old_handler = Signal.trap("USR1") do - Demon::Sidekiq.kill("USR1") - old_handler.call - end + old_handler = + Signal.trap("USR1") do + Demon::Sidekiq.kill("USR1") + old_handler.call + end end - if ENV['DISCOURSE_ENABLE_EMAIL_SYNC_DEMON'] == 'true' + if ENV["DISCOURSE_ENABLE_EMAIL_SYNC_DEMON"] == "true" server.logger.info "starting up EmailSync demon" Demon::EmailSync.start Signal.trap("SIGTSTP") do @@ -119,13 +116,13 @@ before_fork do |server, worker| end class ::Unicorn::HttpServer - alias :master_sleep_orig :master_sleep + alias master_sleep_orig master_sleep def max_sidekiq_rss - rss = `ps -eo rss,args | grep sidekiq | grep -v grep | awk '{print $1}'` - .split("\n") - .map(&:to_i) - .max + rss = + `ps -eo rss,args | grep sidekiq | grep -v grep | awk '{print $1}'`.split("\n") + .map(&:to_i) + .max rss ||= 0 @@ -133,18 +130,24 @@ before_fork do |server, worker| end def max_allowed_sidekiq_rss - [ENV['UNICORN_SIDEKIQ_MAX_RSS'].to_i, 500].max.megabytes + [ENV["UNICORN_SIDEKIQ_MAX_RSS"].to_i, 500].max.megabytes end def force_kill_rogue_sidekiq info = `ps -eo pid,rss,args | grep sidekiq | grep -v grep | awk '{print $1,$2}'` - info.split("\n").each do |row| - pid, mem = row.split(" ").map(&:to_i) - if pid > 0 && (mem * 1024) > max_allowed_sidekiq_rss - Rails.logger.warn "Detected rogue Sidekiq pid #{pid} mem #{mem * 1024}, killing" - Process.kill("KILL", pid) rescue nil + info + .split("\n") + .each do |row| + pid, mem = row.split(" ").map(&:to_i) + if pid > 0 && (mem * 1024) > max_allowed_sidekiq_rss + Rails.logger.warn "Detected rogue Sidekiq pid #{pid} mem #{mem * 1024}, killing" + begin + Process.kill("KILL", pid) + rescue StandardError + nil + end + end end - end end def check_sidekiq_heartbeat @@ -152,13 +155,15 @@ before_fork do |server, worker| @sidekiq_next_heartbeat_check ||= Time.now.to_i + @sidekiq_heartbeat_interval if @sidekiq_next_heartbeat_check < Time.now.to_i - last_heartbeat = Jobs::RunHeartbeat.last_heartbeat restart = false sidekiq_rss = max_sidekiq_rss if sidekiq_rss > max_allowed_sidekiq_rss - Rails.logger.warn("Sidekiq is consuming too much memory (using: %0.2fM) for '%s', restarting" % [(sidekiq_rss.to_f / 1.megabyte), ENV["DISCOURSE_HOSTNAME"]]) + Rails.logger.warn( + "Sidekiq is consuming too much memory (using: %0.2fM) for '%s', restarting" % + [(sidekiq_rss.to_f / 1.megabyte), ENV["DISCOURSE_HOSTNAME"]], + ) restart = true end @@ -185,16 +190,18 @@ before_fork do |server, worker| email_sync_pids = Demon::EmailSync.demons.map { |uid, demon| demon.pid } return 0 if email_sync_pids.empty? - rss = `ps -eo pid,rss,args | grep '#{email_sync_pids.join('|')}' | grep -v grep | awk '{print $2}'` - .split("\n") - .map(&:to_i) - .max + rss = + `ps -eo pid,rss,args | grep '#{email_sync_pids.join("|")}' | grep -v grep | awk '{print $2}'`.split( + "\n", + ) + .map(&:to_i) + .max (rss || 0) * 1024 end def max_allowed_email_sync_rss - [ENV['UNICORN_EMAIL_SYNC_MAX_RSS'].to_i, 500].max.megabytes + [ENV["UNICORN_EMAIL_SYNC_MAX_RSS"].to_i, 500].max.megabytes end def check_email_sync_heartbeat @@ -207,16 +214,22 @@ before_fork do |server, worker| restart = false # Restart process if it does not respond anymore - last_heartbeat_ago = Time.now.to_i - Discourse.redis.get(Demon::EmailSync::HEARTBEAT_KEY).to_i + last_heartbeat_ago = + Time.now.to_i - Discourse.redis.get(Demon::EmailSync::HEARTBEAT_KEY).to_i if last_heartbeat_ago > Demon::EmailSync::HEARTBEAT_INTERVAL.to_i - STDERR.puts("EmailSync heartbeat test failed (last heartbeat was #{last_heartbeat_ago}s ago), restarting") + STDERR.puts( + "EmailSync heartbeat test failed (last heartbeat was #{last_heartbeat_ago}s ago), restarting", + ) restart = true end # Restart process if memory usage is too high email_sync_rss = max_email_sync_rss if email_sync_rss > max_allowed_email_sync_rss - STDERR.puts("EmailSync is consuming too much memory (using: %0.2fM) for '%s', restarting" % [(email_sync_rss.to_f / 1.megabyte), ENV["DISCOURSE_HOSTNAME"]]) + STDERR.puts( + "EmailSync is consuming too much memory (using: %0.2fM) for '%s', restarting" % + [(email_sync_rss.to_f / 1.megabyte), ENV["DISCOURSE_HOSTNAME"]], + ) restart = true end @@ -224,25 +237,22 @@ before_fork do |server, worker| end def master_sleep(sec) - sidekiqs = ENV['UNICORN_SIDEKIQS'].to_i + sidekiqs = ENV["UNICORN_SIDEKIQS"].to_i if sidekiqs > 0 Demon::Sidekiq.ensure_running check_sidekiq_heartbeat end - if ENV['DISCOURSE_ENABLE_EMAIL_SYNC_DEMON'] == 'true' + if ENV["DISCOURSE_ENABLE_EMAIL_SYNC_DEMON"] == "true" Demon::EmailSync.ensure_running check_email_sync_heartbeat end - DiscoursePluginRegistry.demon_processes.each do |demon_class| - demon_class.ensure_running - end + DiscoursePluginRegistry.demon_processes.each { |demon_class| demon_class.ensure_running } master_sleep_orig(sec) end end - end Discourse.redis.close From 0cf6421716d0908da57ad7743a2decb08588b48a Mon Sep 17 00:00:00 2001 From: David Taylor Date: Sat, 7 Jan 2023 12:02:29 +0000 Subject: [PATCH 30/53] DEV: Apply syntax_tree formatting to `Gemfile` --- .streerc | 1 - Gemfile | 287 ++++++++++++++++++++++++++++--------------------------- 2 files changed, 145 insertions(+), 143 deletions(-) diff --git a/.streerc b/.streerc index c385bd0b7c..615e88d6e2 100644 --- a/.streerc +++ b/.streerc @@ -1,6 +1,5 @@ --print-width=100 --plugins=plugin/trailing_comma,disable_ternary ---ignore-files=Gemfile --ignore-files=app/* --ignore-files=db/* --ignore-files=lib/* diff --git a/Gemfile b/Gemfile index 1069509ac6..ef77228f74 100644 --- a/Gemfile +++ b/Gemfile @@ -1,51 +1,51 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" # if there is a super emergency and rubygems is playing up, try #source 'http://production.cf.rubygems.org' -gem 'bootsnap', require: false, platform: :mri +gem "bootsnap", require: false, platform: :mri def rails_master? - ENV["RAILS_MASTER"] == '1' + ENV["RAILS_MASTER"] == "1" end if rails_master? - gem 'arel', git: 'https://github.com/rails/arel.git' - gem 'rails', git: 'https://github.com/rails/rails.git' + gem "arel", git: "https://github.com/rails/arel.git" + gem "rails", git: "https://github.com/rails/rails.git" else # NOTE: Until rubygems gives us optional dependencies we are stuck with this needing to be explicit # this allows us to include the bits of rails we use without pieces we do not. # # To issue a rails update bump the version number here - rails_version = '7.0.3.1' - gem 'actionmailer', rails_version - gem 'actionpack', rails_version - gem 'actionview', rails_version - gem 'activemodel', rails_version - gem 'activerecord', rails_version - gem 'activesupport', rails_version - gem 'railties', rails_version - gem 'sprockets-rails' + rails_version = "7.0.3.1" + gem "actionmailer", rails_version + gem "actionpack", rails_version + gem "actionview", rails_version + gem "activemodel", rails_version + gem "activerecord", rails_version + gem "activesupport", rails_version + gem "railties", rails_version + gem "sprockets-rails" end -gem 'json' +gem "json" # TODO: At the moment Discourse does not work with Sprockets 4, we would need to correct internals # This is a desired upgrade we should get to. -gem 'sprockets', '3.7.2' +gem "sprockets", "3.7.2" # this will eventually be added to rails, # allows us to precompile all our templates in the unicorn master -gem 'actionview_precompiler', require: false +gem "actionview_precompiler", require: false -gem 'discourse-seed-fu' +gem "discourse-seed-fu" -gem 'mail', git: 'https://github.com/discourse/mail.git' -gem 'mini_mime' -gem 'mini_suffix' +gem "mail", git: "https://github.com/discourse/mail.git" +gem "mini_mime" +gem "mini_suffix" -gem 'redis' +gem "redis" # This is explicitly used by Sidekiq and is an optional dependency. # We tell Sidekiq to use the namespace "sidekiq" which triggers this @@ -53,79 +53,79 @@ gem 'redis' # redis namespace support is optional # We already namespace stuff in DiscourseRedis, so we should consider # just using a single implementation in core vs having 2 namespace implementations -gem 'redis-namespace' +gem "redis-namespace" # NOTE: AM serializer gets a lot slower with recent updates # we used an old branch which is the fastest one out there # are long term goal here is to fork this gem so we have a # better maintained living fork -gem 'active_model_serializers', '~> 0.8.3' +gem "active_model_serializers", "~> 0.8.3" -gem 'http_accept_language', require: false +gem "http_accept_language", require: false -gem 'discourse-fonts', require: 'discourse_fonts' +gem "discourse-fonts", require: "discourse_fonts" -gem 'message_bus' +gem "message_bus" -gem 'rails_multisite' +gem "rails_multisite" -gem 'fast_xs', platform: :ruby +gem "fast_xs", platform: :ruby -gem 'xorcist' +gem "xorcist" -gem 'fastimage' +gem "fastimage" -gem 'aws-sdk-s3', require: false -gem 'aws-sdk-sns', require: false -gem 'excon', require: false -gem 'unf', require: false +gem "aws-sdk-s3", require: false +gem "aws-sdk-sns", require: false +gem "excon", require: false +gem "unf", require: false -gem 'email_reply_trimmer' +gem "email_reply_trimmer" -gem 'image_optim' -gem 'multi_json' -gem 'mustache' -gem 'nokogiri' -gem 'loofah' -gem 'css_parser', require: false +gem "image_optim" +gem "multi_json" +gem "mustache" +gem "nokogiri" +gem "loofah" +gem "css_parser", require: false -gem 'omniauth' -gem 'omniauth-facebook' -gem 'omniauth-twitter' -gem 'omniauth-github' +gem "omniauth" +gem "omniauth-facebook" +gem "omniauth-twitter" +gem "omniauth-github" -gem 'omniauth-oauth2', require: false +gem "omniauth-oauth2", require: false -gem 'omniauth-google-oauth2' +gem "omniauth-google-oauth2" # pending: https://github.com/ohler55/oj/issues/789 -gem 'oj', '3.13.14' +gem "oj", "3.13.14" -gem 'pg' -gem 'mini_sql' -gem 'pry-rails', require: false -gem 'pry-byebug', require: false -gem 'r2', require: false -gem 'rake' +gem "pg" +gem "mini_sql" +gem "pry-rails", require: false +gem "pry-byebug", require: false +gem "r2", require: false +gem "rake" -gem 'thor', require: false -gem 'diffy', require: false -gem 'rinku' -gem 'sidekiq' -gem 'mini_scheduler' +gem "thor", require: false +gem "diffy", require: false +gem "rinku" +gem "sidekiq" +gem "mini_scheduler" -gem 'execjs', require: false -gem 'mini_racer' +gem "execjs", require: false +gem "mini_racer" -gem 'highline', require: false +gem "highline", require: false -gem 'rack' +gem "rack" -gem 'rack-protection' # security -gem 'cbor', require: false -gem 'cose', require: false -gem 'addressable' -gem 'json_schemer' +gem "rack-protection" # security +gem "cbor", require: false +gem "cose", require: false +gem "addressable" +gem "json_schemer" gem "net-smtp", require: false gem "net-imap", require: false @@ -135,149 +135,152 @@ gem "digest", require: false # Gems used only for assets and not required in production environments by default. # Allow everywhere for now cause we are allowing asset debugging in production group :assets do - gem 'uglifier' + gem "uglifier" end group :test do - gem 'capybara', require: false - gem 'webmock', require: false - gem 'fakeweb', require: false - gem 'minitest', require: false - gem 'simplecov', require: false - gem 'selenium-webdriver', require: false + gem "capybara", require: false + gem "webmock", require: false + gem "fakeweb", require: false + gem "minitest", require: false + gem "simplecov", require: false + gem "selenium-webdriver", require: false gem "test-prof" - gem 'webdrivers', require: false + gem "webdrivers", require: false end group :test, :development do - gem 'rspec' - gem 'listen', require: false - gem 'certified', require: false - gem 'fabrication', require: false - gem 'mocha', require: false + gem "rspec" + gem "listen", require: false + gem "certified", require: false + gem "fabrication", require: false + gem "mocha", require: false - gem 'rb-fsevent', require: RUBY_PLATFORM =~ /darwin/i ? 'rb-fsevent' : false + gem "rb-fsevent", require: RUBY_PLATFORM =~ /darwin/i ? "rb-fsevent" : false - gem 'rspec-rails' + gem "rspec-rails" - gem 'shoulda-matchers', require: false - gem 'rspec-html-matchers' - gem 'byebug', require: ENV['RM_INFO'].nil?, platform: :mri - gem 'rubocop-discourse', require: false - gem 'parallel_tests' + gem "shoulda-matchers", require: false + gem "rspec-html-matchers" + gem "byebug", require: ENV["RM_INFO"].nil?, platform: :mri + gem "rubocop-discourse", require: false + gem "parallel_tests" - gem 'rswag-specs' + gem "rswag-specs" - gem 'annotate' + gem "annotate" gem "syntax_tree" - gem 'syntax_tree-disable_ternary' + gem "syntax_tree-disable_ternary" end group :development do - gem 'ruby-prof', require: false, platform: :mri - gem 'bullet', require: !!ENV['BULLET'] - gem 'better_errors', platform: :mri, require: !!ENV['BETTER_ERRORS'] - gem 'binding_of_caller' - gem 'yaml-lint' + gem "ruby-prof", require: false, platform: :mri + gem "bullet", require: !!ENV["BULLET"] + gem "better_errors", platform: :mri, require: !!ENV["BETTER_ERRORS"] + gem "binding_of_caller" + gem "yaml-lint" end if ENV["ALLOW_DEV_POPULATE"] == "1" - gem 'discourse_dev_assets' - gem 'faker', "~> 2.16" + gem "discourse_dev_assets" + gem "faker", "~> 2.16" else group :development, :test do - gem 'discourse_dev_assets' - gem 'faker', "~> 2.16" + gem "discourse_dev_assets" + gem "faker", "~> 2.16" end end # this is an optional gem, it provides a high performance replacement # to String#blank? a method that is called quite frequently in current # ActiveRecord, this may change in the future -gem 'fast_blank', platform: :ruby +gem "fast_blank", platform: :ruby # this provides a very efficient lru cache -gem 'lru_redux' +gem "lru_redux" -gem 'htmlentities', require: false +gem "htmlentities", require: false # IMPORTANT: mini profiler monkey patches, so it better be required last # If you want to amend mini profiler to do the monkey patches in the railties # we are open to it. by deferring require to the initializer we can configure discourse installs without it -gem 'rack-mini-profiler', require: ['enable_rails_patches'] +gem "rack-mini-profiler", require: ["enable_rails_patches"] -gem 'unicorn', require: false, platform: :ruby -gem 'puma', require: false -gem 'rbtrace', require: false, platform: :mri -gem 'gc_tracer', require: false, platform: :mri +gem "unicorn", require: false, platform: :ruby +gem "puma", require: false +gem "rbtrace", require: false, platform: :mri +gem "gc_tracer", require: false, platform: :mri # required for feed importing and embedding -gem 'ruby-readability', require: false +gem "ruby-readability", require: false # rss gem is a bundled gem from Ruby 3 onwards -gem 'rss', require: false +gem "rss", require: false -gem 'stackprof', require: false, platform: :mri -gem 'memory_profiler', require: false, platform: :mri +gem "stackprof", require: false, platform: :mri +gem "memory_profiler", require: false, platform: :mri -gem 'cppjieba_rb', require: false +gem "cppjieba_rb", require: false -gem 'lograge', require: false -gem 'logstash-event', require: false -gem 'logstash-logger', require: false -gem 'logster' +gem "lograge", require: false +gem "logstash-event", require: false +gem "logstash-logger", require: false +gem "logster" # NOTE: later versions of sassc are causing a segfault, possibly dependent on processer architecture # and until resolved should be locked at 2.0.1 -gem 'sassc', '2.0.1', require: false +gem "sassc", "2.0.1", require: false gem "sassc-rails" -gem 'rotp', require: false +gem "rotp", require: false -gem 'rqrcode' +gem "rqrcode" -gem 'rubyzip', require: false +gem "rubyzip", require: false -gem 'sshkey', require: false +gem "sshkey", require: false -gem 'rchardet', require: false -gem 'lz4-ruby', require: false, platform: :ruby +gem "rchardet", require: false +gem "lz4-ruby", require: false, platform: :ruby -gem 'sanitize' +gem "sanitize" if ENV["IMPORT"] == "1" - gem 'mysql2' - gem 'redcarpet' + gem "mysql2" + gem "redcarpet" # NOTE: in import mode the version of sqlite can matter a lot, so we stick it to a specific one - gem 'sqlite3', '~> 1.3', '>= 1.3.13' - gem 'ruby-bbcode-to-md', git: 'https://github.com/nlalonde/ruby-bbcode-to-md' - gem 'reverse_markdown' - gem 'tiny_tds' - gem 'csv' + gem "sqlite3", "~> 1.3", ">= 1.3.13" + gem "ruby-bbcode-to-md", git: "https://github.com/nlalonde/ruby-bbcode-to-md" + gem "reverse_markdown" + gem "tiny_tds" + gem "csv" - gem 'parallel', require: false + gem "parallel", require: false end # workaround for openssl 3.0, see # https://github.com/pushpad/web-push/pull/2 -gem 'web-push', require: false, git: 'https://github.com/xfalcox/web-push', branch: 'openssl-3-compat' -gem 'colored2', require: false -gem 'maxminddb' +gem "web-push", + require: false, + git: "https://github.com/xfalcox/web-push", + branch: "openssl-3-compat" +gem "colored2", require: false +gem "maxminddb" -gem 'rails_failover', require: false +gem "rails_failover", require: false -gem 'faraday' -gem 'faraday-retry' +gem "faraday" +gem "faraday-retry" # workaround for faraday-net_http, see # https://github.com/ruby/net-imap/issues/16#issuecomment-803086765 -gem 'net-http' +gem "net-http" # workaround for prometheus-client -gem 'webrick', require: false +gem "webrick", require: false # Workaround until Ruby ships with cgi version 0.3.6 or higher. gem "cgi", ">= 0.3.6", require: false From cb932d6ee1b3b3571e4d4d9118635e2dbf58f0ef Mon Sep 17 00:00:00 2001 From: David Taylor Date: Mon, 9 Jan 2023 11:18:21 +0000 Subject: [PATCH 31/53] DEV: Apply syntax_tree formatting to `spec/*` --- .streerc | 1 - spec/fabricators/allowed_pm_users.rb | 4 +- spec/fabricators/api_key_fabricator.rb | 4 +- .../associated_group_fabricator.rb | 2 +- spec/fabricators/badge_fabricator.rb | 4 +- spec/fabricators/bookmark_fabricator.rb | 15 +- spec/fabricators/category_fabricator.rb | 11 +- spec/fabricators/color_scheme_fabricator.rb | 4 +- .../external_upload_stub_fabricator.rb | 25 +- spec/fabricators/group_fabricator.rb | 4 +- spec/fabricators/invite_fabricator.rb | 2 +- spec/fabricators/muted_user.rb | 4 +- spec/fabricators/notification_fabricator.rb | 12 +- spec/fabricators/permalink_fabricator.rb | 4 +- spec/fabricators/post_fabricator.rb | 53 +- spec/fabricators/post_revision_fabricator.rb | 4 +- spec/fabricators/reviewable_fabricator.rb | 51 +- spec/fabricators/screened_url_fabricator.rb | 4 +- .../sidebar_section_link_fabricator.rb | 8 +- .../single_sign_on_record_fabricator.rb | 4 +- spec/fabricators/tag_fabricator.rb | 4 +- spec/fabricators/tag_group_fabricator.rb | 4 +- .../topic_allowed_user_fabricator.rb | 4 +- spec/fabricators/topic_fabricator.rb | 22 +- spec/fabricators/upload_fabricator.rb | 27 +- spec/fabricators/user_api_key_fabricator.rb | 6 +- spec/fabricators/user_avatar_fabricator.rb | 4 +- spec/fabricators/user_fabricator.rb | 73 +- spec/fabricators/user_field_fabricator.rb | 2 +- spec/fabricators/user_history_fabricator.rb | 3 +- spec/fabricators/user_option_fabricator.rb | 3 +- spec/fabricators/user_profile_fabricator.rb | 4 +- .../user_second_factor_fabricator.rb | 2 +- .../user_security_key_fabricator.rb | 8 +- spec/fabricators/web_hook_fabricator.rb | 68 +- .../change/20990309014015_drop_email_logs.rb | 2 +- .../20990309014014_drop_post_columns.rb | 4 +- .../20990309014013_drop_email_logs_table.rb | 2 +- spec/fixtures/plugins/csp_extension/plugin.rb | 8 +- spec/fixtures/plugins/my_plugin/plugin.rb | 3 +- spec/helpers/application_helper_spec.rb | 403 +- spec/helpers/redis_snapshot_helper.rb | 8 +- spec/helpers/topics_helper_spec.rb | 5 +- .../helpers/user_notifications_helper_spec.rb | 91 +- spec/import_export/category_exporter_spec.rb | 16 +- .../category_structure_exporter_spec.rb | 12 +- spec/import_export/group_exporter_spec.rb | 10 +- spec/import_export/importer_spec.rb | 82 +- spec/import_export/topic_exporter_spec.rb | 12 +- .../track_setting_changes_spec.rb | 12 +- spec/integration/api_keys_spec.rb | 61 +- .../auto_reject_reviewable_users_spec.rb | 4 +- .../blocked_hotlinked_media_spec.rb | 68 +- spec/integration/category_tag_spec.rb | 693 ++- .../content_security_policy_spec.rb | 10 +- spec/integration/discord_omniauth_spec.rb | 96 +- spec/integration/email_style_spec.rb | 55 +- spec/integration/facebook_omniauth_spec.rb | 67 +- spec/integration/flags_spec.rb | 2 - spec/integration/github_omniauth_spec.rb | 162 +- spec/integration/group_spec.rb | 16 +- spec/integration/invalid_request_spec.rb | 23 +- .../invite_only_registration_spec.rb | 47 +- spec/integration/message_bus_spec.rb | 4 +- spec/integration/multisite_cookies_spec.rb | 12 +- spec/integration/multisite_spec.rb | 10 +- spec/integration/rate_limiting_spec.rb | 45 +- spec/integration/request_tracker_spec.rb | 16 +- spec/integration/same_ip_spammers_spec.rb | 54 +- spec/integration/spam_rules_spec.rb | 85 +- spec/integration/topic_auto_close_spec.rb | 39 +- spec/integration/topic_thumbnail_spec.rb | 49 +- spec/integration/twitter_omniauth_spec.rb | 70 +- spec/integration/watched_words_spec.rb | 231 +- spec/integrity/coding_style_spec.rb | 16 +- spec/integrity/common_mark_spec.rb | 116 +- spec/integrity/i18n_spec.rb | 20 +- spec/integrity/js_constants_spec.rb | 8 +- spec/integrity/oj_spec.rb | 4 +- spec/integrity/onceoff_integrity_spec.rb | 7 +- spec/integrity/site_setting_spec.rb | 14 +- spec/jobs/about_stats_spec.rb | 2 +- spec/jobs/activation_reminder_emails_spec.rb | 28 +- spec/jobs/auto_expire_user_api_keys_spec.rb | 8 +- spec/jobs/auto_queue_handler_spec.rb | 6 +- spec/jobs/automatic_group_membership_spec.rb | 27 +- .../bookmark_reminder_notifications_spec.rb | 14 +- spec/jobs/bulk_grant_trust_level_spec.rb | 9 +- spec/jobs/bulk_invite_spec.rb | 113 +- spec/jobs/bump_topic_spec.rb | 1 - spec/jobs/check_new_features_spec.rb | 91 +- spec/jobs/clean_dismissed_topic_users_spec.rb | 12 +- .../jobs/clean_up_associated_accounts_spec.rb | 23 +- spec/jobs/clean_up_email_logs_spec.rb | 10 +- spec/jobs/clean_up_inactive_users_spec.rb | 24 +- spec/jobs/clean_up_post_reply_keys_spec.rb | 12 +- .../jobs/clean_up_unused_staged_users_spec.rb | 2 +- spec/jobs/clean_up_uploads_spec.rb | 147 +- spec/jobs/clean_up_user_export_topics_spec.rb | 36 +- spec/jobs/close_topic_spec.rb | 61 +- .../correct_missing_dualstack_urls_spec.rb | 82 +- spec/jobs/crawl_topic_link_spec.rb | 1 - spec/jobs/create_linked_topic_spec.rb | 37 +- .../create_recent_post_search_indexes_spec.rb | 16 +- spec/jobs/create_user_reviewable_spec.rb | 12 +- spec/jobs/dashboard_stats_spec.rb | 18 +- spec/jobs/delete_replies_spec.rb | 25 +- spec/jobs/delete_topic_spec.rb | 10 +- spec/jobs/disable_bootstrap_mode_spec.rb | 15 +- spec/jobs/download_avatar_from_url_spec.rb | 9 +- spec/jobs/download_backup_email_spec.rb | 15 +- ...wnload_profile_background_from_url_spec.rb | 9 +- spec/jobs/emit_web_hook_event_spec.rb | 260 +- spec/jobs/enable_bootstrap_mode_spec.rb | 23 +- spec/jobs/enqueue_digest_emails_spec.rb | 109 +- spec/jobs/enqueue_suspect_users_spec.rb | 59 +- spec/jobs/export_csv_file_spec.rb | 201 +- spec/jobs/export_user_archive_spec.rb | 455 +- spec/jobs/feature_topic_users_spec.rb | 2 +- ...x_out_of_sync_user_uploaded_avatar_spec.rb | 14 +- ...ix_primary_emails_for_staged_users_spec.rb | 16 +- spec/jobs/fix_s3_etags_spec.rb | 4 +- ...r_usernames_and_groups_names_clash_spec.rb | 12 +- spec/jobs/grant_anniversary_badges_spec.rb | 4 +- ...grant_new_user_of_the_month_badges_spec.rb | 17 +- spec/jobs/heartbeat_spec.rb | 4 +- spec/jobs/ignored_users_summary_spec.rb | 19 +- spec/jobs/invalidate_inactive_admins_spec.rb | 33 +- spec/jobs/invite_email_spec.rb | 15 +- spec/jobs/jobs_base_spec.rb | 74 +- spec/jobs/jobs_spec.rb | 59 +- spec/jobs/mass_award_badge_spec.rb | 30 +- .../migrate_badge_image_to_uploads_spec.rb | 21 +- spec/jobs/notify_category_change_spec.rb | 12 +- .../notify_mailing_list_subscribers_spec.rb | 119 +- spec/jobs/notify_moved_posts_spec.rb | 30 +- spec/jobs/notify_reviewable_spec.rb | 84 +- spec/jobs/notify_tag_change_spec.rb | 93 +- spec/jobs/old_keys_reminder_spec.rb | 50 +- spec/jobs/open_topic_spec.rb | 50 +- .../pending_queued_posts_reminder_spec.rb | 36 +- .../jobs/pending_reviewables_reminder_spec.rb | 26 +- spec/jobs/pending_users_reminder_spec.rb | 13 +- spec/jobs/periodical_updates_spec.rb | 2 - spec/jobs/poll_mailbox_spec.rb | 44 +- .../post_update_topic_tracking_state_spec.rb | 4 +- spec/jobs/post_uploads_recovery_spec.rb | 10 +- spec/jobs/problem_checks_spec.rb | 18 +- spec/jobs/process_bulk_invite_emails_spec.rb | 4 +- spec/jobs/process_email_spec.rb | 2 - spec/jobs/process_post_spec.rb | 30 +- .../process_shelved_notifications_spec.rb | 24 +- spec/jobs/publish_topic_to_category_spec.rb | 46 +- spec/jobs/pull_hotlinked_images_spec.rb | 304 +- ...pull_user_profile_hotlinked_images_spec.rb | 24 +- spec/jobs/rebake_custom_emoji_posts_spec.rb | 12 +- .../refresh_users_reviewable_counts_spec.rb | 39 +- .../regular/bulk_user_title_update_spec.rb | 30 +- spec/jobs/regular/group_smtp_email_spec.rb | 120 +- .../publish_group_membership_updates_spec.rb | 63 +- .../update_post_uploads_secure_status_spec.rb | 2 +- spec/jobs/reindex_search_spec.rb | 40 +- spec/jobs/remove_banner_spec.rb | 34 +- spec/jobs/reviewable_priorities_spec.rb | 7 +- spec/jobs/send_system_message_spec.rb | 28 +- spec/jobs/suspicious_login_spec.rb | 7 +- spec/jobs/sync_acls_for_uploads_spec.rb | 10 +- spec/jobs/tl3_promotions_spec.rb | 17 +- spec/jobs/toggle_topic_closed_spec.rb | 92 +- spec/jobs/topic_timer_enqueuer_spec.rb | 32 +- spec/jobs/truncate_user_flag_stats_spec.rb | 5 +- spec/jobs/update_gravatar_spec.rb | 11 +- spec/jobs/update_s3_inventory_spec.rb | 15 +- spec/jobs/update_username_spec.rb | 19 +- spec/jobs/user_email_spec.rb | 513 ++- spec/lib/admin_confirmation_spec.rb | 23 +- spec/lib/admin_user_index_query_spec.rb | 68 +- spec/lib/archetype_spec.rb | 20 +- .../default_current_user_provider_spec.rb | 387 +- spec/lib/auth/discord_authenticator_spec.rb | 36 +- spec/lib/auth/facebook_authenticator_spec.rb | 63 +- spec/lib/auth/github_authenticator_spec.rb | 174 +- .../auth/google_oauth2_authenticator_spec.rb | 152 +- spec/lib/auth/managed_authenticator_spec.rb | 353 +- spec/lib/auth/result_spec.rb | 4 +- spec/lib/auth/twitter_authenticator_spec.rb | 22 +- .../backup_file_handler_multisite_spec.rb | 2 +- .../backup_file_handler_spec.rb | 18 +- spec/lib/backup_restore/backuper_spec.rb | 48 +- .../backup_restore/database_restorer_spec.rb | 56 +- .../backup_restore/local_backup_store_spec.rb | 57 +- .../backup_restore/meta_data_handler_spec.rb | 57 +- .../backup_restore/s3_backup_store_spec.rb | 141 +- .../shared_context_for_backup_restore.rb | 50 +- .../shared_examples_for_backup_store.rb | 70 +- .../backup_restore/system_interface_spec.rb | 52 +- .../backup_restore/uploads_restorer_spec.rb | 164 +- spec/lib/bookmark_manager_spec.rb | 189 +- spec/lib/bookmark_query_spec.rb | 98 +- ...mark_reminder_notification_handler_spec.rb | 22 +- spec/lib/browser_detection_spec.rb | 160 +- spec/lib/cache_spec.rb | 25 +- spec/lib/category_badge_spec.rb | 4 +- spec/lib/category_guardian_spec.rb | 22 +- .../common_passwords/common_passwords_spec.rb | 10 +- spec/lib/composer_messages_finder_spec.rb | 254 +- spec/lib/compression/engine_spec.rb | 58 +- spec/lib/concern/cached_counting_spec.rb | 18 +- spec/lib/concern/category_hashtag_spec.rb | 33 +- spec/lib/concern/has_custom_fields_spec.rb | 155 +- spec/lib/concern/has_search_data_spec.rb | 17 +- spec/lib/concern/positionable_spec.rb | 6 +- spec/lib/concern/searchable_spec.rb | 24 +- .../lib/concern/second_factor_manager_spec.rb | 219 +- spec/lib/content_buffer_spec.rb | 4 +- .../content_security_policy/builder_spec.rb | 39 +- spec/lib/content_security_policy_spec.rb | 412 +- spec/lib/cooked_post_processor_spec.rb | 769 ++-- spec/lib/crawler_detection_spec.rb | 193 +- spec/lib/current_user_spec.rb | 18 +- spec/lib/db_helper_spec.rb | 13 +- spec/lib/directory_helper_spec.rb | 24 +- spec/lib/discourse_diff_spec.rb | 105 +- spec/lib/discourse_event_spec.rb | 68 +- spec/lib/discourse_hub_spec.rb | 59 +- spec/lib/discourse_js_processor_spec.rb | 87 +- spec/lib/discourse_plugin_registry_spec.rb | 135 +- spec/lib/discourse_redis_spec.rb | 251 +- ...course_sourcemapping_url_processor_spec.rb | 12 +- spec/lib/discourse_spec.rb | 363 +- spec/lib/discourse_tagging_spec.rb | 604 +-- spec/lib/discourse_updates_spec.rb | 170 +- spec/lib/distributed_cache_spec.rb | 20 +- spec/lib/distributed_memoizer_spec.rb | 4 +- spec/lib/distributed_mutex_spec.rb | 70 +- spec/lib/email/authentication_results_spec.rb | 27 +- spec/lib/email/cleaner_spec.rb | 15 +- spec/lib/email/email_spec.rb | 83 +- spec/lib/email/message_builder_spec.rb | 226 +- spec/lib/email/processor_spec.rb | 104 +- spec/lib/email/receiver_spec.rb | 799 ++-- spec/lib/email/renderer_spec.rb | 17 +- spec/lib/email/sender_spec.rb | 448 +- spec/lib/email/styles_spec.rb | 207 +- spec/lib/email_cook_spec.rb | 20 +- spec/lib/email_updater_spec.rb | 223 +- spec/lib/encodings_spec.rb | 16 +- spec/lib/enum_spec.rb | 2 +- spec/lib/excerpt_parser_spec.rb | 48 +- spec/lib/feed_element_installer_spec.rb | 30 +- spec/lib/feed_item_accessor_spec.rb | 36 +- spec/lib/file_helper_spec.rb | 105 +- spec/lib/file_store/base_store_spec.rb | 107 +- spec/lib/file_store/local_store_spec.rb | 87 +- spec/lib/file_store/s3_store_spec.rb | 283 +- spec/lib/filter_best_posts_spec.rb | 39 +- spec/lib/final_destination/http_spec.rb | 9 +- spec/lib/final_destination/resolver_spec.rb | 4 +- spec/lib/final_destination_spec.rb | 365 +- spec/lib/flag_settings_spec.rb | 15 +- .../schema_migration_details_spec.rb | 1 - .../translate_accelerator_spec.rb | 200 +- spec/lib/gaps_spec.rb | 20 +- spec/lib/git_url_spec.rb | 4 +- spec/lib/global_path_spec.rb | 25 +- .../lib/group_email_credentials_check_spec.rb | 56 +- spec/lib/guardian/topic_guardian_spec.rb | 124 +- spec/lib/guardian/user_guardian_spec.rb | 118 +- spec/lib/guardian_spec.rb | 1321 +++--- spec/lib/has_errors_spec.rb | 19 +- spec/lib/highlight_js/highlight_js_spec.rb | 10 +- spec/lib/hijack_spec.rb | 87 +- spec/lib/html_prettify_spec.rb | 17 +- spec/lib/html_to_markdown_spec.rb | 151 +- spec/lib/i18n/discourse_i18n_spec.rb | 148 +- spec/lib/i18n/fallback_locale_list_spec.rb | 32 +- .../i18n_interpolation_keys_finder_spec.rb | 7 +- spec/lib/image_sizer_spec.rb | 48 +- spec/lib/imap/providers/detector_spec.rb | 4 +- spec/lib/imap/providers/generic_spec.rb | 94 +- spec/lib/imap/providers/gmail_spec.rb | 69 +- spec/lib/imap/sync_spec.rb | 609 ++- spec/lib/import/normalize_spec.rb | 3 +- spec/lib/inline_oneboxer_spec.rb | 246 +- spec/lib/js_locale_helper_spec.rb | 181 +- spec/lib/json_error_spec.rb | 4 +- spec/lib/letter_avatar_spec.rb | 2 +- spec/lib/message_id_service_spec.rb | 114 +- spec/lib/method_profiler_spec.rb | 16 +- spec/lib/middleware/anonymous_cache_spec.rb | 199 +- .../discourse_public_exceptions_spec.rb | 17 +- spec/lib/middleware/enforce_hostname_spec.rb | 20 +- spec/lib/middleware/request_tracker_spec.rb | 372 +- spec/lib/migration/column_dropper_spec.rb | 63 +- spec/lib/migration/safe_migrate_spec.rb | 53 +- spec/lib/migration/table_dropper_spec.rb | 28 +- .../lib/mini_sql_multisite_connection_spec.rb | 27 +- spec/lib/mobile_detection_spec.rb | 1 - spec/lib/new_post_manager_spec.rb | 469 ++- spec/lib/new_post_result_spec.rb | 4 +- spec/lib/onebox/domain_checker_spec.rb | 6 +- .../engine/allowlisted_generic_onebox_spec.rb | 215 +- spec/lib/onebox/engine/amazon_onebox_spec.rb | 157 +- .../engine/animated_image_onebox_spec.rb | 4 +- spec/lib/onebox/engine/audio_onebox_spec.rb | 35 +- .../lib/onebox/engine/cloudapp_onebox_spec.rb | 28 +- spec/lib/onebox/engine/gfycat_onebox_spec.rb | 4 +- .../engine/github_actions_onebox_spec.rb | 18 +- .../onebox/engine/github_blob_onebox_spec.rb | 22 +- .../engine/github_commit_onebox_spec.rb | 18 +- .../engine/github_folder_onebox_spec.rb | 33 +- .../onebox/engine/github_gist_onebox_spec.rb | 17 +- .../engine/github_pull_request_onebox_spec.rb | 29 +- .../onebox/engine/gitlab_blob_onebox_spec.rb | 9 +- .../onebox/engine/google_docs_onebox_spec.rb | 4 +- .../onebox/engine/google_drive_onebox_spec.rb | 12 +- .../onebox/engine/google_maps_onebox_spec.rb | 56 +- .../engine/google_photos_onebox_spec.rb | 16 +- .../engine/google_play_app_onebox_spec.rb | 6 +- spec/lib/onebox/engine/hackernews_spec.rb | 9 +- spec/lib/onebox/engine/html_spec.rb | 6 +- spec/lib/onebox/engine/image_onebox_spec.rb | 44 +- spec/lib/onebox/engine/imgur_onebox_spec.rb | 4 +- .../onebox/engine/instagram_onebox_spec.rb | 46 +- spec/lib/onebox/engine/json_spec.rb | 5 +- spec/lib/onebox/engine/kaltura_onebox_spec.rb | 50 +- spec/lib/onebox/engine/motoko_onebox_spec.rb | 13 +- spec/lib/onebox/engine/pdf_onebox_spec.rb | 8 +- spec/lib/onebox/engine/pubmed_onebox_spec.rb | 10 +- spec/lib/onebox/engine/reddit_media_onebox.rb | 4 +- spec/lib/onebox/engine/slides_onebox_spec.rb | 8 +- .../engine/stack_exchange_onebox_spec.rb | 86 +- spec/lib/onebox/engine/standard_embed_spec.rb | 51 +- spec/lib/onebox/engine/trello_onebox_spec.rb | 26 +- .../onebox/engine/twitch_clips_onebox_spec.rb | 6 +- .../engine/twitch_stream_onebox_spec.rb | 10 +- .../onebox/engine/twitch_video_onebox_spec.rb | 6 +- .../engine/twitter_status_onebox_spec.rb | 322 +- .../lib/onebox/engine/typeform_onebox_spec.rb | 27 +- spec/lib/onebox/engine/video_onebox_spec.rb | 33 +- .../onebox/engine/wikimedia_onebox_spec.rb | 12 +- .../onebox/engine/wikipedia_onebox_spec.rb | 16 +- spec/lib/onebox/engine/wistia_onebox_spec.rb | 19 +- spec/lib/onebox/engine/xkcd_spec.rb | 4 +- spec/lib/onebox/engine/youku_onebox_spec.rb | 22 +- spec/lib/onebox/engine/youtube_onebox_spec.rb | 149 +- spec/lib/onebox/engine_spec.rb | 24 +- spec/lib/onebox/helpers_spec.rb | 309 +- spec/lib/onebox/json_ld_spec.rb | 80 +- spec/lib/onebox/matcher_spec.rb | 19 +- spec/lib/onebox/movie_spec.rb | 47 +- spec/lib/onebox/open_graph_spec.rb | 14 +- spec/lib/onebox/preview_spec.rb | 26 +- spec/lib/onebox/status_check_spec.rb | 50 +- spec/lib/onebox_spec.rb | 14 +- spec/lib/oneboxer_spec.rb | 548 ++- spec/lib/onpdiff_spec.rb | 66 +- spec/lib/pbkdf2_spec.rb | 10 +- spec/lib/pinned_check_spec.rb | 8 +- spec/lib/plain_text_to_markdown_spec.rb | 104 +- spec/lib/plugin/filter_manager_spec.rb | 17 +- spec/lib/plugin/instance_spec.rb | 366 +- spec/lib/post_action_creator_spec.rb | 103 +- spec/lib/post_action_destroyer_spec.rb | 61 +- spec/lib/post_creator_spec.rb | 1257 +++--- spec/lib/post_destroyer_spec.rb | 325 +- spec/lib/post_jobs_enqueuer_spec.rb | 24 +- spec/lib/post_locker_spec.rb | 29 +- spec/lib/post_merger_spec.rb | 57 +- spec/lib/post_revisor_spec.rb | 772 ++-- spec/lib/presence_channel_spec.rb | 117 +- spec/lib/pretty_text/helpers_spec.rb | 30 +- spec/lib/pretty_text_spec.rb | 931 +++-- spec/lib/promotion_spec.rb | 55 +- spec/lib/quote_comparer_spec.rb | 3 +- spec/lib/rate_limiter/limit_exceeded_spec.rb | 14 +- spec/lib/rate_limiter_spec.rb | 70 +- spec/lib/retrieve_title_spec.rb | 159 +- spec/lib/rtl_spec.rb | 44 +- spec/lib/s3_cors_rulesets_spec.rb | 85 +- spec/lib/s3_helper_spec.rb | 166 +- spec/lib/s3_inventory_multisite_spec.rb | 5 +- spec/lib/s3_inventory_spec.rb | 152 +- spec/lib/scheduler/defer_spec.rb | 58 +- spec/lib/score_calculator_spec.rb | 8 +- spec/lib/scss_checker_spec.rb | 15 +- spec/lib/search_spec.rb | 1744 ++++---- .../discourse_connect_provider_spec.rb | 36 +- .../second_factor/actions/grant_admin_spec.rb | 56 +- spec/lib/second_factor/auth_manager_spec.rb | 110 +- spec/lib/seed_data/categories_spec.rb | 50 +- spec/lib/seed_data/topics_spec.rb | 26 +- spec/lib/shrink_uploaded_image_spec.rb | 88 +- .../lib/sidebar_section_links_updater_spec.rb | 72 +- spec/lib/site_icon_manager_spec.rb | 9 +- .../site_setting_extension_multisite_spec.rb | 20 +- spec/lib/site_setting_extension_spec.rb | 296 +- spec/lib/site_settings/db_provider_spec.rb | 1 - .../site_settings/defaults_provider_spec.rb | 117 +- .../local_process_provider_multisite_spec.rb | 4 +- .../local_process_provider_spec.rb | 1 - .../lib/site_settings/type_supervisor_spec.rb | 354 +- spec/lib/site_settings/validations_spec.rb | 263 +- spec/lib/site_settings/yaml_loader_spec.rb | 58 +- spec/lib/slug_spec.rb | 168 +- spec/lib/spam_handler_spec.rb | 8 +- spec/lib/stylesheet/compiler_spec.rb | 95 +- spec/lib/stylesheet/importer_spec.rb | 79 +- spec/lib/stylesheet/manager_spec.rb | 653 +-- spec/lib/suggested_topics_builder_spec.rb | 10 +- spec/lib/svg_sprite/svg_sprite_spec.rb | 85 +- spec/lib/system_message_spec.rb | 67 +- spec/lib/text_cleaner_spec.rb | 50 +- spec/lib/text_sentinel_spec.rb | 61 +- spec/lib/theme_flag_modifier_spec.rb | 4 +- spec/lib/theme_javascript_compiler_spec.rb | 115 +- spec/lib/theme_settings_manager_spec.rb | 6 +- spec/lib/theme_settings_parser_spec.rb | 28 +- spec/lib/theme_store/git_importer_spec.rb | 124 +- spec/lib/theme_store/zip_exporter_spec.rb | 137 +- spec/lib/theme_store/zip_importer_spec.rb | 21 +- spec/lib/timeline_lookup_spec.rb | 5 +- spec/lib/tiny_japanese_segmenter_spec.rb | 10 +- spec/lib/topic_creator_spec.rb | 503 ++- spec/lib/topic_publisher_spec.rb | 23 +- .../topic_query/private_message_lists_spec.rb | 236 +- spec/lib/topic_query_spec.rb | 985 +++-- spec/lib/topic_retriever_spec.rb | 29 +- .../lib/topic_upload_security_manager_spec.rb | 24 +- spec/lib/topic_view_spec.rb | 331 +- spec/lib/topics_bulk_action_spec.rb | 226 +- spec/lib/trust_level_spec.rb | 6 +- spec/lib/twitter_api_spec.rb | 8 +- spec/lib/unread_spec.rb | 33 +- spec/lib/upload_creator_spec.rb | 404 +- spec/lib/upload_markdown_spec.rb | 9 +- spec/lib/upload_recovery_spec.rb | 230 +- spec/lib/upload_security_spec.rb | 81 +- spec/lib/url_helper_spec.rb | 55 +- spec/lib/user_comm_screener_spec.rb | 107 +- spec/lib/user_lookup_spec.rb | 28 +- spec/lib/user_name_suggester_spec.rb | 125 +- .../allowed_ip_address_validator_spec.rb | 14 +- ..._search_priority_weights_validator_spec.rb | 12 +- .../censored_words_validator_spec.rb | 16 +- .../validators/css_color_validator_spec.rb | 20 +- .../delete_rejected_email_after_days_spec.rb | 12 +- .../email_address_validator_spec.rb | 23 +- .../email_setting_validator_spec.rb | 8 +- spec/lib/validators/email_validator_spec.rb | 30 +- .../enable_invite_only_validator_spec.rb | 15 +- ...e_local_logins_via_email_validator_spec.rb | 40 +- .../validators/enable_sso_validator_spec.rb | 76 +- .../group_setting_validator_spec.rb | 8 +- .../host_list_setting_validator_spec.rb | 6 +- .../integer_setting_validator_spec.rb | 31 +- .../ip_address_format_validator_spec.rb | 11 +- .../validators/max_emojis_validator_spec.rb | 25 +- .../max_username_length_validator_spec.rb | 25 +- .../min_username_length_validator_spec.rb | 23 +- .../lib/validators/password_validator_spec.rb | 16 +- spec/lib/validators/post_validator_spec.rb | 45 +- .../quality_title_validator_spec.rb | 32 +- .../regex_presence_validator_spec.rb | 12 +- .../regex_setting_validator_spec.rb | 8 +- .../validators/regexp_list_validator_spec.rb | 6 +- .../reply_by_email_address_validator_spec.rb | 19 +- .../reply_by_email_enabled_validator_spec.rb | 5 +- .../search_tokenize_chinese_validator_spec.rb | 6 +- ...search_tokenize_japanese_validator_spec.rb | 6 +- .../selectable_avatars_mode_validator_spec.rb | 2 +- .../sso_overrides_email_validator_spec.rb | 30 +- .../string_setting_validator_spec.rb | 68 +- .../lib/validators/timezone_validator_spec.rb | 4 +- .../topic_title_length_validator_spec.rb | 32 +- ...icode_username_allowlist_validator_spec.rb | 3 +- spec/lib/validators/upload_validator_spec.rb | 86 +- spec/lib/validators/url_validator_spec.rb | 12 +- .../username_setting_validator_spec.rb | 28 +- spec/lib/version_spec.rb | 77 +- spec/lib/webauthn/challenge_generator_spec.rb | 8 +- ...ecurity_key_authentication_service_spec.rb | 151 +- .../security_key_registration_service_spec.rb | 110 +- spec/lib/wizard/step_updater_spec.rb | 341 +- spec/lib/wizard/wizard_builder_spec.rb | 70 +- spec/lib/wizard/wizard_spec.rb | 119 +- spec/lib/wizard/wizard_step_spec.rb | 18 +- spec/mailers/group_smtp_mailer_spec.rb | 97 +- spec/mailers/invite_mailer_spec.rb | 96 +- spec/mailers/rejection_mailer_spec.rb | 22 +- spec/mailers/subscription_mailer_spec.rb | 4 +- spec/mailers/test_mailer_spec.rb | 8 +- spec/mailers/user_notifications_spec.rb | 806 ++-- spec/mailers/version_mailer_spec.rb | 10 +- spec/models/about_spec.rb | 65 +- spec/models/admin_dashboard_data_spec.rb | 183 +- spec/models/api_key_scope_spec.rb | 9 +- spec/models/api_key_spec.rb | 44 +- spec/models/application_request_spec.rb | 6 +- spec/models/associated_group_spec.rb | 2 +- spec/models/badge_spec.rb | 115 +- spec/models/badge_type_spec.rb | 1 - spec/models/bookmark_spec.rb | 53 +- spec/models/category_featured_topic_spec.rb | 55 +- spec/models/category_group_spec.rb | 6 +- spec/models/category_list_spec.rb | 135 +- spec/models/category_spec.rb | 426 +- spec/models/category_user_spec.rb | 249 +- spec/models/child_theme_spec.rb | 8 +- spec/models/color_scheme_color_spec.rb | 21 +- spec/models/color_scheme_spec.rb | 103 +- spec/models/digest_email_site_setting_spec.rb | 12 +- spec/models/directory_item_spec.rb | 112 +- spec/models/discourse_connect_spec.rb | 274 +- spec/models/do_not_disturb_timing_spec.rb | 2 +- spec/models/draft_sequence_spec.rb | 38 +- spec/models/draft_spec.rb | 151 +- spec/models/email_change_request_spec.rb | 11 +- spec/models/email_log_spec.rb | 87 +- spec/models/email_token_spec.rb | 102 +- spec/models/embeddable_host_spec.rb | 112 +- spec/models/emoji_spec.rb | 77 +- spec/models/given_daily_like_spec.rb | 9 +- spec/models/global_setting_spec.rb | 55 +- spec/models/group_archived_message_spec.rb | 42 +- spec/models/group_history_spec.rb | 89 +- spec/models/group_spec.rb | 441 +- spec/models/group_user_spec.rb | 111 +- spec/models/incoming_link_spec.rb | 83 +- spec/models/incoming_links_report_spec.rb | 222 +- spec/models/invite_redeemer_spec.rb | 368 +- spec/models/invite_spec.rb | 345 +- spec/models/javascript_cache_spec.rb | 25 +- spec/models/locale_site_setting_spec.rb | 91 +- .../mailing_list_mode_site_setting_spec.rb | 14 +- spec/models/notification_spec.rb | 574 ++- spec/models/optimized_image_spec.rb | 113 +- spec/models/permalink_spec.rb | 22 +- spec/models/plugin_store_spec.rb | 13 +- spec/models/post_action_spec.rb | 336 +- spec/models/post_action_type_spec.rb | 16 +- spec/models/post_analyzer_spec.rb | 129 +- spec/models/post_mover_spec.rb | 967 +++-- spec/models/post_reply_key_spec.rb | 10 +- spec/models/post_reply_spec.rb | 2 +- spec/models/post_revision_spec.rb | 12 +- spec/models/post_spec.rb | 718 ++-- spec/models/post_timing_spec.rb | 87 +- ...ivate_message_topic_tracking_state_spec.rb | 188 +- spec/models/published_page_spec.rb | 4 +- spec/models/quoted_post_spec.rb | 19 +- spec/models/remote_theme_spec.rb | 63 +- spec/models/report_spec.rb | 723 ++-- spec/models/reviewable_claimed_topic_spec.rb | 2 - spec/models/reviewable_flagged_post_spec.rb | 59 +- spec/models/reviewable_history_spec.rb | 2 - spec/models/reviewable_post_spec.rb | 50 +- spec/models/reviewable_queued_post_spec.rb | 84 +- spec/models/reviewable_score_spec.rb | 23 +- spec/models/reviewable_spec.rb | 169 +- spec/models/reviewable_user_spec.rb | 68 +- spec/models/s3_region_site_setting_spec.rb | 12 +- spec/models/screened_email_spec.rb | 34 +- spec/models/screened_ip_address_spec.rb | 375 +- spec/models/screened_url_spec.rb | 98 +- spec/models/search_log_spec.rb | 182 +- spec/models/sidebar_section_link_spec.rb | 33 +- spec/models/site_setting_spec.rb | 112 +- spec/models/site_spec.rb | 55 +- spec/models/sitemap_spec.rb | 69 +- spec/models/skipped_email_log_spec.rb | 82 +- spec/models/stylesheet_cache_spec.rb | 3 +- spec/models/tag_group_spec.rb | 98 +- spec/models/tag_spec.rb | 85 +- spec/models/tag_user_spec.rb | 167 +- spec/models/theme_field_spec.rb | 408 +- spec/models/theme_modifier_set_spec.rb | 8 +- spec/models/theme_spec.rb | 584 ++- spec/models/top_menu_item_spec.rb | 11 +- spec/models/top_topic_spec.rb | 54 +- spec/models/topic_converter_spec.rb | 91 +- spec/models/topic_embed_spec.rb | 256 +- spec/models/topic_featured_users_spec.rb | 15 +- spec/models/topic_group_spec.rb | 46 +- spec/models/topic_invite_spec.rb | 2 - spec/models/topic_link_click_spec.rb | 255 +- spec/models/topic_link_spec.rb | 288 +- spec/models/topic_list_spec.rb | 73 +- .../models/topic_participants_summary_spec.rb | 9 +- spec/models/topic_posters_summary_spec.rb | 51 +- spec/models/topic_spec.rb | 1723 ++++---- spec/models/topic_tag_spec.rb | 26 +- spec/models/topic_thumbnail_spec.rb | 2 +- spec/models/topic_timer_spec.rb | 165 +- spec/models/topic_tracking_state_spec.rb | 382 +- spec/models/topic_user_spec.rb | 338 +- spec/models/topic_view_item_spec.rb | 6 +- spec/models/translation_override_spec.rb | 228 +- spec/models/trust_level3_requirements_spec.rb | 149 +- .../trust_level_and_staff_setting_spec.rb | 8 +- spec/models/trust_level_setting_spec.rb | 8 +- spec/models/unsubscribe_key_spec.rb | 22 +- spec/models/upload_reference_spec.rb | 199 +- spec/models/upload_spec.rb | 275 +- spec/models/user_action_spec.rb | 144 +- spec/models/user_api_key_spec.rb | 26 +- spec/models/user_archived_message_spec.rb | 21 +- spec/models/user_auth_token_spec.rb | 165 +- spec/models/user_avatar_spec.rb | 98 +- spec/models/user_badge_spec.rb | 72 +- spec/models/user_bookmark_list_spec.rb | 4 +- spec/models/user_email_spec.rb | 12 +- spec/models/user_export_spec.rb | 38 +- spec/models/user_field_spec.rb | 8 +- spec/models/user_history_spec.rb | 51 +- .../models/user_notification_schedule_spec.rb | 62 +- spec/models/user_option_spec.rb | 49 +- spec/models/user_profile_spec.rb | 145 +- spec/models/user_profile_view_spec.rb | 12 +- spec/models/user_search_spec.rb | 56 +- spec/models/user_second_factor_spec.rb | 4 +- spec/models/user_spec.rb | 1696 ++++---- spec/models/user_stat_spec.rb | 118 +- spec/models/user_status_spec.rb | 2 +- spec/models/user_summary_spec.rb | 1 - spec/models/user_visit_spec.rb | 10 +- spec/models/username_validator_spec.rb | 193 +- spec/models/watched_word_spec.rb | 66 +- spec/models/web_crawler_request_spec.rb | 9 +- spec/models/web_hook_event_spec.rb | 23 +- spec/models/web_hook_spec.rb | 247 +- spec/multisite/distributed_cache_spec.rb | 27 +- spec/multisite/jobs_spec.rb | 10 +- spec/multisite/pausable_multisite_spec.rb | 22 +- spec/multisite/pausable_spec.rb | 8 +- spec/multisite/post_spec.rb | 15 +- spec/multisite/request_tracker_spec.rb | 103 +- spec/multisite/s3_store_spec.rb | 171 +- spec/multisite/site_settings_spec.rb | 38 +- spec/rails_helper.rb | 196 +- spec/requests/about_controller_spec.rb | 10 +- spec/requests/admin/admin_controller_spec.rb | 18 +- spec/requests/admin/api_controller_spec.rb | 173 +- .../requests/admin/backups_controller_spec.rb | 564 +-- spec/requests/admin/badges_controller_spec.rb | 315 +- .../admin/color_schemes_controller_spec.rb | 46 +- .../admin/dashboard_controller_spec.rb | 126 +- spec/requests/admin/email_controller_spec.rb | 287 +- .../admin/email_styles_controller_spec.rb | 57 +- .../admin/email_templates_controller_spec.rb | 289 +- .../admin/embeddable_hosts_controller_spec.rb | 59 +- .../admin/embedding_controller_spec.rb | 30 +- spec/requests/admin/emojis_controller_spec.rb | 129 +- spec/requests/admin/groups_controller_spec.rb | 409 +- .../admin/impersonate_controller_spec.rb | 14 +- .../admin/permalinks_controller_spec.rb | 99 +- .../requests/admin/plugins_controller_spec.rb | 6 +- .../requests/admin/reports_controller_spec.rb | 122 +- .../admin/robots_txt_controller_spec.rb | 6 +- .../admin/screened_emails_controller_spec.rb | 12 +- .../screened_ip_addresses_controller_spec.rb | 23 +- .../admin/screened_urls_controller_spec.rb | 4 +- spec/requests/admin/search_logs_spec.rb | 26 +- .../admin/site_settings_controller_spec.rb | 436 +- .../admin/site_texts_controller_spec.rb | 678 +-- .../staff_action_logs_controller_spec.rb | 60 +- spec/requests/admin/themes_controller_spec.rb | 603 +-- .../admin/user_fields_controller_spec.rb | 161 +- spec/requests/admin/users_controller_spec.rb | 827 ++-- .../admin/versions_controller_spec.rb | 12 +- .../admin/watched_words_controller_spec.rb | 161 +- .../admin/web_hooks_controller_spec.rb | 210 +- spec/requests/api/backups_spec.rb | 76 +- spec/requests/api/badges_spec.rb | 79 +- spec/requests/api/categories_spec.rb | 103 +- spec/requests/api/groups_spec.rb | 158 +- spec/requests/api/invites_spec.rb | 148 +- spec/requests/api/notifications_spec.rb | 153 +- spec/requests/api/posts_spec.rb | 1142 +++-- spec/requests/api/private_messages_spec.rb | 577 ++- spec/requests/api/schemas/schema_loader.rb | 5 +- spec/requests/api/search_spec.rb | 28 +- spec/requests/api/shared/shared_examples.rb | 6 +- spec/requests/api/site_spec.rb | 24 +- spec/requests/api/tags_spec.rb | 653 +-- spec/requests/api/topics_spec.rb | 1330 +++--- spec/requests/api/uploads_spec.rb | 228 +- spec/requests/api/user_badges_spec.rb | 22 +- spec/requests/api/users_spec.rb | 1256 +++--- spec/requests/application_controller_spec.rb | 542 ++- .../associate_accounts_controller_spec.rb | 25 +- spec/requests/badges_controller_spec.rb | 46 +- spec/requests/bookmarks_controller_spec.rb | 101 +- spec/requests/bootstrap_controller_spec.rb | 63 +- spec/requests/categories_controller_spec.rb | 621 +-- spec/requests/clicks_controller_spec.rb | 11 +- spec/requests/composer_controller_spec.rb | 353 +- .../composer_messages_controller_spec.rb | 47 +- spec/requests/csp_reports_controller_spec.rb | 63 +- .../directory_columns_controller_spec.rb | 30 +- .../directory_items_controller_spec.rb | 134 +- .../do_not_disturb_controller_spec.rb | 34 +- spec/requests/drafts_controller_spec.rb | 172 +- spec/requests/email_controller_spec.rb | 173 +- spec/requests/embed_controller_spec.rb | 213 +- spec/requests/exceptions_controller_spec.rb | 18 +- spec/requests/export_csv_controller_spec.rb | 18 +- .../requests/extra_locales_controller_spec.rb | 121 +- .../finish_installation_controller_spec.rb | 106 +- spec/requests/forums_controller_spec.rb | 8 +- spec/requests/groups_controller_spec.rb | 1308 +++--- spec/requests/hashtags_controller_spec.rb | 82 +- .../requests/inline_onebox_controller_spec.rb | 10 +- spec/requests/invites_controller_spec.rb | 994 +++-- spec/requests/list_controller_spec.rb | 354 +- spec/requests/metadata_controller_spec.rb | 99 +- .../requests/notifications_controller_spec.rb | 278 +- .../omniauth_callbacks_controller_spec.rb | 454 +- spec/requests/onebox_controller_spec.rb | 71 +- spec/requests/permalinks_controller_spec.rb | 10 +- .../post_action_users_controller_spec.rb | 84 +- spec/requests/post_actions_controller_spec.rb | 220 +- spec/requests/post_readers_controller_spec.rb | 68 +- spec/requests/posts_controller_spec.rb | 1388 +++--- spec/requests/presence_controller_spec.rb | 130 +- .../published_pages_controller_spec.rb | 118 +- .../push_notification_controller_spec.rb | 110 +- spec/requests/qunit_controller_spec.rb | 24 +- ...viewable_claimed_topics_controller_spec.rb | 89 +- spec/requests/reviewables_controller_spec.rb | 396 +- spec/requests/robots_txt_controller_spec.rb | 122 +- spec/requests/safe_mode_controller_spec.rb | 25 +- spec/requests/search_controller_spec.rb | 508 ++- spec/requests/session_controller_spec.rb | 1841 ++++---- .../similar_topics_controller_spec.rb | 22 +- spec/requests/site_controller_spec.rb | 12 +- spec/requests/sitemap_controller_spec.rb | 98 +- spec/requests/static_controller_spec.rb | 217 +- spec/requests/steps_controller_spec.rb | 41 +- spec/requests/stylesheets_controller_spec.rb | 20 +- spec/requests/svg_sprite_controller_spec.rb | 44 +- spec/requests/tag_groups_controller_spec.rb | 45 +- spec/requests/tags_controller_spec.rb | 586 +-- .../theme_javascripts_controller_spec.rb | 69 +- spec/requests/topics_controller_spec.rb | 2691 ++++++------ .../uploads_controller_multisite_spec.rb | 2 +- spec/requests/uploads_controller_spec.rb | 933 +++-- spec/requests/user_actions_controller_spec.rb | 41 +- .../requests/user_api_keys_controller_spec.rb | 52 +- spec/requests/user_avatars_controller_spec.rb | 114 +- spec/requests/user_badges_controller_spec.rb | 264 +- spec/requests/user_status_controller_spec.rb | 66 +- spec/requests/users_controller_spec.rb | 3708 +++++++++-------- spec/requests/users_email_controller_spec.rb | 316 +- spec/requests/webhooks_controller_spec.rb | 188 +- spec/requests/wizard_controller_spec.rb | 12 +- spec/script/import_scripts/base_spec.rb | 16 +- .../support/bbcode/xml_to_markdown_spec.rb | 164 +- .../vanilla_body_parser_spec.rb | 206 +- .../admin_plugin_serializer_spec.rb | 8 +- .../admin_user_list_serializer_spec.rb | 14 +- .../basic_group_serializer_spec.rb | 60 +- .../basic_group_user_serializer_spec.rb | 26 +- .../serializers/basic_post_serializer_spec.rb | 1 - ...reviewable_flagged_post_serializer_spec.rb | 6 +- ..._reviewable_queued_post_serializer_spec.rb | 5 +- .../basic_reviewable_user_serializer_spec.rb | 4 +- .../serializers/basic_user_serializer_spec.rb | 8 +- .../category_detailed_serializer_spec.rb | 11 +- spec/serializers/category_serializer_spec.rb | 35 +- .../category_upload_serializer_spec.rb | 4 +- .../current_user_serializer_spec.rb | 95 +- .../detailed_user_badge_serializer_spec.rb | 76 +- .../directory_item_serializer_spec.rb | 7 +- spec/serializers/emoji_serializer_spec.rb | 26 +- .../serializers/found_user_serializer_spec.rb | 2 +- .../serializers/group_show_serializer_spec.rb | 60 +- .../serializers/group_user_serializer_spec.rb | 2 +- .../listable_topic_serializer_spec.rb | 25 +- .../new_post_result_serializer_spec.rb | 7 +- .../notification_serializer_spec.rb | 6 +- .../pending_post_serializer_spec.rb | 4 +- .../post_revision_serializer_spec.rb | 61 +- spec/serializers/post_serializer_spec.rb | 100 +- ...reviewable_flagged_post_serializer_spec.rb | 8 +- .../reviewable_queued_post_serializer_spec.rb | 42 +- .../reviewable_score_serializer_spec.rb | 55 +- .../serializers/reviewable_serializer_spec.rb | 9 +- .../reviewable_user_serializer_spec.rb | 8 +- .../single_sign_on_record_serializer_spec.rb | 11 +- spec/serializers/site_serializer_spec.rb | 130 +- .../suggested_topic_serializer_spec.rb | 42 +- spec/serializers/tag_group_serializer_spec.rb | 9 +- spec/serializers/theme_serializer_spec.rb | 9 +- .../serializers/topic_link_serializer_spec.rb | 3 +- .../topic_list_item_serializer_spec.rb | 69 +- .../serializers/topic_list_serializer_spec.rb | 12 +- .../topic_tracking_state_serializer_spec.rb | 4 +- .../topic_view_details_serializer_spec.rb | 32 +- .../topic_view_posts_serializer_spec.rb | 40 +- .../serializers/topic_view_serializer_spec.rb | 274 +- spec/serializers/upload_serializer_spec.rb | 14 +- .../user_auth_token_serializer_spec.rb | 23 +- .../serializers/user_badge_serializer_spec.rb | 66 +- .../user_bookmark_list_serializer_spec.rb | 5 +- spec/serializers/user_card_serializer_spec.rb | 1 - .../user_post_bookmark_serializer_spec.rb | 6 +- spec/serializers/user_serializer_spec.rb | 240 +- .../user_summary_serializer_spec.rb | 11 +- .../web_hook_post_serializer_spec.rb | 6 +- .../web_hook_topic_view_serializer_spec.rb | 17 +- .../web_hook_user_serializer_spec.rb | 8 +- spec/serializers/wizard_serializer_spec.rb | 41 +- .../services/anonymous_shadow_creator_spec.rb | 1 - spec/services/auto_silence_spec.rb | 76 +- spec/services/badge_granter_spec.rb | 270 +- spec/services/base_bookmarkable_spec.rb | 33 +- .../category_hashtag_data_source_spec.rb | 5 +- spec/services/color_scheme_revisor_spec.rb | 42 +- spec/services/destroy_task_spec.rb | 51 +- .../email_settings_exception_handler_spec.rb | 38 +- .../services/email_settings_validator_spec.rb | 199 +- spec/services/email_style_updater_spec.rb | 50 +- spec/services/external_upload_manager_spec.rb | 74 +- spec/services/flag_sockpuppets_spec.rb | 206 +- spec/services/group_action_logger_spec.rb | 50 +- spec/services/group_mentions_updater_spec.rb | 39 +- spec/services/group_message_spec.rb | 122 +- .../hashtag_autocomplete_service_spec.rb | 4 +- spec/services/heat_settings_updater_spec.rb | 44 +- .../services/inline_uploads_multisite_spec.rb | 4 +- spec/services/inline_uploads_spec.rb | 29 +- spec/services/notification_emailer_spec.rb | 98 +- .../consolidate_notifications_spec.rb | 29 +- .../consolidation_planner_spec.rb | 35 +- spec/services/post_action_notifier_spec.rb | 211 +- spec/services/post_alerter_spec.rb | 1469 ++++--- spec/services/post_bookmarkable_spec.rb | 54 +- spec/services/post_owner_changer_spec.rb | 174 +- .../services/push_notification_pusher_spec.rb | 86 +- spec/services/random_topic_selector_spec.rb | 11 +- spec/services/search_indexer_spec.rb | 178 +- .../sidebar_site_settings_backfiller_spec.rb | 317 +- spec/services/site_settings_spec.rb | 24 +- spec/services/staff_action_logger_spec.rb | 340 +- spec/services/tag_hashtag_data_source_spec.rb | 2 +- spec/services/themes_spec.rb | 98 +- spec/services/topic_bookmarkable_spec.rb | 54 +- spec/services/topic_status_updater_spec.rb | 69 +- spec/services/topic_timestamp_changer_spec.rb | 19 +- spec/services/trust_level_granter_spec.rb | 6 +- spec/services/user_activator_spec.rb | 4 +- spec/services/user_anonymizer_spec.rb | 124 +- spec/services/user_authenticator_spec.rb | 16 +- spec/services/user_destroyer_spec.rb | 160 +- spec/services/user_merger_spec.rb | 406 +- ...er_notification_schedule_processor_spec.rb | 100 +- spec/services/user_silencer_spec.rb | 52 +- spec/services/user_stat_count_updater_spec.rb | 8 +- spec/services/user_updater_spec.rb | 339 +- spec/services/username_changer_spec.rb | 411 +- .../services/username_checker_service_spec.rb | 44 +- spec/services/wildcard_domain_checker_spec.rb | 40 +- spec/services/wildcard_url_checker_spec.rb | 59 +- spec/services/word_watcher_spec.rb | 262 +- spec/support/bookmarkable_helper.rb | 9 +- .../common_basic_reviewable_serializer.rb | 4 +- spec/support/concurrency.rb | 33 +- spec/support/diagnostics_helper.rb | 1 - spec/support/discourse_event_helper.rb | 11 +- spec/support/fake_s3.rb | 141 +- spec/support/helpers.rb | 42 +- spec/support/imap_helper.rb | 13 +- spec/support/integration_helpers.rb | 26 +- spec/support/match_html_matcher.rb | 19 +- spec/support/onebox_helpers.rb | 7 +- spec/support/rate_limit_matcher.rb | 4 +- .../session_controller_helper_routes.rb | 13 +- .../shared_examples_for_stats_cacheable.rb | 20 +- spec/support/sidekiq_helpers.rb | 27 +- spec/support/system_helpers.rb | 9 +- spec/support/test_second_factor_action.rb | 4 +- spec/support/time_matcher.rb | 24 +- ...opic_guardian_can_see_consistency_check.rb | 3 +- spec/support/uploads_helpers.rb | 10 +- .../user_sidebar_serializer_attributes.rb | 65 +- .../shared_examples_for_versioned_model.rb | 2 +- spec/support/webauthn_integration_helpers.rb | 37 +- spec/swagger_helper.rb | 42 +- spec/system/admin_customize_themes_spec.rb | 12 +- .../page_objects/pages/admin_settings.rb | 6 +- spec/system/page_objects/pages/category.rb | 6 +- spec/system/page_objects/pages/search.rb | 6 +- spec/system/page_objects/pages/user.rb | 4 +- .../page_objects/pages/user_preferences.rb | 4 +- .../pages/user_preferences_sidebar.rb | 9 +- spec/system/search_spec.rb | 6 +- spec/system/user_page/staff_info_spec.rb | 12 +- .../user_preferences_navigation_spec.rb | 4 +- .../viewing_sidebar_preferences_spec.rb | 39 +- spec/tasks/incoming_emails_spec.rb | 16 +- spec/tasks/posts_spec.rb | 62 +- spec/tasks/redis_spec.rb | 37 +- spec/tasks/uploads_spec.rb | 47 +- spec/views/list/list.erb_spec.rb | 16 +- .../failure.html.erb_spec.rb | 8 +- spec/views/topics/show.html.erb_spec.rb | 16 +- 907 files changed, 58693 insertions(+), 45909 deletions(-) diff --git a/.streerc b/.streerc index 615e88d6e2..016c131272 100644 --- a/.streerc +++ b/.streerc @@ -3,4 +3,3 @@ --ignore-files=app/* --ignore-files=db/* --ignore-files=lib/* ---ignore-files=spec/* diff --git a/spec/fabricators/allowed_pm_users.rb b/spec/fabricators/allowed_pm_users.rb index c160aef83d..fd7286396d 100644 --- a/spec/fabricators/allowed_pm_users.rb +++ b/spec/fabricators/allowed_pm_users.rb @@ -1,5 +1,3 @@ # frozen_string_literal: true -Fabricator(:allowed_pm_user) do - user -end +Fabricator(:allowed_pm_user) { user } diff --git a/spec/fabricators/api_key_fabricator.rb b/spec/fabricators/api_key_fabricator.rb index 2f75d78c0b..b2c6748fb1 100644 --- a/spec/fabricators/api_key_fabricator.rb +++ b/spec/fabricators/api_key_fabricator.rb @@ -1,5 +1,3 @@ # frozen_string_literal: true -Fabricator(:api_key) do - -end +Fabricator(:api_key) {} diff --git a/spec/fabricators/associated_group_fabricator.rb b/spec/fabricators/associated_group_fabricator.rb index f7aff76893..15117bae95 100644 --- a/spec/fabricators/associated_group_fabricator.rb +++ b/spec/fabricators/associated_group_fabricator.rb @@ -2,6 +2,6 @@ Fabricator(:associated_group) do name { sequence(:name) { |n| "group_#{n}" } } - provider_name 'google' + provider_name "google" provider_id { SecureRandom.hex(20) } end diff --git a/spec/fabricators/badge_fabricator.rb b/spec/fabricators/badge_fabricator.rb index b99fe209e7..7ec3cb1b3b 100644 --- a/spec/fabricators/badge_fabricator.rb +++ b/spec/fabricators/badge_fabricator.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -Fabricator(:badge_type) do - name { sequence(:name) { |i| "Silver #{i}" } } -end +Fabricator(:badge_type) { name { sequence(:name) { |i| "Silver #{i}" } } } Fabricator(:badge) do name { sequence(:name) { |i| "Badge #{i}" } } diff --git a/spec/fabricators/bookmark_fabricator.rb b/spec/fabricators/bookmark_fabricator.rb index 30dafdf0f1..ba9271a22e 100644 --- a/spec/fabricators/bookmark_fabricator.rb +++ b/spec/fabricators/bookmark_fabricator.rb @@ -10,13 +10,14 @@ end Fabricator(:bookmark_next_business_day_reminder, from: :bookmark) do reminder_at do - date = if Time.zone.now.friday? - Time.zone.now + 3.days - elsif Time.zone.now.saturday? - Time.zone.now + 2.days - else - Time.zone.now + 1.day - end + date = + if Time.zone.now.friday? + Time.zone.now + 3.days + elsif Time.zone.now.saturday? + Time.zone.now + 2.days + else + Time.zone.now + 1.day + end date.iso8601 end reminder_set_at { Time.zone.now } diff --git a/spec/fabricators/category_fabricator.rb b/spec/fabricators/category_fabricator.rb index 0e14967af4..eb6dbcbd0c 100644 --- a/spec/fabricators/category_fabricator.rb +++ b/spec/fabricators/category_fabricator.rb @@ -6,9 +6,7 @@ Fabricator(:category) do user end -Fabricator(:category_with_definition, from: :category) do - skip_category_definition false -end +Fabricator(:category_with_definition, from: :category) { skip_category_definition false } Fabricator(:private_category, from: :category) do transient :group @@ -20,7 +18,10 @@ Fabricator(:private_category, from: :category) do after_build do |cat, transients| cat.update!(read_restricted: true) - cat.category_groups.build(group_id: transients[:group].id, permission_type: transients[:permission_type] || CategoryGroup.permission_types[:full]) + cat.category_groups.build( + group_id: transients[:group].id, + permission_type: transients[:permission_type] || CategoryGroup.permission_types[:full], + ) end end @@ -33,7 +34,7 @@ Fabricator(:link_category, from: :category) do end Fabricator(:mailinglist_mirror_category, from: :category) do - email_in 'list@example.com' + email_in "list@example.com" email_in_allow_strangers true mailinglist_mirror true end diff --git a/spec/fabricators/color_scheme_fabricator.rb b/spec/fabricators/color_scheme_fabricator.rb index 711b0f5e94..539926b71c 100644 --- a/spec/fabricators/color_scheme_fabricator.rb +++ b/spec/fabricators/color_scheme_fabricator.rb @@ -2,5 +2,7 @@ Fabricator(:color_scheme) do name { sequence(:name) { |i| "Palette #{i}" } } - color_scheme_colors(count: 2) { |attrs, i| Fabricate.build(:color_scheme_color, color_scheme: nil) } + color_scheme_colors(count: 2) do |attrs, i| + Fabricate.build(:color_scheme_color, color_scheme: nil) + end end diff --git a/spec/fabricators/external_upload_stub_fabricator.rb b/spec/fabricators/external_upload_stub_fabricator.rb index 5f8eb3e25e..e175aa0d25 100644 --- a/spec/fabricators/external_upload_stub_fabricator.rb +++ b/spec/fabricators/external_upload_stub_fabricator.rb @@ -5,7 +5,12 @@ Fabricator(:external_upload_stub) do created_by { Fabricate(:user) } original_filename "test.txt" - key { |attrs| FileStore::BaseStore.temporary_upload_path("test.txt", folder_prefix: attrs[:folder_prefix] || "") } + key do |attrs| + FileStore::BaseStore.temporary_upload_path( + "test.txt", + folder_prefix: attrs[:folder_prefix] || "", + ) + end upload_type "card_background" filesize 1024 status 1 @@ -14,16 +19,28 @@ end Fabricator(:image_external_upload_stub, from: :external_upload_stub) do original_filename "logo.png" filesize 1024 - key { |attrs| FileStore::BaseStore.temporary_upload_path("logo.png", folder_prefix: attrs[:folder_prefix] || "") } + key do |attrs| + FileStore::BaseStore.temporary_upload_path( + "logo.png", + folder_prefix: attrs[:folder_prefix] || "", + ) + end end Fabricator(:attachment_external_upload_stub, from: :external_upload_stub) do original_filename "file.pdf" filesize 1024 - key { |attrs| FileStore::BaseStore.temporary_upload_path("file.pdf", folder_prefix: attrs[:folder_prefix] || "") } + key do |attrs| + FileStore::BaseStore.temporary_upload_path( + "file.pdf", + folder_prefix: attrs[:folder_prefix] || "", + ) + end end Fabricator(:multipart_external_upload_stub, from: :external_upload_stub) do multipart true - external_upload_identifier { "#{SecureRandom.hex(6)}._#{SecureRandom.hex(6)}_#{SecureRandom.hex(6)}.d.ghQ" } + external_upload_identifier do + "#{SecureRandom.hex(6)}._#{SecureRandom.hex(6)}_#{SecureRandom.hex(6)}.d.ghQ" + end end diff --git a/spec/fabricators/group_fabricator.rb b/spec/fabricators/group_fabricator.rb index de9993d362..c0a13a3f39 100644 --- a/spec/fabricators/group_fabricator.rb +++ b/spec/fabricators/group_fabricator.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true -Fabricator(:group) do - name { sequence(:name) { |n| "my_group_#{n}" } } -end +Fabricator(:group) { name { sequence(:name) { |n| "my_group_#{n}" } } } Fabricator(:public_group, from: :group) do public_admission true diff --git a/spec/fabricators/invite_fabricator.rb b/spec/fabricators/invite_fabricator.rb index aedc23c177..d8a8747796 100644 --- a/spec/fabricators/invite_fabricator.rb +++ b/spec/fabricators/invite_fabricator.rb @@ -2,5 +2,5 @@ Fabricator(:invite) do invited_by(fabricator: :user) - email 'iceking@ADVENTURETIME.ooo' + email "iceking@ADVENTURETIME.ooo" end diff --git a/spec/fabricators/muted_user.rb b/spec/fabricators/muted_user.rb index 4bee8414e0..992c205abe 100644 --- a/spec/fabricators/muted_user.rb +++ b/spec/fabricators/muted_user.rb @@ -1,5 +1,3 @@ # frozen_string_literal: true -Fabricator(:muted_user) do - user -end +Fabricator(:muted_user) { user } diff --git a/spec/fabricators/notification_fabricator.rb b/spec/fabricators/notification_fabricator.rb index 6ee4c1bcfb..51d34032e4 100644 --- a/spec/fabricators/notification_fabricator.rb +++ b/spec/fabricators/notification_fabricator.rb @@ -27,7 +27,7 @@ Fabricator(:private_message_notification, from: :notification) do original_post_type: post.post_type, original_username: post.user.username, revision_number: nil, - display_username: post.user.username + display_username: post.user.username, }.to_json end end @@ -44,7 +44,7 @@ Fabricator(:bookmark_reminder_notification, from: :notification) do original_username: post.user.username, revision_number: nil, display_username: post.user.username, - bookmark_name: "Check out Mr Freeze's opinion here" + bookmark_name: "Check out Mr Freeze's opinion here", }.to_json end end @@ -58,7 +58,7 @@ Fabricator(:replied_notification, from: :notification) do original_post_id: post.id, original_username: post.user.username, revision_number: nil, - display_username: post.user.username + display_username: post.user.username, }.to_json end end @@ -73,7 +73,7 @@ Fabricator(:posted_notification, from: :notification) do original_post_type: post.post_type, original_username: post.user.username, revision_number: nil, - display_username: post.user.username + display_username: post.user.username, }.to_json end end @@ -87,7 +87,7 @@ Fabricator(:mentioned_notification, from: :notification) do original_post_type: attrs[:post].post_type, original_username: attrs[:post].user.username, revision_number: nil, - display_username: attrs[:post].user.username + display_username: attrs[:post].user.username, }.to_json end end @@ -101,7 +101,7 @@ Fabricator(:watching_first_post_notification, from: :notification) do original_post_type: attrs[:post].post_type, original_username: attrs[:post].user.username, revision_number: nil, - display_username: attrs[:post].user.username + display_username: attrs[:post].user.username, }.to_json end end diff --git a/spec/fabricators/permalink_fabricator.rb b/spec/fabricators/permalink_fabricator.rb index b285212606..0f21d1f2e6 100644 --- a/spec/fabricators/permalink_fabricator.rb +++ b/spec/fabricators/permalink_fabricator.rb @@ -1,5 +1,3 @@ # frozen_string_literal: true -Fabricator(:permalink) do - url { sequence(:url) { |i| "my/#{i}/url" } } -end +Fabricator(:permalink) { url { sequence(:url) { |i| "my/#{i}/url" } } } diff --git a/spec/fabricators/post_fabricator.rb b/spec/fabricators/post_fabricator.rb index e18628a141..07e8a73448 100644 --- a/spec/fabricators/post_fabricator.rb +++ b/spec/fabricators/post_fabricator.rb @@ -7,19 +7,17 @@ Fabricator(:post) do post_type Post.types[:regular] # Fabrication bypasses PostCreator, for performance reasons, where the counts are updated so we have to handle this manually here. - after_create do |post, _transients| - UserStatCountUpdater.increment!(post) - end + after_create { |post, _transients| UserStatCountUpdater.increment!(post) } end Fabricator(:post_with_long_raw_content, from: :post) do - raw 'This is a sample post with semi-long raw content. The raw content is also more than + raw "This is a sample post with semi-long raw content. The raw content is also more than two hundred characters to satisfy any test conditions that require content longer - than the typical test post raw content. It really is some long content, folks.' + than the typical test post raw content. It really is some long content, folks." end Fabricator(:post_with_youtube, from: :post) do - raw 'http://www.youtube.com/watch?v=9bZkp7q19f0' + raw "http://www.youtube.com/watch?v=9bZkp7q19f0" cooked '

    http://www.youtube.com/watch?v=9bZkp7q19f0

    ' end @@ -39,7 +37,7 @@ Fabricator(:basic_reply, from: :post) do user(fabricator: :coding_horror) reply_to_post_number 1 topic - raw 'this reply has no quotes' + raw "this reply has no quotes" end Fabricator(:reply, from: :post) do @@ -51,14 +49,12 @@ Fabricator(:reply, from: :post) do ' end -Fabricator(:post_with_plenty_of_images, from: :post) do - cooked <<~HTML +Fabricator(:post_with_plenty_of_images, from: :post) { cooked <<~HTML }

    With an emoji! smile

    HTML -end Fabricator(:post_with_uploaded_image, from: :post) do raw { "" } @@ -101,8 +97,7 @@ Fabricator(:post_with_uploads, from: :post) do " end -Fabricator(:post_with_uploads_and_links, from: :post) do - raw <<~MD +Fabricator(:post_with_uploads_and_links, from: :post) { raw <<~MD } Link Google @@ -110,7 +105,6 @@ Fabricator(:post_with_uploads_and_links, from: :post) do text.txt (20 Bytes) :smile: MD -end Fabricator(:post_with_external_links, from: :post) do user @@ -130,14 +124,15 @@ Fabricator(:private_message_post, from: :post) do transient :recipient user topic do |attrs| - Fabricate(:private_message_topic, + Fabricate( + :private_message_topic, user: attrs[:user], created_at: attrs[:created_at], subtype: TopicSubtype.user_to_user, topic_allowed_users: [ Fabricate.build(:topic_allowed_user, user: attrs[:user]), - Fabricate.build(:topic_allowed_user, user: attrs[:recipient] || Fabricate(:user)) - ] + Fabricate.build(:topic_allowed_user, user: attrs[:recipient] || Fabricate(:user)), + ], ) end raw "Ssshh! This is our secret conversation!" @@ -147,16 +142,15 @@ Fabricator(:group_private_message_post, from: :post) do transient :recipients user topic do |attrs| - Fabricate(:private_message_topic, + Fabricate( + :private_message_topic, user: attrs[:user], created_at: attrs[:created_at], subtype: TopicSubtype.user_to_user, - topic_allowed_users: [ - Fabricate.build(:topic_allowed_user, user: attrs[:user]), - ], + topic_allowed_users: [Fabricate.build(:topic_allowed_user, user: attrs[:user])], topic_allowed_groups: [ - Fabricate.build(:topic_allowed_group, group: attrs[:recipients] || Fabricate(:group)) - ] + Fabricate.build(:topic_allowed_group, group: attrs[:recipients] || Fabricate(:group)), + ], ) end raw "Ssshh! This is our group secret conversation!" @@ -165,13 +159,12 @@ end Fabricator(:private_message_post_one_user, from: :post) do user topic do |attrs| - Fabricate(:private_message_topic, + Fabricate( + :private_message_topic, user: attrs[:user], created_at: attrs[:created_at], subtype: TopicSubtype.user_to_user, - topic_allowed_users: [ - Fabricate.build(:topic_allowed_user, user: attrs[:user]), - ] + topic_allowed_users: [Fabricate.build(:topic_allowed_user, user: attrs[:user])], ) end raw "Ssshh! This is our secret conversation!" @@ -188,10 +181,6 @@ Fabricator(:post_via_email, from: :post) do end end -Fabricator(:whisper, from: :post) do - post_type Post.types[:whisper] -end +Fabricator(:whisper, from: :post) { post_type Post.types[:whisper] } -Fabricator(:small_action, from: :post) do - post_type Post.types[:small_action] -end +Fabricator(:small_action, from: :post) { post_type Post.types[:small_action] } diff --git a/spec/fabricators/post_revision_fabricator.rb b/spec/fabricators/post_revision_fabricator.rb index 059f1af5a9..848d184f59 100644 --- a/spec/fabricators/post_revision_fabricator.rb +++ b/spec/fabricators/post_revision_fabricator.rb @@ -4,7 +4,5 @@ Fabricator(:post_revision) do post user number 2 - modifications do - { "cooked" => ["

    BEFORE

    ", "

    AFTER

    "], "raw" => ["BEFORE", "AFTER"] } - end + modifications { { "cooked" => %w[

    BEFORE

    AFTER

    ], "raw" => %w[BEFORE AFTER] } } end diff --git a/spec/fabricators/reviewable_fabricator.rb b/spec/fabricators/reviewable_fabricator.rb index b2f2612040..2606a99c29 100644 --- a/spec/fabricators/reviewable_fabricator.rb +++ b/spec/fabricators/reviewable_fabricator.rb @@ -2,70 +2,75 @@ Fabricator(:reviewable) do reviewable_by_moderator true - type 'ReviewableUser' + type "ReviewableUser" created_by { Fabricate(:user) } target_id { Fabricate(:user).id } target_type "User" target_created_by { Fabricate(:user) } category score 1.23 - payload { - { list: [1, 2, 3], name: 'bandersnatch' } - } + payload { { list: [1, 2, 3], name: "bandersnatch" } } end Fabricator(:reviewable_queued_post_topic, class_name: :reviewable_queued_post) do reviewable_by_moderator true - type 'ReviewableQueuedPost' + type "ReviewableQueuedPost" created_by { Fabricate(:user) } category - payload { + payload do { raw: "hello world post contents.", title: "queued post title", - tags: ['cool', 'neat'], + tags: %w[cool neat], extra: "some extra data", - archetype: 'regular' + archetype: "regular", } - } + end end Fabricator(:reviewable_queued_post) do reviewable_by_moderator true - type 'ReviewableQueuedPost' + type "ReviewableQueuedPost" created_by { Fabricate(:user) } topic - payload { + payload do { raw: "hello world post contents.", reply_to_post_number: 1, via_email: true, - raw_email: 'store_me', + raw_email: "store_me", auto_track: true, - custom_fields: { hello: 'world' }, - cooking_options: { cat: 'hat' }, + custom_fields: { + hello: "world", + }, + cooking_options: { + cat: "hat", + }, cook_method: Post.cook_methods[:raw_html], - image_sizes: { "http://foo.bar/image.png" => { "width" => 0, "height" => 222 } } + image_sizes: { + "http://foo.bar/image.png" => { + "width" => 0, + "height" => 222, + }, + }, } - } + end end Fabricator(:reviewable_flagged_post) do reviewable_by_moderator true - type 'ReviewableFlaggedPost' + type "ReviewableFlaggedPost" created_by { Fabricate(:user) } topic - target_type 'Post' + target_type "Post" target { Fabricate(:post) } - reviewable_scores { |p| [ - Fabricate.build(:reviewable_score, reviewable_id: p[:id]), - ]} + reviewable_scores { |p| [Fabricate.build(:reviewable_score, reviewable_id: p[:id])] } end Fabricator(:reviewable_user) do reviewable_by_moderator true - type 'ReviewableUser' + type "ReviewableUser" created_by { Fabricate(:user) } - target_type 'User' + target_type "User" target { Fabricate(:user) } end diff --git a/spec/fabricators/screened_url_fabricator.rb b/spec/fabricators/screened_url_fabricator.rb index 8533946c65..7225852a5d 100644 --- a/spec/fabricators/screened_url_fabricator.rb +++ b/spec/fabricators/screened_url_fabricator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true Fabricator(:screened_url) do - url { sequence(:url) { |n| "spammers#{n}.org/buy/stuff" } } - domain { sequence(:domain) { |n| "spammers#{n}.org" } } + url { sequence(:url) { |n| "spammers#{n}.org/buy/stuff" } } + domain { sequence(:domain) { |n| "spammers#{n}.org" } } action_type ScreenedEmail.actions[:do_nothing] end diff --git a/spec/fabricators/sidebar_section_link_fabricator.rb b/spec/fabricators/sidebar_section_link_fabricator.rb index 4a5d768380..569de894fe 100644 --- a/spec/fabricators/sidebar_section_link_fabricator.rb +++ b/spec/fabricators/sidebar_section_link_fabricator.rb @@ -1,13 +1,9 @@ # frozen_string_literal: true -Fabricator(:sidebar_section_link) do - user -end +Fabricator(:sidebar_section_link) { user } Fabricator(:category_sidebar_section_link, from: :sidebar_section_link) do linkable(fabricator: :category) end -Fabricator(:tag_sidebar_section_link, from: :sidebar_section_link) do - linkable(fabricator: :tag) -end +Fabricator(:tag_sidebar_section_link, from: :sidebar_section_link) { linkable(fabricator: :tag) } diff --git a/spec/fabricators/single_sign_on_record_fabricator.rb b/spec/fabricators/single_sign_on_record_fabricator.rb index 95c6a6028f..1afbc72706 100644 --- a/spec/fabricators/single_sign_on_record_fabricator.rb +++ b/spec/fabricators/single_sign_on_record_fabricator.rb @@ -5,5 +5,7 @@ Fabricator(:single_sign_on_record) do external_id { sequence(:external_id) { |i| "ext_#{i}" } } external_username { sequence(:username) { |i| "bruce#{i}" } } external_email { sequence(:email) { |i| "bruce#{i}@wayne.com" } } - last_payload { sequence(:last_payload) { |i| "nonce=#{i}1870a940bbcbb46f06880ed338d58a07&name=" } } + last_payload do + sequence(:last_payload) { |i| "nonce=#{i}1870a940bbcbb46f06880ed338d58a07&name=" } + end end diff --git a/spec/fabricators/tag_fabricator.rb b/spec/fabricators/tag_fabricator.rb index c2192294ef..6d68de48f4 100644 --- a/spec/fabricators/tag_fabricator.rb +++ b/spec/fabricators/tag_fabricator.rb @@ -1,5 +1,3 @@ # frozen_string_literal: true -Fabricator(:tag) do - name { sequence(:name) { |i| "tag#{i + 1}" } } -end +Fabricator(:tag) { name { sequence(:name) { |i| "tag#{i + 1}" } } } diff --git a/spec/fabricators/tag_group_fabricator.rb b/spec/fabricators/tag_group_fabricator.rb index 990ec85d72..64a38f9004 100644 --- a/spec/fabricators/tag_group_fabricator.rb +++ b/spec/fabricators/tag_group_fabricator.rb @@ -1,5 +1,3 @@ # frozen_string_literal: true -Fabricator(:tag_group) do - name { sequence(:name) { |i| "tag_group_#{i}" } } -end +Fabricator(:tag_group) { name { sequence(:name) { |i| "tag_group_#{i}" } } } diff --git a/spec/fabricators/topic_allowed_user_fabricator.rb b/spec/fabricators/topic_allowed_user_fabricator.rb index eb3d75f6e2..99494eb170 100644 --- a/spec/fabricators/topic_allowed_user_fabricator.rb +++ b/spec/fabricators/topic_allowed_user_fabricator.rb @@ -1,5 +1,3 @@ # frozen_string_literal: true -Fabricator(:topic_allowed_user) do - user -end +Fabricator(:topic_allowed_user) { user } diff --git a/spec/fabricators/topic_fabricator.rb b/spec/fabricators/topic_fabricator.rb index b65cd48d7f..c693fc213c 100644 --- a/spec/fabricators/topic_fabricator.rb +++ b/spec/fabricators/topic_fabricator.rb @@ -8,25 +8,21 @@ Fabricator(:topic) do end end -Fabricator(:deleted_topic, from: :topic) do - deleted_at { 1.minute.ago } -end +Fabricator(:deleted_topic, from: :topic) { deleted_at { 1.minute.ago } } -Fabricator(:closed_topic, from: :topic) do - closed true -end +Fabricator(:closed_topic, from: :topic) { closed true } -Fabricator(:banner_topic, from: :topic) do - archetype Archetype.banner -end +Fabricator(:banner_topic, from: :topic) { archetype Archetype.banner } Fabricator(:private_message_topic, from: :topic) do transient :recipient category_id { nil } title { sequence(:title) { |i| "This is a private message #{i}" } } archetype "private_message" - topic_allowed_users { |t| [ - Fabricate.build(:topic_allowed_user, user: t[:user]), - Fabricate.build(:topic_allowed_user, user: t[:recipient] || Fabricate(:user)) - ]} + topic_allowed_users do |t| + [ + Fabricate.build(:topic_allowed_user, user: t[:user]), + Fabricate.build(:topic_allowed_user, user: t[:recipient] || Fabricate(:user)), + ] + end end diff --git a/spec/fabricators/upload_fabricator.rb b/spec/fabricators/upload_fabricator.rb index cb47f293b0..aec9a20721 100644 --- a/spec/fabricators/upload_fabricator.rb +++ b/spec/fabricators/upload_fabricator.rb @@ -12,9 +12,7 @@ Fabricator(:upload) do url do |attrs| sequence(:url) do |n| - Discourse.store.get_path_for( - "original", n + 1, attrs[:sha1], ".#{attrs[:extension]}" - ) + Discourse.store.get_path_for("original", n + 1, attrs[:sha1], ".#{attrs[:extension]}") end end @@ -35,15 +33,16 @@ Fabricator(:image_upload, from: :upload) do transient color: "white" after_create do |upload, transients| - file = Tempfile.new(['fabricated', '.png']) + file = Tempfile.new(%w[fabricated .png]) `convert -size #{upload.width}x#{upload.height} xc:#{transients[:color]} "#{file.path}"` upload.url = Discourse.store.store_upload(file, upload) upload.sha1 = Upload.generate_digest(file.path) - WebMock - .stub_request(:get, "http://#{Discourse.current_hostname}#{upload.url}") - .to_return(status: 200, body: File.new(file.path)) + WebMock.stub_request(:get, "http://#{Discourse.current_hostname}#{upload.url}").to_return( + status: 200, + body: File.new(file.path), + ) end end @@ -72,13 +71,9 @@ end Fabricator(:upload_s3, from: :upload) do url do |attrs| sequence(:url) do |n| - path = +Discourse.store.get_path_for( - "original", n + 1, attrs[:sha1], ".#{attrs[:extension]}" - ) + path = +Discourse.store.get_path_for("original", n + 1, attrs[:sha1], ".#{attrs[:extension]}") - if Rails.configuration.multisite - path.prepend(File.join(Discourse.store.upload_path, "/")) - end + path.prepend(File.join(Discourse.store.upload_path, "/")) if Rails.configuration.multisite File.join(Discourse.store.absolute_base_url, path) end @@ -87,15 +82,13 @@ end Fabricator(:s3_image_upload, from: :upload_s3) do after_create do |upload| - file = Tempfile.new(['fabricated', '.png']) + file = Tempfile.new(%w[fabricated .png]) `convert -size #{upload.width}x#{upload.height} xc:white "#{file.path}"` upload.url = Discourse.store.store_upload(file, upload) upload.sha1 = Upload.generate_digest(file.path) - WebMock - .stub_request(:get, upload.url) - .to_return(status: 200, body: File.new(file.path)) + WebMock.stub_request(:get, upload.url).to_return(status: 200, body: File.new(file.path)) end end diff --git a/spec/fabricators/user_api_key_fabricator.rb b/spec/fabricators/user_api_key_fabricator.rb index 3ca55431c8..fc2e805ecb 100644 --- a/spec/fabricators/user_api_key_fabricator.rb +++ b/spec/fabricators/user_api_key_fabricator.rb @@ -3,15 +3,15 @@ Fabricator(:user_api_key) do user client_id { SecureRandom.hex } - application_name 'some app' + application_name "some app" end Fabricator(:user_api_key_scope) Fabricator(:readonly_user_api_key, from: :user_api_key) do - scopes { [Fabricate.build(:user_api_key_scope, name: 'read')] } + scopes { [Fabricate.build(:user_api_key_scope, name: "read")] } end Fabricator(:bookmarks_calendar_user_api_key, from: :user_api_key) do - scopes { [Fabricate.build(:user_api_key_scope, name: 'bookmarks_calendar')] } + scopes { [Fabricate.build(:user_api_key_scope, name: "bookmarks_calendar")] } end diff --git a/spec/fabricators/user_avatar_fabricator.rb b/spec/fabricators/user_avatar_fabricator.rb index f7431bcbb1..773947f04b 100644 --- a/spec/fabricators/user_avatar_fabricator.rb +++ b/spec/fabricators/user_avatar_fabricator.rb @@ -1,5 +1,3 @@ # frozen_string_literal: true -Fabricator(:user_avatar) do - user -end +Fabricator(:user_avatar) { user } diff --git a/spec/fabricators/user_fabricator.rb b/spec/fabricators/user_fabricator.rb index bee8812bd8..1a0fcca19a 100644 --- a/spec/fabricators/user_fabricator.rb +++ b/spec/fabricators/user_fabricator.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true -Fabricator(:user_stat) do -end +Fabricator(:user_stat) {} Fabricator(:user, class_name: :user) do - name 'Bruce Wayne' + name "Bruce Wayne" username { sequence(:username) { |i| "bruce#{i}" } } email { sequence(:email) { |i| "bruce#{i}@wayne.com" } } - password 'myawesomepassword' + password "myawesomepassword" trust_level TrustLevel[1] ip_address { sequence(:ip_address) { |i| "99.232.23.#{i % 254}" } } active true @@ -18,31 +17,31 @@ Fabricator(:user_with_secondary_email, from: :user) do end Fabricator(:coding_horror, from: :user) do - name 'Coding Horror' - username 'CodingHorror' - email 'jeff@somewhere.com' - password 'mymoreawesomepassword' + name "Coding Horror" + username "CodingHorror" + email "jeff@somewhere.com" + password "mymoreawesomepassword" end Fabricator(:evil_trout, from: :user) do - name 'Evil Trout' - username 'eviltrout' - email 'eviltrout@somewhere.com' - password 'imafish123' + name "Evil Trout" + username "eviltrout" + email "eviltrout@somewhere.com" + password "imafish123" end Fabricator(:walter_white, from: :user) do - name 'Walter White' - username 'heisenberg' - email 'wwhite@bluemeth.com' - password 'letscook123' + name "Walter White" + username "heisenberg" + email "wwhite@bluemeth.com" + password "letscook123" end Fabricator(:inactive_user, from: :user) do - name 'Inactive User' - username 'inactive_user' - email 'inactive@idontexist.com' - password 'qwerqwer123' + name "Inactive User" + username "inactive_user" + email "inactive@idontexist.com" + password "qwerqwer123" active false end @@ -59,7 +58,7 @@ Fabricator(:moderator, from: :user) do end Fabricator(:admin, from: :user) do - name 'Anne Admin' + name "Anne Admin" username { sequence(:username) { |i| "anne#{i}" } } email { sequence(:email) { |i| "anne#{i}@discourse.org" } } admin true @@ -71,17 +70,17 @@ Fabricator(:admin, from: :user) do end Fabricator(:newuser, from: :user) do - name 'Newbie Newperson' - username 'newbie' - email 'newbie@new.com' + name "Newbie Newperson" + username "newbie" + email "newbie@new.com" trust_level TrustLevel[0] end Fabricator(:active_user, from: :user) do - name 'Luke Skywalker' + name "Luke Skywalker" username { sequence(:username) { |i| "luke#{i}" } } email { sequence(:email) { |i| "luke#{i}@skywalker.com" } } - password 'myawesomepassword' + password "myawesomepassword" trust_level TrustLevel[1] after_create do |user| @@ -91,29 +90,25 @@ Fabricator(:active_user, from: :user) do end Fabricator(:leader, from: :user) do - name 'Veteran McVeteranish' + name "Veteran McVeteranish" username { sequence(:username) { |i| "leader#{i}" } } email { sequence(:email) { |i| "leader#{i}@leaderfun.com" } } trust_level TrustLevel[3] end -Fabricator(:trust_level_0, from: :user) do - trust_level TrustLevel[0] -end +Fabricator(:trust_level_0, from: :user) { trust_level TrustLevel[0] } -Fabricator(:trust_level_1, from: :user) do - trust_level TrustLevel[1] -end +Fabricator(:trust_level_1, from: :user) { trust_level TrustLevel[1] } Fabricator(:trust_level_4, from: :user) do - name 'Leader McElderson' + name "Leader McElderson" username { sequence(:username) { |i| "tl4#{i}" } } email { sequence(:email) { |i| "tl4#{i}@elderfun.com" } } trust_level TrustLevel[4] end Fabricator(:anonymous, from: :user) do - name '' + name "" username { sequence(:username) { |i| "anonymous#{i}" } } email { sequence(:email) { |i| "anonymous#{i}@anonymous.com" } } trust_level TrustLevel[1] @@ -127,13 +122,9 @@ Fabricator(:anonymous, from: :user) do end end -Fabricator(:staged, from: :user) do - staged true -end +Fabricator(:staged, from: :user) { staged true } -Fabricator(:unicode_user, from: :user) do - username { sequence(:username) { |i| "Löwe#{i}" } } -end +Fabricator(:unicode_user, from: :user) { username { sequence(:username) { |i| "Löwe#{i}" } } } Fabricator(:bot, from: :user) do id do diff --git a/spec/fabricators/user_field_fabricator.rb b/spec/fabricators/user_field_fabricator.rb index c8019b390a..8534e80657 100644 --- a/spec/fabricators/user_field_fabricator.rb +++ b/spec/fabricators/user_field_fabricator.rb @@ -3,7 +3,7 @@ Fabricator(:user_field) do name { sequence(:name) { |i| "field_#{i}" } } description "user field description" - field_type 'text' + field_type "text" editable true required true end diff --git a/spec/fabricators/user_history_fabricator.rb b/spec/fabricators/user_history_fabricator.rb index 4464b03c7c..72bdd16075 100644 --- a/spec/fabricators/user_history_fabricator.rb +++ b/spec/fabricators/user_history_fabricator.rb @@ -1,4 +1,3 @@ # frozen_string_literal: true -Fabricator(:user_history) do -end +Fabricator(:user_history) {} diff --git a/spec/fabricators/user_option_fabricator.rb b/spec/fabricators/user_option_fabricator.rb index 17c0cbc788..77e55384a8 100644 --- a/spec/fabricators/user_option_fabricator.rb +++ b/spec/fabricators/user_option_fabricator.rb @@ -1,4 +1,3 @@ # frozen_string_literal: true -Fabricator(:user_option) do -end +Fabricator(:user_option) {} diff --git a/spec/fabricators/user_profile_fabricator.rb b/spec/fabricators/user_profile_fabricator.rb index 042474ed8b..ada2483eeb 100644 --- a/spec/fabricators/user_profile_fabricator.rb +++ b/spec/fabricators/user_profile_fabricator.rb @@ -5,6 +5,4 @@ Fabricator(:user_profile) do user end -Fabricator(:user_profile_long, from: :user_profile) do - bio_raw ("trout" * 1000) -end +Fabricator(:user_profile_long, from: :user_profile) { bio_raw ("trout" * 1000) } diff --git a/spec/fabricators/user_second_factor_fabricator.rb b/spec/fabricators/user_second_factor_fabricator.rb index cbb2d5aa4a..8d4a517631 100644 --- a/spec/fabricators/user_second_factor_fabricator.rb +++ b/spec/fabricators/user_second_factor_fabricator.rb @@ -2,7 +2,7 @@ Fabricator(:user_second_factor_totp, from: :user_second_factor) do user - data 'rcyryaqage3jexfj' + data "rcyryaqage3jexfj" enabled true method UserSecondFactor.methods[:totp] end diff --git a/spec/fabricators/user_security_key_fabricator.rb b/spec/fabricators/user_security_key_fabricator.rb index 827413a18d..edc4279f60 100644 --- a/spec/fabricators/user_security_key_fabricator.rb +++ b/spec/fabricators/user_security_key_fabricator.rb @@ -5,8 +5,12 @@ Fabricator(:user_security_key) do # Note: these values are valid and decode to a credential ID and COSE public key # HOWEVER they are largely useless unless you have the device that created # them. It is nice to have an approximation though. - credential_id { 'mJAJ4CznTO0SuLkJbYwpgK75ao4KMNIPlU5KWM92nq39kRbXzI9mSv6GxTcsMYoiPgaouNw7b7zBiS4vsQaO6A==' } - public_key { 'pQECAyYgASFYIMNgw4GCpwBUlR2SznJ1yY7B9yFvsuxhfo+C9kcA4IitIlggRdofrCezymy2B/YarX+gfB6gZKg648/cHIMjf6wWmmU=' } + credential_id do + "mJAJ4CznTO0SuLkJbYwpgK75ao4KMNIPlU5KWM92nq39kRbXzI9mSv6GxTcsMYoiPgaouNw7b7zBiS4vsQaO6A==" + end + public_key do + "pQECAyYgASFYIMNgw4GCpwBUlR2SznJ1yY7B9yFvsuxhfo+C9kcA4IitIlggRdofrCezymy2B/YarX+gfB6gZKg648/cHIMjf6wWmmU=" + end enabled true factor_type { UserSecurityKey.factor_types[:second_factor] } name { sequence(:name) { |i| "Security Key #{i + 1}" } } diff --git a/spec/fabricators/web_hook_fabricator.rb b/spec/fabricators/web_hook_fabricator.rb index 3e490b3f4c..97f753bd13 100644 --- a/spec/fabricators/web_hook_fabricator.rb +++ b/spec/fabricators/web_hook_fabricator.rb @@ -1,62 +1,48 @@ # frozen_string_literal: true Fabricator(:web_hook) do - payload_url 'https://meta.discourse.org/webhook_listener' - content_type WebHook.content_types['application/json'] + payload_url "https://meta.discourse.org/webhook_listener" + content_type WebHook.content_types["application/json"] wildcard_web_hook false - secret 'my_lovely_secret_for_web_hook' + secret "my_lovely_secret_for_web_hook" verify_certificate true active true - transient post_hook: WebHookEventType.find_by(name: 'post') + transient post_hook: WebHookEventType.find_by(name: "post") - after_build do |web_hook, transients| - web_hook.web_hook_event_types << transients[:post_hook] - end + after_build { |web_hook, transients| web_hook.web_hook_event_types << transients[:post_hook] } end -Fabricator(:inactive_web_hook, from: :web_hook) do - active false -end +Fabricator(:inactive_web_hook, from: :web_hook) { active false } -Fabricator(:wildcard_web_hook, from: :web_hook) do - wildcard_web_hook true -end +Fabricator(:wildcard_web_hook, from: :web_hook) { wildcard_web_hook true } Fabricator(:topic_web_hook, from: :web_hook) do - transient topic_hook: WebHookEventType.find_by(name: 'topic') + transient topic_hook: WebHookEventType.find_by(name: "topic") - after_build do |web_hook, transients| - web_hook.web_hook_event_types = [transients[:topic_hook]] - end + after_build { |web_hook, transients| web_hook.web_hook_event_types = [transients[:topic_hook]] } end Fabricator(:post_web_hook, from: :web_hook) do - transient topic_hook: WebHookEventType.find_by(name: 'post') + transient topic_hook: WebHookEventType.find_by(name: "post") - after_build do |web_hook, transients| - web_hook.web_hook_event_types = [transients[:post_hook]] - end + after_build { |web_hook, transients| web_hook.web_hook_event_types = [transients[:post_hook]] } end Fabricator(:user_web_hook, from: :web_hook) do - transient user_hook: WebHookEventType.find_by(name: 'user') + transient user_hook: WebHookEventType.find_by(name: "user") - after_build do |web_hook, transients| - web_hook.web_hook_event_types = [transients[:user_hook]] - end + after_build { |web_hook, transients| web_hook.web_hook_event_types = [transients[:user_hook]] } end Fabricator(:group_web_hook, from: :web_hook) do - transient group_hook: WebHookEventType.find_by(name: 'group') + transient group_hook: WebHookEventType.find_by(name: "group") - after_build do |web_hook, transients| - web_hook.web_hook_event_types = [transients[:group_hook]] - end + after_build { |web_hook, transients| web_hook.web_hook_event_types = [transients[:group_hook]] } end Fabricator(:category_web_hook, from: :web_hook) do - transient category_hook: WebHookEventType.find_by(name: 'category') + transient category_hook: WebHookEventType.find_by(name: "category") after_build do |web_hook, transients| web_hook.web_hook_event_types = [transients[:category_hook]] @@ -64,15 +50,13 @@ Fabricator(:category_web_hook, from: :web_hook) do end Fabricator(:tag_web_hook, from: :web_hook) do - transient tag_hook: WebHookEventType.find_by(name: 'tag') + transient tag_hook: WebHookEventType.find_by(name: "tag") - after_build do |web_hook, transients| - web_hook.web_hook_event_types = [transients[:tag_hook]] - end + after_build { |web_hook, transients| web_hook.web_hook_event_types = [transients[:tag_hook]] } end Fabricator(:reviewable_web_hook, from: :web_hook) do - transient reviewable_hook: WebHookEventType.find_by(name: 'reviewable') + transient reviewable_hook: WebHookEventType.find_by(name: "reviewable") after_build do |web_hook, transients| web_hook.web_hook_event_types = [transients[:reviewable_hook]] @@ -80,7 +64,7 @@ Fabricator(:reviewable_web_hook, from: :web_hook) do end Fabricator(:notification_web_hook, from: :web_hook) do - transient notification_hook: WebHookEventType.find_by(name: 'notification') + transient notification_hook: WebHookEventType.find_by(name: "notification") after_build do |web_hook, transients| web_hook.web_hook_event_types = [transients[:notification_hook]] @@ -88,7 +72,7 @@ Fabricator(:notification_web_hook, from: :web_hook) do end Fabricator(:user_badge_web_hook, from: :web_hook) do - transient user_badge_hook: WebHookEventType.find_by(name: 'user_badge') + transient user_badge_hook: WebHookEventType.find_by(name: "user_badge") after_build do |web_hook, transients| web_hook.web_hook_event_types = [transients[:user_badge_hook]] @@ -96,7 +80,7 @@ Fabricator(:user_badge_web_hook, from: :web_hook) do end Fabricator(:group_user_web_hook, from: :web_hook) do - transient group_user_hook: WebHookEventType.find_by(name: 'group_user') + transient group_user_hook: WebHookEventType.find_by(name: "group_user") after_build do |web_hook, transients| web_hook.web_hook_event_types = [transients[:group_user_hook]] @@ -104,15 +88,13 @@ Fabricator(:group_user_web_hook, from: :web_hook) do end Fabricator(:like_web_hook, from: :web_hook) do - transient like_hook: WebHookEventType.find_by(name: 'like') + transient like_hook: WebHookEventType.find_by(name: "like") - after_build do |web_hook, transients| - web_hook.web_hook_event_types = [transients[:like_hook]] - end + after_build { |web_hook, transients| web_hook.web_hook_event_types = [transients[:like_hook]] } end Fabricator(:user_promoted_web_hook, from: :web_hook) do - transient user_promoted_hook: WebHookEventType.find_by(name: 'user_promoted') + transient user_promoted_hook: WebHookEventType.find_by(name: "user_promoted") after_build do |web_hook, transients| web_hook.web_hook_event_types = [transients[:user_promoted_hook]] diff --git a/spec/fixtures/db/post_migrate/change/20990309014015_drop_email_logs.rb b/spec/fixtures/db/post_migrate/change/20990309014015_drop_email_logs.rb index 7b4b1e2252..441440772a 100644 --- a/spec/fixtures/db/post_migrate/change/20990309014015_drop_email_logs.rb +++ b/spec/fixtures/db/post_migrate/change/20990309014015_drop_email_logs.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class DropEmailLogs < ActiveRecord::Migration[5.2] - DROPPED_TABLES ||= %i{email_logs} + DROPPED_TABLES ||= %i[email_logs] def change drop_table :email_logs diff --git a/spec/fixtures/db/post_migrate/drop_column/20990309014014_drop_post_columns.rb b/spec/fixtures/db/post_migrate/drop_column/20990309014014_drop_post_columns.rb index 8390f83207..556489f255 100644 --- a/spec/fixtures/db/post_migrate/drop_column/20990309014014_drop_post_columns.rb +++ b/spec/fixtures/db/post_migrate/drop_column/20990309014014_drop_post_columns.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true class DropPostColumns < ActiveRecord::Migration[5.2] - DROPPED_COLUMNS ||= { - posts: %i{via_email raw_email} - } + DROPPED_COLUMNS ||= { posts: %i[via_email raw_email] } def up remove_column :posts, :via_email diff --git a/spec/fixtures/db/post_migrate/drop_table/20990309014013_drop_email_logs_table.rb b/spec/fixtures/db/post_migrate/drop_table/20990309014013_drop_email_logs_table.rb index 5d07960ea7..ed8d31d7e6 100644 --- a/spec/fixtures/db/post_migrate/drop_table/20990309014013_drop_email_logs_table.rb +++ b/spec/fixtures/db/post_migrate/drop_table/20990309014013_drop_email_logs_table.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class DropEmailLogsTable < ActiveRecord::Migration[5.2] - DROPPED_TABLES ||= %i{email_logs} + DROPPED_TABLES ||= %i[email_logs] def up drop_table :email_logs diff --git a/spec/fixtures/plugins/csp_extension/plugin.rb b/spec/fixtures/plugins/csp_extension/plugin.rb index e694598c4a..0991754eb6 100644 --- a/spec/fixtures/plugins/csp_extension/plugin.rb +++ b/spec/fixtures/plugins/csp_extension/plugin.rb @@ -6,8 +6,8 @@ # authors: xrav3nz extend_content_security_policy( - script_src: ['https://from-plugin.com', '/local/path'], - object_src: ['https://test-stripping.com'], - frame_ancestors: ['https://frame-ancestors-plugin.ext'], - manifest_src: ['https://manifest-src.com'] + script_src: %w[https://from-plugin.com /local/path], + object_src: ["https://test-stripping.com"], + frame_ancestors: ["https://frame-ancestors-plugin.ext"], + manifest_src: ["https://manifest-src.com"], ) diff --git a/spec/fixtures/plugins/my_plugin/plugin.rb b/spec/fixtures/plugins/my_plugin/plugin.rb index 2253f3a1b0..62c7ed4dc5 100644 --- a/spec/fixtures/plugins/my_plugin/plugin.rb +++ b/spec/fixtures/plugins/my_plugin/plugin.rb @@ -5,8 +5,7 @@ # version: 0.1 # authors: Frank Zappa -auth_provider title: 'with Ubuntu', - authenticator: Auth::FacebookAuthenticator.new +auth_provider title: "with Ubuntu", authenticator: Auth::FacebookAuthenticator.new register_javascript < HTML - SiteSetting.opengraph_image = Fabricate(:upload, - url: '/images/og-image.svg' - ) + SiteSetting.opengraph_image = Fabricate(:upload, url: "/images/og-image.svg") expect(helper.crawlable_meta_data).to include(<<~HTML) HTML - SiteSetting.twitter_summary_large_image = Fabricate(:upload, - url: '/images/twitter.png' - ) + SiteSetting.twitter_summary_large_image = Fabricate(:upload, url: "/images/twitter.png") expect(helper.crawlable_meta_data).to include(<<~HTML) HTML - SiteSetting.twitter_summary_large_image = Fabricate(:upload, - url: '/images/twitter.svg' - ) + SiteSetting.twitter_summary_large_image = Fabricate(:upload, url: "/images/twitter.svg") expect(helper.crawlable_meta_data).to include(<<~HTML) HTML - SiteSetting.logo = Fabricate(:upload, url: '/images/d-logo-sketch.svg') + SiteSetting.logo = Fabricate(:upload, url: "/images/d-logo-sketch.svg") expect(helper.crawlable_meta_data).not_to include("twitter:image") end end end - describe 'discourse_color_scheme_stylesheets' do + describe "discourse_color_scheme_stylesheets" do fab!(:user) { Fabricate(:user) } - it 'returns a stylesheet link tag by default' do + it "returns a stylesheet link tag by default" do cs_stylesheets = helper.discourse_color_scheme_stylesheets expect(cs_stylesheets).to include("stylesheets/color_definitions") end - it 'returns two color scheme link tags when dark mode is enabled' do - SiteSetting.default_dark_mode_color_scheme_id = ColorScheme.where(name: "Dark").pluck_first(:id) + it "returns two color scheme link tags when dark mode is enabled" do + SiteSetting.default_dark_mode_color_scheme_id = + ColorScheme.where(name: "Dark").pluck_first(:id) cs_stylesheets = helper.discourse_color_scheme_stylesheets expect(cs_stylesheets).to include("(prefers-color-scheme: dark)") expect(cs_stylesheets.scan("stylesheets/color_definitions").size).to eq(2) end - it 'handles a missing dark color scheme gracefully' do + it "handles a missing dark color scheme gracefully" do scheme = ColorScheme.create!(name: "pyramid") SiteSetting.default_dark_mode_color_scheme_id = scheme.id scheme.destroy! @@ -638,7 +671,7 @@ RSpec.describe ApplicationHelper do context "with custom light scheme" do before do - @new_cs = Fabricate(:color_scheme, name: 'Flamboyant') + @new_cs = Fabricate(:color_scheme, name: "Flamboyant") user.user_option.color_scheme_id = @new_cs.id user.user_option.save! helper.request.env[Auth::DefaultCurrentUserProvider::CURRENT_USER_KEY] = user @@ -673,9 +706,10 @@ RSpec.describe ApplicationHelper do user.user_option.dark_scheme_id = -1 user.user_option.save! helper.request.env[Auth::DefaultCurrentUserProvider::CURRENT_USER_KEY] = user - @new_cs = Fabricate(:color_scheme, name: 'Custom Color Scheme') + @new_cs = Fabricate(:color_scheme, name: "Custom Color Scheme") - SiteSetting.default_dark_mode_color_scheme_id = ColorScheme.where(name: "Dark").pluck_first(:id) + SiteSetting.default_dark_mode_color_scheme_id = + ColorScheme.where(name: "Dark").pluck_first(:id) end it "returns no dark scheme stylesheet when user has disabled that option" do @@ -711,23 +745,24 @@ RSpec.describe ApplicationHelper do end describe "dark_color_scheme?" do - it 'returns false for the base color scheme' do + it "returns false for the base color scheme" do expect(helper.dark_color_scheme?).to eq(false) end - it 'works correctly for a dark scheme' do - dark_theme = Theme.create( - name: "Dark", - user_id: -1, - color_scheme_id: ColorScheme.find_by(base_scheme_id: "Dark").id - ) + it "works correctly for a dark scheme" do + dark_theme = + Theme.create( + name: "Dark", + user_id: -1, + color_scheme_id: ColorScheme.find_by(base_scheme_id: "Dark").id, + ) helper.request.env[:resolved_theme_id] = dark_theme.id expect(helper.dark_color_scheme?).to eq(true) end end - describe 'html_lang' do + describe "html_lang" do fab!(:user) { Fabricate(:user) } before do @@ -735,12 +770,12 @@ RSpec.describe ApplicationHelper do SiteSetting.default_locale = :fr end - it 'returns default locale if no request' do + it "returns default locale if no request" do helper.request = nil expect(helper.html_lang).to eq(SiteSetting.default_locale) end - it 'returns current user locale if request' do + it "returns current user locale if request" do helper.request.env[Auth::DefaultCurrentUserProvider::CURRENT_USER_KEY] = user expect(helper.html_lang).to eq(I18n.locale.to_s) end diff --git a/spec/helpers/redis_snapshot_helper.rb b/spec/helpers/redis_snapshot_helper.rb index 92d5be9cab..fb7e6a9b40 100644 --- a/spec/helpers/redis_snapshot_helper.rb +++ b/spec/helpers/redis_snapshot_helper.rb @@ -2,12 +2,8 @@ module RedisSnapshotHelper def use_redis_snapshotting - before(:each) do - RedisSnapshot.begin_faux_transaction - end + before(:each) { RedisSnapshot.begin_faux_transaction } - after(:each) do - RedisSnapshot.end_faux_transaction - end + after(:each) { RedisSnapshot.end_faux_transaction } end end diff --git a/spec/helpers/topics_helper_spec.rb b/spec/helpers/topics_helper_spec.rb index 5f0462fd97..075770839a 100644 --- a/spec/helpers/topics_helper_spec.rb +++ b/spec/helpers/topics_helper_spec.rb @@ -1,13 +1,14 @@ # frozen_string_literal: true RSpec.describe TopicsHelper do - describe "#categories_breadcrumb" do let(:user) { Fabricate(:user) } let(:category) { Fabricate(:category_with_definition) } let(:subcategory) { Fabricate(:category_with_definition, parent_category_id: category.id) } - let(:subsubcategory) { Fabricate(:category_with_definition, parent_category_id: subcategory.id) } + let(:subsubcategory) do + Fabricate(:category_with_definition, parent_category_id: subcategory.id) + end it "works with sub-sub-categories" do SiteSetting.max_category_nesting = 3 diff --git a/spec/helpers/user_notifications_helper_spec.rb b/spec/helpers/user_notifications_helper_spec.rb index 175c7bbb64..94ef066d9a 100644 --- a/spec/helpers/user_notifications_helper_spec.rb +++ b/spec/helpers/user_notifications_helper_spec.rb @@ -3,18 +3,17 @@ RSpec.describe UserNotificationsHelper do let(:upload_path) { Discourse.store.upload_path } - describe '#email_excerpt' do - let(:paragraphs) { [ - "

    This is the first paragraph, but you should read more.

    ", - "

    And here is its friend, the second paragraph.

    " - ] } - - let(:cooked) do - paragraphs.join("\n") + describe "#email_excerpt" do + let(:paragraphs) do + [ + "

    This is the first paragraph, but you should read more.

    ", + "

    And here is its friend, the second paragraph.

    ", + ] end - let(:post_quote) do - <<~HTML + let(:cooked) { paragraphs.join("\n") } + + let(:post_quote) { <<~HTML }
    HTML - end let(:image_paragraph) do '

    ' end - let(:lightbox_image) do - <<~HTML + let(:lightbox_image) { <<~HTML }

    HTML - end let(:expected_lightbox_image) do '' @@ -53,14 +49,16 @@ RSpec.describe UserNotificationsHelper do end it "doesn't count emoji images" do - with_emoji = "

    Hi \":smile:\"

    " + with_emoji = + "

    Hi \":smile:\"

    " arg = ([with_emoji] + paragraphs).join("\n") SiteSetting.digest_min_excerpt_length = 50 expect(helper.email_excerpt(arg)).to eq([with_emoji, paragraphs[0]].join) end it "only counts link text" do - with_link = "

    Hi friends!

    " + with_link = + "

    Hi friends!

    " arg = ([with_link] + paragraphs).join("\n") SiteSetting.digest_min_excerpt_length = 50 expect(helper.email_excerpt(arg)).to eq([with_link, paragraphs[0]].join) @@ -81,11 +79,12 @@ RSpec.describe UserNotificationsHelper do

    AFTER

    HTML - expect(helper.email_excerpt(cooked)).to eq "

    BEFORE

    \n

    This is a user quote

    \n

    AFTER

    " + expect( + helper.email_excerpt(cooked), + ).to eq "

    BEFORE

    \n

    This is a user quote

    \n

    AFTER

    " end it "defaults to content after post quote (image w/ no text)" do - cooked = <<~HTML #{post_quote} #{image_paragraph} @@ -94,7 +93,8 @@ RSpec.describe UserNotificationsHelper do end it "defaults to content after post quote (onebox)" do - aside_onebox = '' + aside_onebox = + '' cooked = <<~HTML #{post_quote} #{aside_onebox} @@ -120,44 +120,40 @@ RSpec.describe UserNotificationsHelper do end end - describe '#logo_url' do - describe 'local store' do + describe "#logo_url" do + describe "local store" do let(:upload) { Fabricate(:upload, sha1: "somesha1") } - before do - SiteSetting.logo = upload - end + before { SiteSetting.logo = upload } - it 'should return the right URL' do + it "should return the right URL" do expect(helper.logo_url).to eq( - "http://test.localhost/#{upload_path}/original/1X/somesha1.png" + "http://test.localhost/#{upload_path}/original/1X/somesha1.png", ) end - describe 'when cdn path is configured' do + describe "when cdn path is configured" do before do - GlobalSetting.expects(:cdn_url) - .returns('https://some.localcdn.com') - .at_least_once + GlobalSetting.expects(:cdn_url).returns("https://some.localcdn.com").at_least_once end - it 'should return the right URL' do + it "should return the right URL" do expect(helper.logo_url).to eq( - "https://some.localcdn.com/#{upload_path}/original/1X/somesha1.png" + "https://some.localcdn.com/#{upload_path}/original/1X/somesha1.png", ) end end - describe 'when logo is an SVG' do + describe "when logo is an SVG" do let(:upload) { Fabricate(:upload, extension: "svg") } - it 'should return nil' do + it "should return nil" do expect(helper.logo_url).to eq(nil) end end end - describe 's3 store' do + describe "s3 store" do let(:upload) { Fabricate(:upload_s3, sha1: "somesha1") } before do @@ -165,32 +161,27 @@ RSpec.describe UserNotificationsHelper do SiteSetting.logo = upload end - it 'should return the right URL' do + it "should return the right URL" do expect(helper.logo_url).to eq( - "http://s3-upload-bucket.s3.dualstack.#{SiteSetting.s3_region}.amazonaws.com/original/1X/somesha1.png" + "http://s3-upload-bucket.s3.dualstack.#{SiteSetting.s3_region}.amazonaws.com/original/1X/somesha1.png", ) end - describe 'when global cdn path is configured' do - it 'should return the right url' do - GlobalSetting.stubs(:cdn_url).returns('https://some.cdn.com/cluster') + describe "when global cdn path is configured" do + it "should return the right url" do + GlobalSetting.stubs(:cdn_url).returns("https://some.cdn.com/cluster") expect(helper.logo_url).to eq( - "http://s3-upload-bucket.s3.dualstack.#{SiteSetting.s3_region}.amazonaws.com/original/1X/somesha1.png" + "http://s3-upload-bucket.s3.dualstack.#{SiteSetting.s3_region}.amazonaws.com/original/1X/somesha1.png", ) end end - describe 'when cdn path is configured' do - before do - SiteSetting.s3_cdn_url = 'https://some.cdn.com' + describe "when cdn path is configured" do + before { SiteSetting.s3_cdn_url = "https://some.cdn.com" } - end - - it 'should return the right url' do - expect(helper.logo_url).to eq( - "https://some.cdn.com/original/1X/somesha1.png" - ) + it "should return the right url" do + expect(helper.logo_url).to eq("https://some.cdn.com/original/1X/somesha1.png") end end end diff --git a/spec/import_export/category_exporter_spec.rb b/spec/import_export/category_exporter_spec.rb index 5fda50252b..4a41fe9e8b 100644 --- a/spec/import_export/category_exporter_spec.rb +++ b/spec/import_export/category_exporter_spec.rb @@ -3,26 +3,23 @@ require "import_export" RSpec.describe ImportExport::CategoryExporter do - fab!(:category) { Fabricate(:category) } fab!(:group) { Fabricate(:group) } fab!(:user) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) } fab!(:user3) { Fabricate(:user) } - before do - STDOUT.stubs(:write) - end + before { STDOUT.stubs(:write) } - describe '.perform' do - it 'export the category when it is found' do + describe ".perform" do + it "export the category when it is found" do data = ImportExport::CategoryExporter.new([category.id]).perform.export_data expect(data[:categories].count).to eq(1) expect(data[:groups].count).to eq(0) end - it 'export the category with permission groups' do + it "export the category with permission groups" do _category_group = Fabricate(:category_group, category: category, group: group) data = ImportExport::CategoryExporter.new([category.id]).perform.export_data @@ -30,7 +27,7 @@ RSpec.describe ImportExport::CategoryExporter do expect(data[:groups].count).to eq(1) end - it 'export multiple categories' do + it "export multiple categories" do category2 = Fabricate(:category) _category_group = Fabricate(:category_group, category: category, group: group) data = ImportExport::CategoryExporter.new([category.id, category2.id]).perform.export_data @@ -39,7 +36,7 @@ RSpec.describe ImportExport::CategoryExporter do expect(data[:groups].count).to eq(1) end - it 'export the category with topics and users' do + it "export the category with topics and users" do topic1 = Fabricate(:topic, category: category, user_id: -1) Fabricate(:post, topic: topic1, user: User.find(-1), post_number: 1) topic2 = Fabricate(:topic, category: category, user: user) @@ -54,5 +51,4 @@ RSpec.describe ImportExport::CategoryExporter do expect(data[:users].map { |u| u[:id] }).to match_array([user.id, user2.id, user3.id]) end end - end diff --git a/spec/import_export/category_structure_exporter_spec.rb b/spec/import_export/category_structure_exporter_spec.rb index b5ee22fbde..776ee20cd3 100644 --- a/spec/import_export/category_structure_exporter_spec.rb +++ b/spec/import_export/category_structure_exporter_spec.rb @@ -3,12 +3,9 @@ require "import_export/category_structure_exporter" RSpec.describe ImportExport::CategoryStructureExporter do + before { STDOUT.stubs(:write) } - before do - STDOUT.stubs(:write) - end - - it 'export all the categories' do + it "export all the categories" do category = Fabricate(:category) data = ImportExport::CategoryStructureExporter.new.perform.export_data @@ -17,7 +14,7 @@ RSpec.describe ImportExport::CategoryStructureExporter do expect(data[:users].blank?).to eq(true) end - it 'export all the categories with permission groups' do + it "export all the categories with permission groups" do category = Fabricate(:category) group = Fabricate(:group) category_group = Fabricate(:category_group, category: category, group: group) @@ -28,7 +25,7 @@ RSpec.describe ImportExport::CategoryStructureExporter do expect(data[:users].blank?).to eq(true) end - it 'export all the categories with permission groups and users' do + it "export all the categories with permission groups and users" do category = Fabricate(:category) group = Fabricate(:group) user = Fabricate(:user) @@ -40,5 +37,4 @@ RSpec.describe ImportExport::CategoryStructureExporter do expect(data[:groups].count).to eq(1) expect(data[:users].count).to eq(1) end - end diff --git a/spec/import_export/group_exporter_spec.rb b/spec/import_export/group_exporter_spec.rb index 8fd5c0f908..d6e188b29a 100644 --- a/spec/import_export/group_exporter_spec.rb +++ b/spec/import_export/group_exporter_spec.rb @@ -3,12 +3,9 @@ require "import_export/group_exporter" RSpec.describe ImportExport::GroupExporter do + before { STDOUT.stubs(:write) } - before do - STDOUT.stubs(:write) - end - - it 'exports all the groups' do + it "exports all the groups" do group = Fabricate(:group) user = Fabricate(:user) group_user = Fabricate(:group_user, group: group, user: user) @@ -18,7 +15,7 @@ RSpec.describe ImportExport::GroupExporter do expect(data[:users].blank?).to eq(true) end - it 'exports all the groups with users' do + it "exports all the groups with users" do group = Fabricate(:group) user = Fabricate(:user) group_user = Fabricate(:group_user, group: group, user: user) @@ -27,5 +24,4 @@ RSpec.describe ImportExport::GroupExporter do expect(data[:groups].map { |g| g[:id] }).to include(group.id) expect(data[:users].map { |u| u[:id] }).to include(user.id) end - end diff --git a/spec/import_export/importer_spec.rb b/spec/import_export/importer_spec.rb index 39009ddabf..00cc89203e 100644 --- a/spec/import_export/importer_spec.rb +++ b/spec/import_export/importer_spec.rb @@ -3,9 +3,7 @@ require "import_export" RSpec.describe ImportExport::Importer do - before do - STDOUT.stubs(:write) - end + before { STDOUT.stubs(:write) } let(:import_data) do import_file = Rack::Test::UploadedFile.new(file_from_fixtures("import-export.json", "json")) @@ -16,93 +14,79 @@ RSpec.describe ImportExport::Importer do ImportExport::Importer.new(data).perform end - describe '.perform' do - it 'topics and users' do + describe ".perform" do + it "topics and users" do data = import_data.dup data[:categories] = nil data[:groups] = nil - expect { - import(data) - }.to not_change { Category.count } - .and not_change { Group.count } - .and change { Topic.count }.by(2) - .and change { User.count }.by(2) + expect { import(data) }.to not_change { Category.count }.and not_change { + Group.count + }.and change { Topic.count }.by(2).and change { User.count }.by(2) end - context 'with categories and groups' do - it 'works' do + context "with categories and groups" do + it "works" do data = import_data.dup data[:topics] = nil data[:users] = nil - expect { - import(data) - }.to change { Category.count }.by(6) - .and change { Group.count }.by(2) - .and change { Topic.count }.by(6) - .and not_change { User.count } + expect { import(data) }.to change { Category.count }.by(6).and change { Group.count }.by( + 2, + ).and change { Topic.count }.by(6).and not_change { User.count } end - it 'works with sub-sub-categories' do + it "works with sub-sub-categories" do data = import_data.dup # 11 -> 10 -> 15 data[:categories].find { |c| c[:id] == 10 }[:parent_category_id] = 11 data[:categories].find { |c| c[:id] == 15 }[:parent_category_id] = 10 - expect { import(data) } - .to change { Category.count }.by(6) - .and change { SiteSetting.max_category_nesting }.from(2).to(3) + expect { import(data) }.to change { Category.count }.by(6).and change { + SiteSetting.max_category_nesting + }.from(2).to(3) end - it 'fixes permissions' do + it "fixes permissions" do data = import_data.dup data[:categories].find { |c| c[:id] == 10 }[:permissions_params] = { custom_group: 1 } data[:categories].find { |c| c[:id] == 15 }[:permissions_params] = { staff: 1 } permissions = data[:categories].find { |c| c[:id] == 10 }[:permissions_params] - expect { import(data) } - .to change { Category.count }.by(6) - .and change { permissions[:staff] }.from(nil).to(1) + expect { import(data) }.to change { Category.count }.by(6).and change { + permissions[:staff] + }.from(nil).to(1) end end - it 'categories, groups and users' do + it "categories, groups and users" do data = import_data.dup data[:topics] = nil - expect { - import(data) - }.to change { Category.count }.by(6) - .and change { Group.count }.by(2) - .and change { Topic.count }.by(6) - .and change { User.count }.by(2) + expect { import(data) }.to change { Category.count }.by(6).and change { Group.count }.by( + 2, + ).and change { Topic.count }.by(6).and change { User.count }.by(2) end - it 'groups' do + it "groups" do data = import_data.dup data[:categories] = nil data[:topics] = nil data[:users] = nil - expect { - import(data) - }.to not_change { Category.count } - .and change { Group.count }.by(2) - .and not_change { Topic.count } - .and not_change { User.count } + expect { import(data) }.to not_change { Category.count }.and change { Group.count }.by( + 2, + ).and not_change { Topic.count }.and not_change { User.count } end - it 'all' do - expect { - import(import_data) - }.to change { Category.count }.by(6) - .and change { Group.count }.by(2) - .and change { Topic.count }.by(8) - .and change { User.count }.by(2) - .and change { TranslationOverride.count }.by(1) + it "all" do + expect { import(import_data) }.to change { Category.count }.by(6).and change { + Group.count + }.by(2).and change { Topic.count }.by(8).and change { User.count }.by(2).and change { + TranslationOverride.count + }.by(1) end end end diff --git a/spec/import_export/topic_exporter_spec.rb b/spec/import_export/topic_exporter_spec.rb index 809189158c..a36318a5d3 100644 --- a/spec/import_export/topic_exporter_spec.rb +++ b/spec/import_export/topic_exporter_spec.rb @@ -3,17 +3,14 @@ require "import_export" RSpec.describe ImportExport::TopicExporter do - - before do - STDOUT.stubs(:write) - end + before { STDOUT.stubs(:write) } fab!(:user) { Fabricate(:user) } fab!(:topic) { Fabricate(:topic, user: user) } fab!(:post) { Fabricate(:post, topic: topic, user: user) } - describe '.perform' do - it 'export a single topic' do + describe ".perform" do + it "export a single topic" do data = ImportExport::TopicExporter.new([topic.id]).perform.export_data expect(data[:categories].blank?).to eq(true) @@ -22,7 +19,7 @@ RSpec.describe ImportExport::TopicExporter do expect(data[:users].count).to eq(1) end - it 'export multiple topics' do + it "export multiple topics" do topic2 = Fabricate(:topic, user: user) _post2 = Fabricate(:post, user: user, topic: topic2) data = ImportExport::TopicExporter.new([topic.id, topic2.id]).perform.export_data @@ -33,5 +30,4 @@ RSpec.describe ImportExport::TopicExporter do expect(data[:users].map { |u| u[:id] }).to match_array([user.id]) end end - end diff --git a/spec/initializers/track_setting_changes_spec.rb b/spec/initializers/track_setting_changes_spec.rb index bab78a1d33..47c538b4f5 100644 --- a/spec/initializers/track_setting_changes_spec.rb +++ b/spec/initializers/track_setting_changes_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -RSpec.describe 'Setting changes' do - describe '#must_approve_users' do +RSpec.describe "Setting changes" do + describe "#must_approve_users" do before { SiteSetting.must_approve_users = false } - it 'does not approve a user with associated reviewables' do + it "does not approve a user with associated reviewables" do user_pending_approval = Fabricate(:reviewable_user).target SiteSetting.must_approve_users = true @@ -12,7 +12,7 @@ RSpec.describe 'Setting changes' do expect(user_pending_approval.reload.approved?).to eq(false) end - it 'approves a user with no associated reviewables' do + it "approves a user with no associated reviewables" do non_approved_user = Fabricate(:user, approved: false) SiteSetting.must_approve_users = true @@ -21,10 +21,10 @@ RSpec.describe 'Setting changes' do end end - describe '#reviewable_low_priority_threshold' do + describe "#reviewable_low_priority_threshold" do let(:new_threshold) { 5 } - it 'sets the low priority value' do + it "sets the low priority value" do medium_threshold = 10 Reviewable.set_priorities(medium: medium_threshold) diff --git a/spec/integration/api_keys_spec.rb b/spec/integration/api_keys_spec.rb index be9e790a68..c7ec616e20 100644 --- a/spec/integration/api_keys_spec.rb +++ b/spec/integration/api_keys_spec.rb @@ -1,25 +1,21 @@ # frozen_string_literal: true -RSpec.describe 'api keys' do +RSpec.describe "api keys" do let(:user) { Fabricate(:user) } let(:api_key) { ApiKey.create!(user_id: user.id, created_by_id: Discourse.system_user) } - it 'works in headers' do - get '/session/current.json', headers: { - HTTP_API_KEY: api_key.key - } + it "works in headers" do + get "/session/current.json", headers: { HTTP_API_KEY: api_key.key } expect(response.status).to eq(200) expect(response.parsed_body["current_user"]["username"]).to eq(user.username) end - it 'does not work in parameters' do - get '/session/current.json', params: { - api_key: api_key.key - } + it "does not work in parameters" do + get "/session/current.json", params: { api_key: api_key.key } expect(response.status).to eq(404) end - it 'allows parameters on ics routes' do + it "allows parameters on ics routes" do get "/u/#{user.username}/bookmarks.ics?api_key=#{api_key.key}&api_username=#{user.username.downcase}" expect(response.status).to eq(200) @@ -28,14 +24,14 @@ RSpec.describe 'api keys' do expect(response.status).to eq(403) end - it 'allows parameters for handle mail' do + it "allows parameters for handle mail" do admin_api_key = ApiKey.create!(user: Fabricate(:admin), created_by_id: Discourse.system_user) post "/admin/email/handle_mail.json?api_key=#{admin_api_key.key}", params: { email: "blah" } expect(response.status).to eq(200) end - it 'allows parameters for rss feeds' do + it "allows parameters for rss feeds" do SiteSetting.login_required = true get "/latest.rss?api_key=#{api_key.key}&api_username=#{user.username.downcase}" @@ -52,32 +48,28 @@ RSpec.describe 'api keys' do plugin.add_api_parameter_route methods: [:get], actions: ["session#current"] end - it 'allows parameter access to the registered route' do - get '/session/current.json', params: { - api_key: api_key.key - } + it "allows parameter access to the registered route" do + get "/session/current.json", params: { api_key: api_key.key } expect(response.status).to eq(200) end end end -RSpec.describe 'user api keys' do +RSpec.describe "user api keys" do let(:user) { Fabricate(:user) } let(:user_api_key) { Fabricate(:readonly_user_api_key, user: user) } - it 'updates last used time on use' do + it "updates last used time on use" do freeze_time user_api_key.update_columns(last_used_at: 7.days.ago) - get '/session/current.json', headers: { - HTTP_USER_API_KEY: user_api_key.key, - } + get "/session/current.json", headers: { HTTP_USER_API_KEY: user_api_key.key } expect(user_api_key.reload.last_used_at).to eq_time(Time.zone.now) end - it 'allows parameters on ics routes' do + it "allows parameters on ics routes" do get "/u/#{user.username}/bookmarks.ics?user_api_key=#{user_api_key.key}" expect(response.status).to eq(200) @@ -86,7 +78,7 @@ RSpec.describe 'user api keys' do expect(response.status).to eq(403) end - it 'allows parameters for rss feeds' do + it "allows parameters for rss feeds" do SiteSetting.login_required = true get "/latest.rss?user_api_key=#{user_api_key.key}" @@ -102,27 +94,19 @@ RSpec.describe 'user api keys' do calendar_key = Fabricate(:bookmarks_calendar_user_api_key, user: admin) - get "/u/#{user.username}/bookmarks.json", headers: { - HTTP_USER_API_KEY: calendar_key.key, - } + get "/u/#{user.username}/bookmarks.json", headers: { HTTP_USER_API_KEY: calendar_key.key } expect(response.status).to eq(403) # Does not allow json - get "/u/#{user.username}/bookmarks.ics", headers: { - HTTP_USER_API_KEY: calendar_key.key, - } + get "/u/#{user.username}/bookmarks.ics", headers: { HTTP_USER_API_KEY: calendar_key.key } expect(response.status).to eq(200) # Allows ICS # Now restrict the key calendar_key.scopes.first.update(allowed_parameters: { username: admin.username }) - get "/u/#{user.username}/bookmarks.ics", headers: { - HTTP_USER_API_KEY: calendar_key.key, - } + get "/u/#{user.username}/bookmarks.ics", headers: { HTTP_USER_API_KEY: calendar_key.key } expect(response.status).to eq(403) # Cannot access another users calendar - get "/u/#{admin.username}/bookmarks.ics", headers: { - HTTP_USER_API_KEY: calendar_key.key, - } + get "/u/#{admin.username}/bookmarks.ics", headers: { HTTP_USER_API_KEY: calendar_key.key } expect(response.status).to eq(200) # Can access own calendar end @@ -138,12 +122,9 @@ RSpec.describe 'user api keys' do user_api_key.save! end - it 'allows parameter access to the registered route' do - get '/session/current.json', headers: { - HTTP_USER_API_KEY: user_api_key.key - } + it "allows parameter access to the registered route" do + get "/session/current.json", headers: { HTTP_USER_API_KEY: user_api_key.key } expect(response.status).to eq(200) end end - end diff --git a/spec/integration/auto_reject_reviewable_users_spec.rb b/spec/integration/auto_reject_reviewable_users_spec.rb index f993f097c2..dd09810733 100644 --- a/spec/integration/auto_reject_reviewable_users_spec.rb +++ b/spec/integration/auto_reject_reviewable_users_spec.rb @@ -12,9 +12,7 @@ RSpec.describe "auto reject reviewable users" do Jobs::AutoQueueHandler.new.execute({}) expect(old_user.reload.rejected?).to eq(true) - expect(UserHistory.last.context).to eq( - I18n.t("user.destroy_reasons.reviewable_reject_auto") - ) + expect(UserHistory.last.context).to eq(I18n.t("user.destroy_reasons.reviewable_reject_auto")) end end end diff --git a/spec/integration/blocked_hotlinked_media_spec.rb b/spec/integration/blocked_hotlinked_media_spec.rb index c727210c48..c9dfdc2a6b 100644 --- a/spec/integration/blocked_hotlinked_media_spec.rb +++ b/spec/integration/blocked_hotlinked_media_spec.rb @@ -3,11 +3,20 @@ RSpec.describe "hotlinked media blocking" do let(:hotlinked_url) { "http://example.com/images/2/2e/Longcat1.png" } let(:onebox_url) { "http://example.com/onebox" } - let(:png) { Base64.decode64("R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==") } + let(:png) do + Base64.decode64( + "R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==", + ) + end before do SiteSetting.download_remote_images_to_local = false - stub_request(:get, hotlinked_url).to_return(body: png, headers: { "Content-Type" => "image/png" }) + stub_request(:get, hotlinked_url).to_return( + body: png, + headers: { + "Content-Type" => "image/png", + }, + ) stub_image_size end @@ -19,38 +28,65 @@ RSpec.describe "hotlinked media blocking" do context "with hotlinked media blocked, before post-processing" do before do SiteSetting.block_hotlinked_media = true - Oneboxer.stubs(:cached_onebox).returns("") + Oneboxer.stubs(:cached_onebox).returns( + "", + ) end it "blocks hotlinked images" do post = Fabricate(:post, raw: "") expect(post.cooked).not_to have_tag("img[src]") - expect(post.cooked).to have_tag("img", with: { PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => hotlinked_url }) + expect(post.cooked).to have_tag( + "img", + with: { + PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => hotlinked_url, + }, + ) end it "blocks hotlinked videos with src" do post = Fabricate(:post, raw: "![alt text|video](#{hotlinked_url})") expect(post.cooked).not_to have_tag("video source[src]") - expect(post.cooked).to have_tag("video source", with: { PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => hotlinked_url }) + expect(post.cooked).to have_tag( + "video source", + with: { + PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => hotlinked_url, + }, + ) end it "blocks hotlinked videos with srcset" do srcset = "#{hotlinked_url} 1x,https://example.com 2x" post = Fabricate(:post, raw: "") expect(post.cooked).not_to have_tag("video source[srcset]") - expect(post.cooked).to have_tag("video source", with: { PrettyText::BLOCKED_HOTLINKED_SRCSET_ATTR => srcset }) + expect(post.cooked).to have_tag( + "video source", + with: { + PrettyText::BLOCKED_HOTLINKED_SRCSET_ATTR => srcset, + }, + ) end it "blocks hotlinked audio" do post = Fabricate(:post, raw: "![alt text|audio](#{hotlinked_url})") expect(post.cooked).not_to have_tag("audio source[src]") - expect(post.cooked).to have_tag("audio source", with: { PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => hotlinked_url }) + expect(post.cooked).to have_tag( + "audio source", + with: { + PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => hotlinked_url, + }, + ) end it "blocks hotlinked onebox content when cached (post_analyzer)" do post = Fabricate(:post, raw: "#{onebox_url}") expect(post.cooked).not_to have_tag("img[src]") - expect(post.cooked).to have_tag("img", with: { PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => hotlinked_url }) + expect(post.cooked).to have_tag( + "img", + with: { + PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => hotlinked_url, + }, + ) end it "allows relative URLs" do @@ -81,8 +117,18 @@ RSpec.describe "hotlinked media blocking" do post.reload expect(post.cooked).to have_tag("img", with: { "src" => "https://example.com" }) expect(post.cooked).to have_tag("img", with: { "src" => "https://example.com/myimage.png" }) - expect(post.cooked).to have_tag("img", with: { PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => "https://example.com.malicious.com/myimage.png" }) - expect(post.cooked).to have_tag("img", with: { PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => "https://malicious.invalid/https://example.com" }) + expect(post.cooked).to have_tag( + "img", + with: { + PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => "https://example.com.malicious.com/myimage.png", + }, + ) + expect(post.cooked).to have_tag( + "img", + with: { + PrettyText::BLOCKED_HOTLINKED_SRC_ATTR => "https://malicious.invalid/https://example.com", + }, + ) end it "allows multiple exceptions" do @@ -126,7 +172,7 @@ RSpec.describe "hotlinked media blocking" do expect(post.cooked).not_to have_tag("audio") expect(post.cooked).to have_tag( "a.blocked-hotlinked-placeholder[href^='http://example.com'][rel='noopener nofollow ugc']", - count: 4 + count: 4, ) end end diff --git a/spec/integration/category_tag_spec.rb b/spec/integration/category_tag_spec.rb index 59bee8e897..7a67d1928b 100644 --- a/spec/integration/category_tag_spec.rb +++ b/spec/integration/category_tag_spec.rb @@ -6,13 +6,13 @@ RSpec.describe "category tag restrictions" do DiscourseTagging.filter_allowed_tags(Guardian.new(user), opts) end - fab!(:tag1) { Fabricate(:tag, name: 'tag1') } - fab!(:tag2) { Fabricate(:tag, name: 'tag2') } - fab!(:tag3) { Fabricate(:tag, name: 'tag3') } - fab!(:tag4) { Fabricate(:tag, name: 'tag4') } - let(:tag_with_colon) { Fabricate(:tag, name: 'with:colon') } + fab!(:tag1) { Fabricate(:tag, name: "tag1") } + fab!(:tag2) { Fabricate(:tag, name: "tag2") } + fab!(:tag3) { Fabricate(:tag, name: "tag3") } + fab!(:tag4) { Fabricate(:tag, name: "tag4") } + let(:tag_with_colon) { Fabricate(:tag, name: "with:colon") } - fab!(:user) { Fabricate(:user) } + fab!(:user) { Fabricate(:user) } fab!(:admin) { Fabricate(:admin) } before do @@ -23,29 +23,29 @@ RSpec.describe "category tag restrictions" do context "with tags restricted to one category" do fab!(:category_with_tags) { Fabricate(:category) } - fab!(:other_category) { Fabricate(:category) } + fab!(:other_category) { Fabricate(:category) } - before do - category_with_tags.tags = [tag1, tag2] - end + before { category_with_tags.tags = [tag1, tag2] } it "tags belonging to that category can only be used there" do - msg = I18n.t( - "tags.forbidden.category_does_not_allow_tags", - count: 1, - tags: tag3.name, - category: category_with_tags.name - ) + msg = + I18n.t( + "tags.forbidden.category_does_not_allow_tags", + count: 1, + tags: tag3.name, + category: category_with_tags.name, + ) expect { create_post(category: category_with_tags, tags: [tag1.name, tag2.name, tag3.name]) }.to raise_error(StandardError, msg) - msg = I18n.t( - "tags.forbidden.restricted_tags_cannot_be_used_in_category", - count: 2, - tags: [tag1, tag2].map(&:name).sort.join(", "), - category: other_category.name - ) + msg = + I18n.t( + "tags.forbidden.restricted_tags_cannot_be_used_in_category", + count: 2, + tags: [tag1, tag2].map(&:name).sort.join(", "), + category: other_category.name, + ) expect { create_post(category: other_category, tags: [tag1.name, tag2.name, tag3.name]) }.to raise_error(StandardError, msg) @@ -53,27 +53,60 @@ RSpec.describe "category tag restrictions" do it "search can show only permitted tags" do expect(filter_allowed_tags.count).to eq(Tag.count) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags), [tag1, tag2]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category_with_tags), + [tag1, tag2], + ) expect_same_tag_names(filter_allowed_tags(for_input: true), [tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag1.name]), [tag2]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag1.name], term: 'tag'), [tag2]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name]), [tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name], term: 'tag'), [tag4]) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: category_with_tags, + selected_tags: [tag1.name], + ), + [tag2], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: category_with_tags, + selected_tags: [tag1.name], + term: "tag", + ), + [tag2], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category), + [tag3, tag4], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name]), + [tag4], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: other_category, + selected_tags: [tag3.name], + term: "tag", + ), + [tag4], + ) end it "search can handle colons in tag names" do tag_with_colon - expect_same_tag_names(filter_allowed_tags(for_input: true, term: 'with:c'), [tag_with_colon]) + expect_same_tag_names(filter_allowed_tags(for_input: true, term: "with:c"), [tag_with_colon]) end it "can't create new tags in a restricted category" do - msg = I18n.t( - "tags.forbidden.category_does_not_allow_tags", - count: 1, - tags: "newtag", - category: category_with_tags.name - ) + msg = + I18n.t( + "tags.forbidden.category_does_not_allow_tags", + count: 1, + tags: "newtag", + category: category_with_tags.name, + ) expect { create_post(category: category_with_tags, tags: [tag1.name, "newtag"]) }.to raise_error(StandardError, msg) @@ -89,61 +122,161 @@ RSpec.describe "category tag restrictions" do end it "can create tags when changing category settings" do - expect { other_category.update(allowed_tags: ['newtag']) }.to change { Tag.count }.by(1) - expect { other_category.update(allowed_tags: [tag1.name, 'tag-stuff', tag2.name, 'another-tag']) }.to change { Tag.count }.by(2) + expect { other_category.update(allowed_tags: ["newtag"]) }.to change { Tag.count }.by(1) + expect { + other_category.update(allowed_tags: [tag1.name, "tag-stuff", tag2.name, "another-tag"]) + }.to change { Tag.count }.by(2) end - context 'with required tags from tag group' do + context "with required tags from tag group" do fab!(:tag_group) { Fabricate(:tag_group, tags: [tag1, tag3]) } - before { category_with_tags.update!(category_required_tag_groups: [CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1)]) } + before do + category_with_tags.update!( + category_required_tag_groups: [ + CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1), + ], + ) + end it "search only returns the allowed tags" do - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags), [tag1]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag1.name]), [tag2]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag2.name]), [tag1]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category_with_tags), + [tag1], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: category_with_tags, + selected_tags: [tag1.name], + ), + [tag2], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: category_with_tags, + selected_tags: [tag2.name], + ), + [tag1], + ) end end - context 'when category allows other tags to be used' do - before do - category_with_tags.update!(allow_global_tags: true) - end + context "when category allows other tags to be used" do + before { category_with_tags.update!(allow_global_tags: true) } it "search can show the permitted tags" do expect(filter_allowed_tags.count).to eq(Tag.count) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags), [tag1, tag2, tag3, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category_with_tags), + [tag1, tag2, tag3, tag4], + ) expect_same_tag_names(filter_allowed_tags(for_input: true), [tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag1.name]), [tag2, tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag1.name], term: 'tag'), [tag2, tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name]), [tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name], term: 'tag'), [tag4]) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: category_with_tags, + selected_tags: [tag1.name], + ), + [tag2, tag3, tag4], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: category_with_tags, + selected_tags: [tag1.name], + term: "tag", + ), + [tag2, tag3, tag4], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category), + [tag3, tag4], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: other_category, + selected_tags: [tag3.name], + ), + [tag4], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: other_category, + selected_tags: [tag3.name], + term: "tag", + ), + [tag4], + ) end it "works if no tags are restricted to the category" do other_category.update!(allow_global_tags: true) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name]), [tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category, selected_tags: [tag3.name], term: 'tag'), [tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category), + [tag3, tag4], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: other_category, + selected_tags: [tag3.name], + ), + [tag4], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: other_category, + selected_tags: [tag3.name], + term: "tag", + ), + [tag4], + ) end - context 'with required tags from tag group' do + context "with required tags from tag group" do fab!(:tag_group) { Fabricate(:tag_group, tags: [tag1, tag3]) } - before { category_with_tags.update!(category_required_tag_groups: [CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1)]) } + before do + category_with_tags.update!( + category_required_tag_groups: [ + CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1), + ], + ) + end it "search only returns the allowed tags" do - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags), [tag1, tag3]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag1.name]), [tag2, tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category_with_tags, selected_tags: [tag2.name]), [tag1, tag3]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category_with_tags), + [tag1, tag3], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: category_with_tags, + selected_tags: [tag1.name], + ), + [tag2, tag3, tag4], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: category_with_tags, + selected_tags: [tag2.name], + ), + [tag1, tag3], + ) end end end end context "with tag groups restricted to a category" do - fab!(:tag_group1) { Fabricate(:tag_group) } - fab!(:category) { Fabricate(:category) } - fab!(:other_category) { Fabricate(:category) } + fab!(:tag_group1) { Fabricate(:tag_group) } + fab!(:category) { Fabricate(:category) } + fab!(:other_category) { Fabricate(:category) } before do tag_group1.tags = [tag1, tag2] @@ -156,7 +289,10 @@ RSpec.describe "category tag restrictions" do expect_same_tag_names(filter_allowed_tags(for_input: true), [tag3, tag4]) tag_group1.tags = [tag2, tag3, tag4] - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category), [tag2, tag3, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category), + [tag2, tag3, tag4], + ) expect_same_tag_names(filter_allowed_tags(for_input: true), [tag1]) expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag1]) end @@ -165,74 +301,122 @@ RSpec.describe "category tag restrictions" do category.allowed_tags = [tag4.name] category.reload - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category), [tag1, tag2, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category), + [tag1, tag2, tag4], + ) expect_same_tag_names(filter_allowed_tags(for_input: true), [tag3]) expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag3]) end it "enforces restrictions when creating a topic" do - msg = I18n.t( - "tags.forbidden.category_does_not_allow_tags", - count: 1, - tags: "newtag", - category: category.name + msg = + I18n.t( + "tags.forbidden.category_does_not_allow_tags", + count: 1, + tags: "newtag", + category: category.name, + ) + expect { create_post(category: category, tags: [tag1.name, "newtag"]) }.to raise_error( + StandardError, + msg, ) - expect { - create_post(category: category, tags: [tag1.name, "newtag"]) - }.to raise_error(StandardError, msg) end it "handles colons" do tag_with_colon - expect_same_tag_names(filter_allowed_tags(for_input: true, term: 'with:c'), [tag_with_colon]) + expect_same_tag_names(filter_allowed_tags(for_input: true, term: "with:c"), [tag_with_colon]) end - context 'with required tags from tag group' do + context "with required tags from tag group" do fab!(:tag_group) { Fabricate(:tag_group, tags: [tag1, tag3]) } - before { category.update!(category_required_tag_groups: [CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1)]) } + before do + category.update!( + category_required_tag_groups: [ + CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1), + ], + ) + end it "search only returns the allowed tags" do expect_same_tag_names(filter_allowed_tags(for_input: true, category: category), [tag1]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category, selected_tags: [tag1.name]), [tag2]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category, selected_tags: [tag2.name]), [tag1]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category, selected_tags: [tag1.name]), + [tag2], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category, selected_tags: [tag2.name]), + [tag1], + ) end end - context 'when category allows other tags to be used' do - before do - category.update!(allow_global_tags: true) - end + context "when category allows other tags to be used" do + before { category.update!(allow_global_tags: true) } - it 'filters tags correctly' do - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category), [tag1, tag2, tag3, tag4]) + it "filters tags correctly" do + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category), + [tag1, tag2, tag3, tag4], + ) expect_same_tag_names(filter_allowed_tags(for_input: true), [tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag3, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category), + [tag3, tag4], + ) tag_group1.tags = [tag2, tag3, tag4] - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category), [tag1, tag2, tag3, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category), + [tag1, tag2, tag3, tag4], + ) expect_same_tag_names(filter_allowed_tags(for_input: true), [tag1]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag1]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category), + [tag1], + ) end it "works if no tags are restricted to the category" do other_category.update!(allow_global_tags: true) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag3, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category), + [tag3, tag4], + ) tag_group1.tags = [tag2, tag3, tag4] - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag1]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category), + [tag1], + ) end - context 'with required tags from tag group' do + context "with required tags from tag group" do fab!(:tag_group) { Fabricate(:tag_group, tags: [tag1, tag3]) } - before { category.update!(category_required_tag_groups: [CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1)]) } + before do + category.update!( + category_required_tag_groups: [ + CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1), + ], + ) + end it "search only returns the allowed tags" do - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category), [tag1, tag3]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category, selected_tags: [tag1.name]), [tag2, tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category, selected_tags: [tag2.name]), [tag1, tag3]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category), + [tag1, tag3], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category, selected_tags: [tag1.name]), + [tag2, tag3, tag4], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category, selected_tags: [tag2.name]), + [tag1, tag3], + ) end end - context 'when another category has restricted tags using groups' do + context "when another category has restricted tags using groups" do fab!(:category2) { Fabricate(:category) } fab!(:tag_group2) { Fabricate(:tag_group) } @@ -242,32 +426,59 @@ RSpec.describe "category tag restrictions" do category2.reload end - it 'filters tags correctly' do - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category2), [tag2, tag3]) + it "filters tags correctly" do + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category2), + [tag2, tag3], + ) expect_same_tag_names(filter_allowed_tags(for_input: true), [tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category), [tag1, tag2, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category), + [tag4], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category), + [tag1, tag2, tag4], + ) end it "doesn't care about tags in a group that isn't used in a category" do unused_tag_group = Fabricate(:tag_group) unused_tag_group.tags = [tag4] - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category2), [tag2, tag3]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category2), + [tag2, tag3], + ) expect_same_tag_names(filter_allowed_tags(for_input: true), [tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category), [tag1, tag2, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category), + [tag4], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category), + [tag1, tag2, tag4], + ) end end - context 'when another category has restricted tags' do + context "when another category has restricted tags" do fab!(:category2) { Fabricate(:category) } it "doesn't filter tags that are also restricted in another category" do category2.tags = [tag2, tag3] - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category2), [tag2, tag3]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category2), + [tag2, tag3], + ) expect_same_tag_names(filter_allowed_tags(for_input: true), [tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category), [tag1, tag2, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category), + [tag4], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category), + [tag1, tag2, tag4], + ) end end end @@ -278,80 +489,139 @@ RSpec.describe "category tag restrictions" do tag_group = Fabricate(:tag_group, parent_tag_id: tag1.id) tag_group.tags = [tag3, tag4] expect_same_tag_names(filter_allowed_tags(for_input: true), [tag1, tag2]) - expect_same_tag_names(filter_allowed_tags(for_input: true, selected_tags: [tag1.name]), [tag2, tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, selected_tags: [tag1.name, tag3.name]), [tag2, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, selected_tags: [tag1.name]), + [tag2, tag3, tag4], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, selected_tags: [tag1.name, tag3.name]), + [tag2, tag4], + ) end it "for tagging a topic, filter_allowed_tags allows tags without parent tag" do tag_group = Fabricate(:tag_group, parent_tag_id: tag1.id) tag_group.tags = [tag3, tag4] expect_same_tag_names(filter_allowed_tags(for_topic: true), [tag1, tag2, tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_topic: true, selected_tags: [tag1.name]), [tag1, tag2, tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_topic: true, selected_tags: [tag1.name, tag3.name]), [tag1, tag2, tag3, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_topic: true, selected_tags: [tag1.name]), + [tag1, tag2, tag3, tag4], + ) + expect_same_tag_names( + filter_allowed_tags(for_topic: true, selected_tags: [tag1.name, tag3.name]), + [tag1, tag2, tag3, tag4], + ) end it "filter_allowed_tags returns tags common to more than one tag group with parent tag" do - common = Fabricate(:tag, name: 'common') + common = Fabricate(:tag, name: "common") tag_group = Fabricate(:tag_group, parent_tag_id: tag1.id) tag_group.tags = [tag2, common] tag_group = Fabricate(:tag_group, parent_tag_id: tag3.id) tag_group.tags = [tag4] expect_same_tag_names(filter_allowed_tags(for_input: true), [tag1, tag3]) - expect_same_tag_names(filter_allowed_tags(for_input: true, selected_tags: [tag1.name]), [tag2, tag3, common]) - expect_same_tag_names(filter_allowed_tags(for_input: true, selected_tags: [tag3.name]), [tag4, tag1]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, selected_tags: [tag1.name]), + [tag2, tag3, common], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, selected_tags: [tag3.name]), + [tag4, tag1], + ) tag_group.tags = [tag4, common] expect_same_tag_names(filter_allowed_tags(for_input: true), [tag1, tag3]) - expect_same_tag_names(filter_allowed_tags(for_input: true, selected_tags: [tag1.name]), [tag2, tag3, common]) - expect_same_tag_names(filter_allowed_tags(for_input: true, selected_tags: [tag3.name]), [tag4, tag1, common]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, selected_tags: [tag1.name]), + [tag2, tag3, common], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, selected_tags: [tag3.name]), + [tag4, tag1, common], + ) parent_tag_group = Fabricate(:tag_group, tags: [tag1, tag3]) expect_same_tag_names(filter_allowed_tags(for_input: true), [tag1, tag3]) - expect_same_tag_names(filter_allowed_tags(for_input: true, selected_tags: [tag1.name]), [tag2, tag3, common]) - expect_same_tag_names(filter_allowed_tags(for_input: true, selected_tags: [tag3.name]), [tag4, tag1, common]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, selected_tags: [tag1.name]), + [tag2, tag3, common], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, selected_tags: [tag3.name]), + [tag4, tag1, common], + ) parent_tag_group.update!(one_per_topic: true) expect_same_tag_names(filter_allowed_tags(for_input: true), [tag1, tag3]) - expect_same_tag_names(filter_allowed_tags(for_input: true, selected_tags: [tag1.name]), [tag2, common]) - expect_same_tag_names(filter_allowed_tags(for_input: true, selected_tags: [tag3.name]), [tag4, common]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, selected_tags: [tag1.name]), + [tag2, common], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, selected_tags: [tag3.name]), + [tag4, common], + ) end - context 'with required tags from tag group' do + context "with required tags from tag group" do fab!(:tag_group) { Fabricate(:tag_group, tags: [tag1, tag2]) } - fab!(:category) { Fabricate(:category, category_required_tag_groups: [CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1)]) } + fab!(:category) do + Fabricate( + :category, + category_required_tag_groups: [ + CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1), + ], + ) + end it "search only returns the allowed tags" do tag_group_with_parent = Fabricate(:tag_group, parent_tag_id: tag1.id, tags: [tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category), [tag1, tag2]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category, selected_tags: [tag2.name]), [tag1]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category, selected_tags: [tag1.name]), [tag2, tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: category, selected_tags: [tag1.name, tag2.name]), [tag3, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category), + [tag1, tag2], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category, selected_tags: [tag2.name]), + [tag1], + ) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: category, selected_tags: [tag1.name]), + [tag2, tag3, tag4], + ) + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: category, + selected_tags: [tag1.name, tag2.name], + ), + [tag3, tag4], + ) end end context "with category restrictions" do - fab!(:car_category) { Fabricate(:category) } - fab!(:other_category) { Fabricate(:category) } - fab!(:makes) { Fabricate(:tag_group, name: "Makes") } - fab!(:honda_group) { Fabricate(:tag_group, name: "Honda Models") } - fab!(:ford_group) { Fabricate(:tag_group, name: "Ford Models") } + fab!(:car_category) { Fabricate(:category) } + fab!(:other_category) { Fabricate(:category) } + fab!(:makes) { Fabricate(:tag_group, name: "Makes") } + fab!(:honda_group) { Fabricate(:tag_group, name: "Honda Models") } + fab!(:ford_group) { Fabricate(:tag_group, name: "Ford Models") } before do @tags = {} - ['honda', 'ford', 'civic', 'accord', 'mustang', 'taurus'].each do |name| + %w[honda ford civic accord mustang taurus].each do |name| @tags[name] = Fabricate(:tag, name: name) end - makes.tags = [@tags['honda'], @tags['ford']] + makes.tags = [@tags["honda"], @tags["ford"]] - honda_group.parent_tag_id = @tags['honda'].id + honda_group.parent_tag_id = @tags["honda"].id honda_group.save - honda_group.tags = [@tags['civic'], @tags['accord']] + honda_group.tags = [@tags["civic"], @tags["accord"]] - ford_group.parent_tag_id = @tags['ford'].id + ford_group.parent_tag_id = @tags["ford"].id ford_group.save - ford_group.tags = [@tags['mustang'], @tags['taurus']] + ford_group.tags = [@tags["mustang"], @tags["taurus"]] car_category.allowed_tag_groups = [makes.name, honda_group.name, ford_group.name] end @@ -359,39 +629,75 @@ RSpec.describe "category tag restrictions" do it "handles all those rules" do # car tags can't be used outside of car category: expect_same_tag_names(filter_allowed_tags(for_input: true), [tag1, tag2, tag3, tag4]) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: other_category), [tag1, tag2, tag3, tag4]) + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: other_category), + [tag1, tag2, tag3, tag4], + ) # in car category, a make tag must be given first: - expect(sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category))).to eq(['ford', 'honda']) + expect( + sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category)), + ).to eq(%w[ford honda]) # model tags depend on which make is chosen: - expect(sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['honda']))).to eq(['accord', 'civic', 'ford']) - expect(sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['ford']))).to eq(['honda', 'mustang', 'taurus']) + expect( + sorted_tag_names( + filter_allowed_tags(for_input: true, category: car_category, selected_tags: ["honda"]), + ), + ).to eq(%w[accord civic ford]) + expect( + sorted_tag_names( + filter_allowed_tags(for_input: true, category: car_category, selected_tags: ["ford"]), + ), + ).to eq(%w[honda mustang taurus]) makes.update!(one_per_topic: true) - expect(sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['honda']))).to eq(['accord', 'civic']) - expect(sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['ford']))).to eq(['mustang', 'taurus']) + expect( + sorted_tag_names( + filter_allowed_tags(for_input: true, category: car_category, selected_tags: ["honda"]), + ), + ).to eq(%w[accord civic]) + expect( + sorted_tag_names( + filter_allowed_tags(for_input: true, category: car_category, selected_tags: ["ford"]), + ), + ).to eq(%w[mustang taurus]) honda_group.update!(one_per_topic: true) ford_group.update!(one_per_topic: true) - expect(sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['honda']))).to eq(['accord', 'civic']) - expect(sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['ford']))).to eq(['mustang', 'taurus']) + expect( + sorted_tag_names( + filter_allowed_tags(for_input: true, category: car_category, selected_tags: ["honda"]), + ), + ).to eq(%w[accord civic]) + expect( + sorted_tag_names( + filter_allowed_tags(for_input: true, category: car_category, selected_tags: ["ford"]), + ), + ).to eq(%w[mustang taurus]) car_category.update!(allow_global_tags: true) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: car_category), - ['ford', 'honda', tag1, tag2, tag3, tag4] + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: car_category), + ["ford", "honda", tag1, tag2, tag3, tag4], ) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['ford']), - ['mustang', 'taurus', tag1, tag2, tag3, tag4] + expect_same_tag_names( + filter_allowed_tags(for_input: true, category: car_category, selected_tags: ["ford"]), + ["mustang", "taurus", tag1, tag2, tag3, tag4], ) - expect_same_tag_names(filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['ford', 'mustang']), - [tag1, tag2, tag3, tag4] + expect_same_tag_names( + filter_allowed_tags( + for_input: true, + category: car_category, + selected_tags: %w[ford mustang], + ), + [tag1, tag2, tag3, tag4], ) end it "can apply the tags to a topic" do - post = create_post(category: car_category, tags: ['ford', 'mustang']) - expect(post.topic.tags.map(&:name).sort).to eq(['ford', 'mustang']) + post = create_post(category: car_category, tags: %w[ford mustang]) + expect(post.topic.tags.map(&:name).sort).to eq(%w[ford mustang]) end context "with limit one tag from each group" do @@ -402,24 +708,51 @@ RSpec.describe "category tag restrictions" do end it "can restrict one tag from each group" do - expect(sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category))).to eq(['ford', 'honda']) - expect(sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['honda']))).to eq(['accord', 'civic']) - expect(sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['ford']))).to eq(['mustang', 'taurus']) - expect(sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['ford', 'mustang']))).to eq([]) + expect( + sorted_tag_names(filter_allowed_tags(for_input: true, category: car_category)), + ).to eq(%w[ford honda]) + expect( + sorted_tag_names( + filter_allowed_tags( + for_input: true, + category: car_category, + selected_tags: ["honda"], + ), + ), + ).to eq(%w[accord civic]) + expect( + sorted_tag_names( + filter_allowed_tags(for_input: true, category: car_category, selected_tags: ["ford"]), + ), + ).to eq(%w[mustang taurus]) + expect( + sorted_tag_names( + filter_allowed_tags( + for_input: true, + category: car_category, + selected_tags: %w[ford mustang], + ), + ), + ).to eq([]) end it "can apply the tags to a topic" do - post = create_post(category: car_category, tags: ['ford', 'mustang']) - expect(post.topic.tags.map(&:name).sort).to eq(['ford', 'mustang']) + post = create_post(category: car_category, tags: %w[ford mustang]) + expect(post.topic.tags.map(&:name).sort).to eq(%w[ford mustang]) end it "can remove extra tags from the same group" do # A weird case that input field wouldn't allow. # Only one tag from car makers is allowed, but we're saying that two have been selected. - names = filter_allowed_tags(for_input: true, category: car_category, selected_tags: ['honda', 'ford']).map(&:name) - expect(names.include?('honda') || names.include?('ford')).to eq(false) - expect(names).to include('civic') - expect(names).to include('mustang') + names = + filter_allowed_tags( + for_input: true, + category: car_category, + selected_tags: %w[honda ford], + ).map(&:name) + expect(names.include?("honda") || names.include?("ford")).to eq(false) + expect(names).to include("civic") + expect(names).to include("mustang") end end end @@ -441,9 +774,9 @@ RSpec.describe "tag topic counts per category" do end it "counts when a topic is created with tags" do - expect { - Fabricate(:topic, category: category, tags: [tag1, tag2]) - }.to change { CategoryTagStat.count }.by(2) + expect { Fabricate(:topic, category: category, tags: [tag1, tag2]) }.to change { + CategoryTagStat.count + }.by(2) expect(CategoryTagStat.where(category: category, tag: tag1).sum(:topic_count)).to eq(1) expect(CategoryTagStat.where(category: category, tag: tag2).sum(:topic_count)).to eq(1) end @@ -461,7 +794,7 @@ RSpec.describe "tag topic counts per category" do context "with topic with 2 tags" do fab!(:topic) { Fabricate(:topic, category: category, tags: [tag1, tag2]) } - fab!(:post) { Fabricate(:post, user: topic.user, topic: topic) } + fab!(:post) { Fabricate(:post, user: topic.user, topic: topic) } it "has correct counts after tag is removed from a topic" do post @@ -473,7 +806,12 @@ RSpec.describe "tag topic counts per category" do end it "has correct counts after a topic's category changes" do - PostRevisor.new(post).revise!(topic.user, category_id: category2.id, raw: post.raw, tags: [tag1.name, tag2.name]) + PostRevisor.new(post).revise!( + topic.user, + category_id: category2.id, + raw: post.raw, + tags: [tag1.name, tag2.name], + ) expect(CategoryTagStat.where(category: category, tag: tag1).sum(:topic_count)).to eq(0) expect(CategoryTagStat.where(category: category, tag: tag2).sum(:topic_count)).to eq(0) expect(CategoryTagStat.where(category: category2, tag: tag1).sum(:topic_count)).to eq(1) @@ -481,7 +819,12 @@ RSpec.describe "tag topic counts per category" do end it "has correct counts after topic's category AND tags changed" do - PostRevisor.new(post).revise!(topic.user, raw: post.raw, tags: [tag2.name, tag3.name], category_id: category2.id) + PostRevisor.new(post).revise!( + topic.user, + raw: post.raw, + tags: [tag2.name, tag3.name], + category_id: category2.id, + ) expect(CategoryTagStat.where(category: category, tag: tag1).sum(:topic_count)).to eq(0) expect(CategoryTagStat.where(category: category, tag: tag2).sum(:topic_count)).to eq(0) expect(CategoryTagStat.where(category: category, tag: tag3).sum(:topic_count)).to eq(0) @@ -496,8 +839,18 @@ RSpec.describe "tag topic counts per category" do fab!(:post) { Fabricate(:post, user: topic.user, topic: topic) } it "counts after topic becomes uncategorized" do - PostRevisor.new(post).revise!(topic.user, raw: post.raw, tags: [tag1.name], category_id: SiteSetting.uncategorized_category_id) - expect(CategoryTagStat.where(category: Category.find(SiteSetting.uncategorized_category_id), tag: tag1).sum(:topic_count)).to eq(1) + PostRevisor.new(post).revise!( + topic.user, + raw: post.raw, + tags: [tag1.name], + category_id: SiteSetting.uncategorized_category_id, + ) + expect( + CategoryTagStat.where( + category: Category.find(SiteSetting.uncategorized_category_id), + tag: tag1, + ).sum(:topic_count), + ).to eq(1) expect(CategoryTagStat.where(category: category, tag: tag1).sum(:topic_count)).to eq(0) end diff --git a/spec/integration/content_security_policy_spec.rb b/spec/integration/content_security_policy_spec.rb index 69a56acc65..658a50bb7a 100644 --- a/spec/integration/content_security_policy_spec.rb +++ b/spec/integration/content_security_policy_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -RSpec.describe 'content security policy integration' do - +RSpec.describe "content security policy integration" do it "adds the csp headers correctly" do SiteSetting.content_security_policy = false get "/" @@ -15,8 +14,10 @@ RSpec.describe 'content security policy integration' do context "with different hostnames" do before do SiteSetting.content_security_policy = true - RailsMultisite::ConnectionManagement.stubs(:current_db_hostnames).returns(['primary.example.com', 'secondary.example.com']) - RailsMultisite::ConnectionManagement.stubs(:current_hostname).returns('primary.example.com') + RailsMultisite::ConnectionManagement.stubs(:current_db_hostnames).returns( + %w[primary.example.com secondary.example.com], + ) + RailsMultisite::ConnectionManagement.stubs(:current_hostname).returns("primary.example.com") end it "works with the primary domain" do @@ -52,5 +53,4 @@ RSpec.describe 'content security policy integration' do expect(response.headers["Content-Security-Policy"]).to include("https://test.localhost") end end - end diff --git a/spec/integration/discord_omniauth_spec.rb b/spec/integration/discord_omniauth_spec.rb index 0ca80d1c96..230459a885 100644 --- a/spec/integration/discord_omniauth_spec.rb +++ b/spec/integration/discord_omniauth_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe 'Discord OAuth2' do +describe "Discord OAuth2" do let(:access_token) { "discord_access_token_448" } let(:client_id) { "abcdef11223344" } let(:client_secret) { "adddcccdddd99922" } @@ -9,15 +9,14 @@ describe 'Discord OAuth2' do fab!(:user1) { Fabricate(:user) } def setup_discord_email_stub(email, verified:) - stub_request(:get, "https://discord.com/api/users/@me") - .with( - headers: { - "Authorization" => "Bearer #{access_token}" - } - ) - .to_return( - status: 200, - body: JSON.dump( + stub_request(:get, "https://discord.com/api/users/@me").with( + headers: { + "Authorization" => "Bearer #{access_token}", + }, + ).to_return( + status: 200, + body: + JSON.dump( id: "80351110224678912", username: "Nelly", discriminator: "1337", @@ -26,14 +25,14 @@ describe 'Discord OAuth2' do email: email, flags: 64, banner: "06c16474723fe537c283b8efa61a30c8", - accent_color: 16711680, + accent_color: 16_711_680, premium_type: 1, - public_flags: 64 + public_flags: 64, ), - headers: { - "Content-Type" => "application/json" - } - ) + headers: { + "Content-Type" => "application/json", + }, + ) end before do @@ -41,50 +40,49 @@ describe 'Discord OAuth2' do SiteSetting.discord_client_id = client_id SiteSetting.discord_secret = client_secret - stub_request(:post, "https://discord.com/api/oauth2/token") - .with( - body: hash_including( + stub_request(:post, "https://discord.com/api/oauth2/token").with( + body: + hash_including( "client_id" => client_id, "client_secret" => client_secret, "code" => temp_code, "grant_type" => "authorization_code", - "redirect_uri" => "http://test.localhost/auth/discord/callback" - ) - ) - .to_return( - status: 200, - body: Rack::Utils.build_query( + "redirect_uri" => "http://test.localhost/auth/discord/callback", + ), + ).to_return( + status: 200, + body: + Rack::Utils.build_query( access_token: access_token, scope: "identify emails guilds", token_type: "Bearer", - expires_in: 604800, + expires_in: 604_800, refresh_token: "D43f5y0ahjqew82jZ4NViEr2YafMKhue", ), - headers: { - "Content-Type" => "application/x-www-form-urlencoded" - } - ) + headers: { + "Content-Type" => "application/x-www-form-urlencoded", + }, + ) - stub_request(:get, "https://discord.com/api/users/@me/guilds") - .with( - headers: { - "Authorization" => "Bearer #{access_token}" - } - ) - .to_return( - status: 200, - body: JSON.dump( + stub_request(:get, "https://discord.com/api/users/@me/guilds").with( + headers: { + "Authorization" => "Bearer #{access_token}", + }, + ).to_return( + status: 200, + body: + JSON.dump( id: "80351110224678912", name: "1337 Krew", icon: "8342729096ea3675442027381ff50dfe", owner: true, permissions: "36953089", - features: ["COMMUNITY", "NEWS"] + features: %w[COMMUNITY NEWS], ), - headers: { - "Content-Type" => "application/json" - } - ) + headers: { + "Content-Type" => "application/json", + }, + ) end it "doesn't sign in anyone if the email from discord is not verified" do @@ -94,10 +92,7 @@ describe 'Discord OAuth2' do setup_discord_email_stub(user1.email, verified: false) - post "/auth/discord/callback", params: { - state: session["omniauth.state"], - code: temp_code - } + post "/auth/discord/callback", params: { state: session["omniauth.state"], code: temp_code } expect(response.status).to eq(302) expect(response.location).to eq("http://test.localhost/") @@ -111,10 +106,7 @@ describe 'Discord OAuth2' do setup_discord_email_stub(user1.email, verified: true) - post "/auth/discord/callback", params: { - state: session["omniauth.state"], - code: temp_code - } + post "/auth/discord/callback", params: { state: session["omniauth.state"], code: temp_code } expect(response.status).to eq(302) expect(response.location).to eq("http://test.localhost/") diff --git a/spec/integration/email_style_spec.rb b/spec/integration/email_style_spec.rb index a53cff48e2..1b6aa37a7d 100644 --- a/spec/integration/email_style_spec.rb +++ b/spec/integration/email_style_spec.rb @@ -6,14 +6,14 @@ RSpec.describe EmailStyle do SiteSetting.email_custom_template = "%{email_content}<%= (111 * 333) %>" html = Email::Renderer.new(UserNotifications.signup(Fabricate(:user))).html expect(html).not_to include("36963") - expect(html).to include('') + expect(html).to include("") end end context "with a custom template" do before do SiteSetting.email_custom_template = "

    FOR YOU

    %{email_content}
    " - SiteSetting.email_custom_css = 'h1 { color: red; } div.body { color: #FAB; }' + SiteSetting.email_custom_css = "h1 { color: red; } div.body { color: #FAB; }" SiteSetting.email_custom_css_compiled = SiteSetting.email_custom_css end @@ -22,36 +22,36 @@ RSpec.describe EmailStyle do SiteSetting.remove_override!(:email_custom_css) end - context 'with invite' do + context "with invite" do fab!(:invite) { Fabricate(:invite) } let(:invite_mail) { InviteMailer.send_invite(invite) } subject(:mail_html) { Email::Renderer.new(invite_mail).html } - it 'applies customizations' do + it "applies customizations" do expect(mail_html.scan('

    FOR YOU

    ').count).to eq(1) expect(mail_html).to match("#{Discourse.base_url}/invites/#{invite.invite_key}") end - it 'applies customizations if compiled is missing' do + it "applies customizations if compiled is missing" do SiteSetting.remove_override!(:email_custom_css_compiled) expect(mail_html.scan('

    FOR YOU

    ').count).to eq(1) expect(mail_html).to match("#{Discourse.base_url}/invites/#{invite.invite_key}") end - it 'can apply RTL attrs' do - SiteSetting.default_locale = 'he' + it "can apply RTL attrs" do + SiteSetting.default_locale = "he" body_attrs = mail_html.match(/])+/) expect(body_attrs[0]&.downcase).to match(/text-align:\s*right/) expect(body_attrs[0]&.downcase).to include('dir="rtl"') end end - context 'when user_replied' do + context "when user_replied" do let(:response_by_user) { Fabricate(:user, name: "John Doe") } - let(:category) { Fabricate(:category, name: 'India') } + let(:category) { Fabricate(:category, name: "India") } let(:topic) { Fabricate(:topic, category: category, title: "Super cool topic") } - let(:post) { Fabricate(:post, topic: topic, raw: 'This is My super duper cool topic') } + let(:post) { Fabricate(:post, topic: topic, raw: "This is My super duper cool topic") } let(:response) { Fabricate(:basic_reply, topic: post.topic, user: response_by_user) } let(:user) { Fabricate(:user) } let(:notification) { Fabricate(:replied_notification, user: user, post: response) } @@ -61,7 +61,7 @@ RSpec.describe EmailStyle do user, post: response, notification_type: notification.notification_type, - notification_data_hash: notification.data_hash + notification_data_hash: notification.data_hash, ) end @@ -72,42 +72,45 @@ RSpec.describe EmailStyle do expect(mail_html.scan('

    FOR YOU

    ').count).to eq(1) matches = mail_html.match(/
    #{post.raw}/) - expect(matches[1]).to include('color: #FAB;') # custom - expect(matches[1]).to include('padding-top:5px;') # div.body + expect(matches[1]).to include("color: #FAB;") # custom + expect(matches[1]).to include("padding-top:5px;") # div.body end # TODO: translation override end - context 'with signup' do + context "with signup" do let(:signup_mail) { UserNotifications.signup(Fabricate(:user)) } subject(:mail_html) { Email::Renderer.new(signup_mail).html } it "customizations are applied to html part of emails" do expect(mail_html.scan('

    FOR YOU

    ').count).to eq(1) - expect(mail_html).to include('activate-account') + expect(mail_html).to include("activate-account") end - context 'with translation override' do + context "with translation override" do before do TranslationOverride.upsert!( SiteSetting.default_locale, - 'user_notifications.signup.text_body_template', - "CLICK THAT LINK: %{base_url}/u/activate-account/%{email_token}" + "user_notifications.signup.text_body_template", + "CLICK THAT LINK: %{base_url}/u/activate-account/%{email_token}", ) end after do - TranslationOverride.revert!(SiteSetting.default_locale, ['user_notifications.signup.text_body_template']) + TranslationOverride.revert!( + SiteSetting.default_locale, + ["user_notifications.signup.text_body_template"], + ) end it "applies customizations when translation override exists" do expect(mail_html.scan('

    FOR YOU

    ').count).to eq(1) - expect(mail_html.scan('CLICK THAT LINK').count).to eq(1) + expect(mail_html.scan("CLICK THAT LINK").count).to eq(1) end end - context 'with some bad css' do + context "with some bad css" do before do SiteSetting.email_custom_css = '@import "nope.css"; h1 {{{ size: really big; ' SiteSetting.email_custom_css_compiled = SiteSetting.email_custom_css @@ -115,13 +118,15 @@ RSpec.describe EmailStyle do it "can render the html" do expect(mail_html.scan(/FOR YOU<\/h1>/).count).to eq(1) - expect(mail_html).to include('activate-account') + expect(mail_html).to include("activate-account") end end end - context 'with digest' do - fab!(:popular_topic) { Fabricate(:topic, user: Fabricate(:coding_horror), created_at: 1.hour.ago) } + context "with digest" do + fab!(:popular_topic) do + Fabricate(:topic, user: Fabricate(:coding_horror), created_at: 1.hour.ago) + end let(:summary_email) { UserNotifications.digest(Fabricate(:user)) } subject(:mail_html) { Email::Renderer.new(summary_email).html } @@ -133,7 +138,7 @@ RSpec.describe EmailStyle do it "doesn't apply customizations if apply_custom_styles_to_digest is disabled" do SiteSetting.apply_custom_styles_to_digest = false expect(mail_html).to_not include('

    FOR YOU

    ') - expect(mail_html).to_not include('FOR YOU') + expect(mail_html).to_not include("FOR YOU") expect(mail_html).to include(popular_topic.title) end end diff --git a/spec/integration/facebook_omniauth_spec.rb b/spec/integration/facebook_omniauth_spec.rb index e1bef76668..5f596af6ee 100644 --- a/spec/integration/facebook_omniauth_spec.rb +++ b/spec/integration/facebook_omniauth_spec.rb @@ -1,11 +1,13 @@ # frozen_string_literal: true -describe 'Facebook OAuth2' do +describe "Facebook OAuth2" do let(:access_token) { "facebook_access_token_448" } let(:app_id) { "432489234823984" } let(:app_secret) { "adddcccdddd99922" } let(:temp_code) { "facebook_temp_code_544254" } - let(:appsecret_proof) { OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, app_secret, access_token) } + let(:appsecret_proof) do + OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, app_secret, access_token) + end fab!(:user1) { Fabricate(:user) } @@ -18,19 +20,16 @@ describe 'Facebook OAuth2' do } body[:email] = email if email - stub_request(:get, "https://graph.facebook.com/v5.0/me?appsecret_proof=#{appsecret_proof}&fields=name,first_name,last_name,email") - .with( - headers: { - "Authorization" => "OAuth #{access_token}" - } - ) - .to_return( - status: 200, - body: JSON.dump(body), - headers: { - "Content-Type" => "application/json" - } - ) + stub_request( + :get, + "https://graph.facebook.com/v5.0/me?appsecret_proof=#{appsecret_proof}&fields=name,first_name,last_name,email", + ).with(headers: { "Authorization" => "OAuth #{access_token}" }).to_return( + status: 200, + body: JSON.dump(body), + headers: { + "Content-Type" => "application/json", + }, + ) end before do @@ -38,27 +37,23 @@ describe 'Facebook OAuth2' do SiteSetting.facebook_app_id = app_id SiteSetting.facebook_app_secret = app_secret - stub_request(:post, "https://graph.facebook.com/v5.0/oauth/access_token") - .with( - body: hash_including( + stub_request(:post, "https://graph.facebook.com/v5.0/oauth/access_token").with( + body: + hash_including( "client_id" => app_id, "client_secret" => app_secret, "code" => temp_code, "grant_type" => "authorization_code", - "redirect_uri" => "http://test.localhost/auth/facebook/callback" - ) - ) - .to_return( - status: 200, - body: Rack::Utils.build_query( - access_token: access_token, - scope: "email", - token_type: "Bearer", + "redirect_uri" => "http://test.localhost/auth/facebook/callback", ), - headers: { - "Content-Type" => "application/x-www-form-urlencoded" - } - ) + ).to_return( + status: 200, + body: + Rack::Utils.build_query(access_token: access_token, scope: "email", token_type: "Bearer"), + headers: { + "Content-Type" => "application/x-www-form-urlencoded", + }, + ) end it "signs in the user if the API response from facebook includes an email (implies it's verified) and the email matches an existing user's" do @@ -68,10 +63,7 @@ describe 'Facebook OAuth2' do setup_facebook_email_stub(email: user1.email) - post "/auth/facebook/callback", params: { - state: session["omniauth.state"], - code: temp_code - } + post "/auth/facebook/callback", params: { state: session["omniauth.state"], code: temp_code } expect(response.status).to eq(302) expect(response.location).to eq("http://test.localhost/") @@ -85,10 +77,7 @@ describe 'Facebook OAuth2' do setup_facebook_email_stub(email: nil) - post "/auth/facebook/callback", params: { - state: session["omniauth.state"], - code: temp_code - } + post "/auth/facebook/callback", params: { state: session["omniauth.state"], code: temp_code } expect(response.status).to eq(302) expect(response.location).to eq("http://test.localhost/") diff --git a/spec/integration/flags_spec.rb b/spec/integration/flags_spec.rb index 7459174757..61201ca49d 100644 --- a/spec/integration/flags_spec.rb +++ b/spec/integration/flags_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true RSpec.describe PostAction do - it "triggers the 'flag_reviewed' event when there was at least one flag" do admin = Fabricate(:admin) @@ -14,5 +13,4 @@ RSpec.describe PostAction do events = DiscourseEvent.track_events { PostDestroyer.new(admin, flagged_post).destroy } expect(events.map { |e| e[:event_name] }).to include(:flag_reviewed) end - end diff --git a/spec/integration/github_omniauth_spec.rb b/spec/integration/github_omniauth_spec.rb index 9714526d1b..05cbcdddc4 100644 --- a/spec/integration/github_omniauth_spec.rb +++ b/spec/integration/github_omniauth_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe 'GitHub Oauth2' do +describe "GitHub Oauth2" do let(:access_token) { "github_access_token_448" } let(:client_id) { "abcdef11223344" } let(:client_secret) { "adddcccdddd99922" } @@ -10,19 +10,17 @@ describe 'GitHub Oauth2' do fab!(:user2) { Fabricate(:user) } def setup_github_emails_stub(emails) - stub_request(:get, "https://api.github.com/user/emails") - .with( - headers: { - "Authorization" => "Bearer #{access_token}" - } - ) - .to_return( - status: 200, - body: JSON.dump(emails), - headers: { - "Content-Type" => "application/json" - } - ) + stub_request(:get, "https://api.github.com/user/emails").with( + headers: { + "Authorization" => "Bearer #{access_token}", + }, + ).to_return( + status: 200, + body: JSON.dump(emails), + headers: { + "Content-Type" => "application/json", + }, + ) end before do @@ -30,35 +28,34 @@ describe 'GitHub Oauth2' do SiteSetting.github_client_id = client_id SiteSetting.github_client_secret = client_secret - stub_request(:post, "https://github.com/login/oauth/access_token") - .with( - body: hash_including( + stub_request(:post, "https://github.com/login/oauth/access_token").with( + body: + hash_including( "client_id" => client_id, "client_secret" => client_secret, "code" => temp_code, - ) - ) - .to_return( - status: 200, - body: Rack::Utils.build_query( + ), + ).to_return( + status: 200, + body: + Rack::Utils.build_query( access_token: access_token, scope: "user:email", - token_type: "bearer" + token_type: "bearer", ), - headers: { - "Content-Type" => "application/x-www-form-urlencoded" - } - ) + headers: { + "Content-Type" => "application/x-www-form-urlencoded", + }, + ) - stub_request(:get, "https://api.github.com/user") - .with( - headers: { - "Authorization" => "Bearer #{access_token}" - } - ) - .to_return( - status: 200, - body: JSON.dump( + stub_request(:get, "https://api.github.com/user").with( + headers: { + "Authorization" => "Bearer #{access_token}", + }, + ).to_return( + status: 200, + body: + JSON.dump( login: "octocat", id: 1, node_id: "MDQ6VXNlcjE=", @@ -94,20 +91,20 @@ describe 'GitHub Oauth2' do private_gists: 81, total_private_repos: 100, owned_private_repos: 100, - disk_usage: 10000, + disk_usage: 10_000, collaborators: 8, two_factor_authentication: true, plan: { name: "Medium", space: 400, private_repos: 20, - collaborators: 0 - } + collaborators: 0, + }, ), - headers: { - "Content-Type" => "application/json" - } - ) + headers: { + "Content-Type" => "application/json", + }, + ) end it "doesn't sign in anyone if none of the emails from github are verified" do @@ -117,25 +114,12 @@ describe 'GitHub Oauth2' do setup_github_emails_stub( [ - { - email: user1.email, - primary: true, - verified: false, - visibility: "private" - }, - { - email: user2.email, - primary: false, - verified: false, - visibility: "private" - } - ] + { email: user1.email, primary: true, verified: false, visibility: "private" }, + { email: user2.email, primary: false, verified: false, visibility: "private" }, + ], ) - post "/auth/github/callback", params: { - state: session["omniauth.state"], - code: temp_code - } + post "/auth/github/callback", params: { state: session["omniauth.state"], code: temp_code } expect(response.status).to eq(302) expect(response.location).to eq("http://test.localhost/") expect(session[:current_user_id]).to be_blank @@ -148,25 +132,12 @@ describe 'GitHub Oauth2' do setup_github_emails_stub( [ - { - email: user1.email, - primary: true, - verified: false, - visibility: "private" - }, - { - email: user2.email, - primary: false, - verified: true, - visibility: "private" - } - ] + { email: user1.email, primary: true, verified: false, visibility: "private" }, + { email: user2.email, primary: false, verified: true, visibility: "private" }, + ], ) - post "/auth/github/callback", params: { - state: session["omniauth.state"], - code: temp_code - } + post "/auth/github/callback", params: { state: session["omniauth.state"], code: temp_code } expect(response.status).to eq(302) expect(response.location).to eq("http://test.localhost/") expect(session[:current_user_id]).to eq(user2.id) @@ -183,21 +154,13 @@ describe 'GitHub Oauth2' do email: "somerandomemail@discourse.org", primary: true, verified: true, - visibility: "private" + visibility: "private", }, - { - email: user2.email, - primary: false, - verified: false, - visibility: "private" - } - ] + { email: user2.email, primary: false, verified: false, visibility: "private" }, + ], ) - post "/auth/github/callback", params: { - state: session["omniauth.state"], - code: temp_code - } + post "/auth/github/callback", params: { state: session["omniauth.state"], code: temp_code } expect(response.status).to eq(302) expect(response.location).to eq("http://test.localhost/") expect(session[:current_user_id]).to be_blank @@ -210,25 +173,12 @@ describe 'GitHub Oauth2' do setup_github_emails_stub( [ - { - email: user1.email, - primary: true, - verified: true, - visibility: "private" - }, - { - email: user2.email, - primary: false, - verified: true, - visibility: "private" - } - ] + { email: user1.email, primary: true, verified: true, visibility: "private" }, + { email: user2.email, primary: false, verified: true, visibility: "private" }, + ], ) - post "/auth/github/callback", params: { - state: session["omniauth.state"], - code: temp_code - } + post "/auth/github/callback", params: { state: session["omniauth.state"], code: temp_code } expect(response.status).to eq(302) expect(response.location).to eq("http://test.localhost/") expect(session[:current_user_id]).to eq(user1.id) diff --git a/spec/integration/group_spec.rb b/spec/integration/group_spec.rb index 0573053b68..c9bf71e206 100644 --- a/spec/integration/group_spec.rb +++ b/spec/integration/group_spec.rb @@ -6,18 +6,20 @@ RSpec.describe Group do :group, visibility_level: Group.visibility_levels[:public], mentionable_level: Group::ALIAS_LEVELS[:nobody], - users: [ Fabricate(:user) ] + users: [Fabricate(:user)], ) end let(:post) { Fabricate(:post, raw: "mention @#{group.name}") } - before do - Jobs.run_immediately! - end + before { Jobs.run_immediately! } - it 'users can mention public groups, but does not create a notification' do - expect { post }.not_to change { Notification.where(notification_type: Notification.types[:group_mentioned]).count } - expect(post.cooked).to include("@#{group.name}") + it "users can mention public groups, but does not create a notification" do + expect { post }.not_to change { + Notification.where(notification_type: Notification.types[:group_mentioned]).count + } + expect(post.cooked).to include( + "@#{group.name}", + ) end end diff --git a/spec/integration/invalid_request_spec.rb b/spec/integration/invalid_request_spec.rb index e3e4e0bad7..6d19bcb9c6 100644 --- a/spec/integration/invalid_request_spec.rb +++ b/spec/integration/invalid_request_spec.rb @@ -1,27 +1,31 @@ # frozen_string_literal: true -RSpec.describe 'invalid requests', type: :request do +RSpec.describe "invalid requests", type: :request do before do @orig_logger = Rails.logger Rails.logger = @fake_logger = FakeLogger.new end - after do - Rails.logger = @orig_logger - end + after { Rails.logger = @orig_logger } it "handles NotFound with invalid json body" do - post "/latest.json", params: "{some: malformed: json", headers: { "content-type" => "application/json" } + post "/latest.json", + params: "{some: malformed: json", + headers: { + "content-type" => "application/json", + } expect(response.status).to eq(404) expect(@fake_logger.warnings.length).to eq(0) expect(@fake_logger.errors.length).to eq(0) end it "handles EOFError when multipart request is malformed" do - post "/latest.json", params: "somecontent", headers: { - "content-type" => "multipart/form-data; boundary=abcde", - "content-length" => "1" - } + post "/latest.json", + params: "somecontent", + headers: { + "content-type" => "multipart/form-data; boundary=abcde", + "content-length" => "1", + } expect(response.status).to eq(400) expect(@fake_logger.warnings.length).to eq(0) expect(@fake_logger.errors.length).to eq(0) @@ -33,5 +37,4 @@ RSpec.describe 'invalid requests', type: :request do expect(@fake_logger.warnings.length).to eq(0) expect(@fake_logger.errors.length).to eq(0) end - end diff --git a/spec/integration/invite_only_registration_spec.rb b/spec/integration/invite_only_registration_spec.rb index a30869094e..1ea4a9c1ec 100644 --- a/spec/integration/invite_only_registration_spec.rb +++ b/spec/integration/invite_only_registration_spec.rb @@ -1,45 +1,46 @@ # encoding: UTF-8 # frozen_string_literal: true -RSpec.describe 'invite only' do - - describe '#create invite only' do - it 'can create user via API' do - +RSpec.describe "invite only" do + describe "#create invite only" do + it "can create user via API" do SiteSetting.invite_only = true Jobs.run_immediately! admin = Fabricate(:admin) api_key = Fabricate(:api_key, user: admin) - post '/users.json', params: { - name: 'bob', - username: 'bob', - password: 'strongpassword', - email: 'bob@bob.com', - }, headers: { - HTTP_API_KEY: api_key.key, - HTTP_API_USERNAME: admin.username - } + post "/users.json", + params: { + name: "bob", + username: "bob", + password: "strongpassword", + email: "bob@bob.com", + }, + headers: { + HTTP_API_KEY: api_key.key, + HTTP_API_USERNAME: admin.username, + } user_id = response.parsed_body["user_id"] expect(user_id).to be > 0 # activate and approve - put "/admin/users/#{user_id}/activate.json", headers: { - HTTP_API_KEY: api_key.key, - HTTP_API_USERNAME: admin.username - } + put "/admin/users/#{user_id}/activate.json", + headers: { + HTTP_API_KEY: api_key.key, + HTTP_API_USERNAME: admin.username, + } - put "/admin/users/#{user_id}/approve.json", headers: { - HTTP_API_KEY: api_key.key, - HTTP_API_USERNAME: admin.username - } + put "/admin/users/#{user_id}/approve.json", + headers: { + HTTP_API_KEY: api_key.key, + HTTP_API_USERNAME: admin.username, + } u = User.find(user_id) expect(u.active).to eq(true) expect(u.approved).to eq(true) - end end end diff --git a/spec/integration/message_bus_spec.rb b/spec/integration/message_bus_spec.rb index a2f961ddc5..ca814fa8ff 100644 --- a/spec/integration/message_bus_spec.rb +++ b/spec/integration/message_bus_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -RSpec.describe 'message bus integration' do - +RSpec.describe "message bus integration" do it "allows anonymous requests to the messagebus" do post "/message-bus/poll" expect(response.status).to eq(200) @@ -27,5 +26,4 @@ RSpec.describe 'message bus integration' do expect(response.status).to eq(200) end end - end diff --git a/spec/integration/multisite_cookies_spec.rb b/spec/integration/multisite_cookies_spec.rb index 15bad1c74b..9a24b4d0e2 100644 --- a/spec/integration/multisite_cookies_spec.rb +++ b/spec/integration/multisite_cookies_spec.rb @@ -1,19 +1,25 @@ # frozen_string_literal: true -RSpec.describe 'multisite', type: [:multisite, :request] do +RSpec.describe "multisite", type: %i[multisite request] do it "works" do get "http://test.localhost/session/csrf.json" expect(response.status).to eq(200) cookie = CGI.escape(response.cookies["_forum_session"]) id1 = session["session_id"] - get "http://test.localhost/session/csrf.json", headers: { "Cookie" => "_forum_session=#{cookie};" } + get "http://test.localhost/session/csrf.json", + headers: { + "Cookie" => "_forum_session=#{cookie};", + } expect(response.status).to eq(200) id2 = session["session_id"] expect(id1).to eq(id2) - get "http://test2.localhost/session/csrf.json", headers: { "Cookie" => "_forum_session=#{cookie};" } + get "http://test2.localhost/session/csrf.json", + headers: { + "Cookie" => "_forum_session=#{cookie};", + } expect(response.status).to eq(200) id3 = session["session_id"] diff --git a/spec/integration/multisite_spec.rb b/spec/integration/multisite_spec.rb index d53c443560..a1f27013f6 100644 --- a/spec/integration/multisite_spec.rb +++ b/spec/integration/multisite_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe 'multisite', type: [:multisite, :request] do +RSpec.describe "multisite", type: %i[multisite request] do it "should always allow /srv/status through" do get "http://unknown.com/srv/status" expect(response.status).to eq(200) @@ -13,10 +13,12 @@ RSpec.describe 'multisite', type: [:multisite, :request] do end it "should hit correct site otherwise" do - site_1_url = Fabricate(:topic, title: "Site 1 Topic Title", user: Discourse.system_user).relative_url + site_1_url = + Fabricate(:topic, title: "Site 1 Topic Title", user: Discourse.system_user).relative_url - test_multisite_connection('second') do - site_2_url = Fabricate(:topic, title: "Site 2 Topic Title", user: Discourse.system_user).relative_url + test_multisite_connection("second") do + site_2_url = + Fabricate(:topic, title: "Site 2 Topic Title", user: Discourse.system_user).relative_url get "http://test.localhost/#{site_1_url}.json" expect(request.env["RAILS_MULTISITE_HOST"]).to eq("test.localhost") diff --git a/spec/integration/rate_limiting_spec.rb b/spec/integration/rate_limiting_spec.rb index ebf24e909b..cdda0a8a75 100644 --- a/spec/integration/rate_limiting_spec.rb +++ b/spec/integration/rate_limiting_spec.rb @@ -1,8 +1,7 @@ # encoding: UTF-8 # frozen_string_literal: true -RSpec.describe 'rate limiter integration' do - +RSpec.describe "rate limiter integration" do before do RateLimiter.enable RateLimiter.clear_all! @@ -13,12 +12,13 @@ RSpec.describe 'rate limiter integration' do global_setting :reject_message_bus_queue_seconds, 0.1 - post "/message-bus/#{SecureRandom.hex}/poll", headers: { - "HTTP_X_REQUEST_START" => "t=#{Time.now.to_f - 0.2}" - } + post "/message-bus/#{SecureRandom.hex}/poll", + headers: { + "HTTP_X_REQUEST_START" => "t=#{Time.now.to_f - 0.2}", + } expect(response.status).to eq(429) - expect(response.headers['Retry-After'].to_i).to be > 29 + expect(response.headers["Retry-After"].to_i).to be > 29 end it "will not rate limit when all is good" do @@ -26,9 +26,10 @@ RSpec.describe 'rate limiter integration' do global_setting :reject_message_bus_queue_seconds, 0.1 - post "/message-bus/#{SecureRandom.hex}/poll", headers: { - "HTTP_X_REQUEST_START" => "t=#{Time.now.to_f - 0.05}" - } + post "/message-bus/#{SecureRandom.hex}/poll", + headers: { + "HTTP_X_REQUEST_START" => "t=#{Time.now.to_f - 0.05}", + } expect(response.status).to eq(200) end @@ -37,15 +38,15 @@ RSpec.describe 'rate limiter integration' do name = Auth::DefaultCurrentUserProvider::TOKEN_COOKIE # we try 11 times because the rate limit is 10 - 11.times { + 11.times do cookies[name] = SecureRandom.hex - get '/categories.json' + get "/categories.json" expect(response.cookies.has_key?(name)).to eq(true) expect(response.cookies[name]).to be_nil - } + end end - it 'can cleanly limit requests and sets a Retry-After header' do + it "can cleanly limit requests and sets a Retry-After header" do freeze_time RateLimiter.clear_all! @@ -55,17 +56,19 @@ RSpec.describe 'rate limiter integration' do global_setting :max_admin_api_reqs_per_minute, 1 - get '/admin/api/keys.json', headers: { - HTTP_API_KEY: api_key.key, - HTTP_API_USERNAME: admin.username - } + get "/admin/api/keys.json", + headers: { + HTTP_API_KEY: api_key.key, + HTTP_API_USERNAME: admin.username, + } expect(response.status).to eq(200) - get '/admin/api/keys.json', headers: { - HTTP_API_KEY: api_key.key, - HTTP_API_USERNAME: admin.username - } + get "/admin/api/keys.json", + headers: { + HTTP_API_KEY: api_key.key, + HTTP_API_USERNAME: admin.username, + } expect(response.status).to eq(429) diff --git a/spec/integration/request_tracker_spec.rb b/spec/integration/request_tracker_spec.rb index 239d4155d3..bec86c245b 100644 --- a/spec/integration/request_tracker_spec.rb +++ b/spec/integration/request_tracker_spec.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true -RSpec.describe 'request tracker' do +RSpec.describe "request tracker" do let(:api_key) do Fabricate( :api_key, user: Fabricate.build(:user), - api_key_scopes: [ApiKeyScope.new(resource: 'users', action: 'show')] + api_key_scopes: [ApiKeyScope.new(resource: "users", action: "show")], ) end let(:user_api_key) do - Fabricate(:user_api_key, scopes: [Fabricate.build(:user_api_key_scope, name: 'session_info')]) + Fabricate(:user_api_key, scopes: [Fabricate.build(:user_api_key_scope, name: "session_info")]) end before do @@ -25,8 +25,8 @@ RSpec.describe 'request tracker' do CachedCounting.disable end - context 'when using an api key' do - it 'is counted as an API request' do + context "when using an api key" do + it "is counted as an API request" do get "/u/#{api_key.user.username}.json", headers: { HTTP_API_KEY: api_key.key } expect(response.status).to eq(200) @@ -37,9 +37,9 @@ RSpec.describe 'request tracker' do end end - context 'when using an user api key' do - it 'is counted as a user API request' do - get '/session/current.json', headers: { HTTP_USER_API_KEY: user_api_key.key } + context "when using an user api key" do + it "is counted as a user API request" do + get "/session/current.json", headers: { HTTP_USER_API_KEY: user_api_key.key } expect(response.status).to eq(200) CachedCounting.flush diff --git a/spec/integration/same_ip_spammers_spec.rb b/spec/integration/same_ip_spammers_spec.rb index a586891cf4..2d3f928920 100644 --- a/spec/integration/same_ip_spammers_spec.rb +++ b/spec/integration/same_ip_spammers_spec.rb @@ -2,45 +2,41 @@ # frozen_string_literal: true RSpec.describe "spammers on same IP" do - let(:ip_address) { '182.189.119.174' } - let!(:spammer1) { Fabricate(:user, ip_address: ip_address) } - let!(:spammer2) { Fabricate(:user, ip_address: ip_address) } - let(:spammer3) { Fabricate(:user, ip_address: ip_address) } + let(:ip_address) { "182.189.119.174" } + let!(:spammer1) { Fabricate(:user, ip_address: ip_address) } + let!(:spammer2) { Fabricate(:user, ip_address: ip_address) } + let(:spammer3) { Fabricate(:user, ip_address: ip_address) } - context 'when flag_sockpuppets is disabled' do - let!(:first_post) { create_post(user: spammer1) } - let!(:second_post) { create_post(user: spammer2, topic: first_post.topic) } + context "when flag_sockpuppets is disabled" do + let!(:first_post) { create_post(user: spammer1) } + let!(:second_post) { create_post(user: spammer2, topic: first_post.topic) } - it 'should not increase spam count' do - expect(first_post.reload.spam_count).to eq(0) + it "should not increase spam count" do + expect(first_post.reload.spam_count).to eq(0) expect(second_post.reload.spam_count).to eq(0) end end - context 'when flag_sockpuppets is enabled' do - before do - SiteSetting.flag_sockpuppets = true - end + context "when flag_sockpuppets is enabled" do + before { SiteSetting.flag_sockpuppets = true } - after do - SiteSetting.flag_sockpuppets = false - end + after { SiteSetting.flag_sockpuppets = false } - context 'when first spammer starts a topic' do + context "when first spammer starts a topic" do let!(:first_post) { create_post(user: spammer1) } - context 'when second spammer replies' do - let!(:second_post) { create_post(user: spammer2, topic: first_post.topic) } + context "when second spammer replies" do + let!(:second_post) { create_post(user: spammer2, topic: first_post.topic) } - it 'should increase spam count' do + it "should increase spam count" do expect(first_post.reload.spam_count).to eq(1) expect(second_post.reload.spam_count).to eq(1) end - context 'with third spam post' do + context "with third spam post" do let!(:third_post) { create_post(user: spammer3, topic: first_post.topic) } - it 'should increase spam count' do + it "should increase spam count" do expect(first_post.reload.spam_count).to eq(1) expect(second_post.reload.spam_count).to eq(1) expect(third_post.reload.spam_count).to eq(1) @@ -49,16 +45,18 @@ RSpec.describe "spammers on same IP" do end end - context 'when first user is not new' do - let!(:old_user) { Fabricate(:user, ip_address: ip_address, created_at: 2.days.ago, trust_level: TrustLevel[1]) } + context "when first user is not new" do + let!(:old_user) do + Fabricate(:user, ip_address: ip_address, created_at: 2.days.ago, trust_level: TrustLevel[1]) + end - context 'when first user starts a topic' do + context "when first user starts a topic" do let!(:first_post) { create_post(user: old_user) } - context 'with a reply by a new user at the same IP address' do - let!(:second_post) { create_post(user: spammer2, topic: first_post.topic) } + context "with a reply by a new user at the same IP address" do + let!(:second_post) { create_post(user: spammer2, topic: first_post.topic) } - it 'should increase the spam count correctly' do + it "should increase the spam count correctly" do expect(first_post.reload.spam_count).to eq(0) expect(second_post.reload.spam_count).to eq(1) end diff --git a/spec/integration/spam_rules_spec.rb b/spec/integration/spam_rules_spec.rb index edefab13de..9c1f2c9f78 100644 --- a/spec/integration/spam_rules_spec.rb +++ b/spec/integration/spam_rules_spec.rb @@ -2,11 +2,11 @@ # frozen_string_literal: true RSpec.describe "spam rules for users" do - describe 'auto-silence users based on flagging' do - fab!(:admin) { Fabricate(:admin) } # needed to send a system message + describe "auto-silence users based on flagging" do + fab!(:admin) { Fabricate(:admin) } # needed to send a system message fab!(:moderator) { Fabricate(:moderator) } - fab!(:user1) { Fabricate(:user) } - fab!(:user2) { Fabricate(:user) } + fab!(:user1) { Fabricate(:user) } + fab!(:user2) { Fabricate(:user) } before do SiteSetting.hide_post_sensitivity = Reviewable.sensitivities[:disabled] @@ -15,23 +15,21 @@ RSpec.describe "spam rules for users" do SiteSetting.num_users_to_silence_new_user = 2 end - context 'when spammer is a new user' do - fab!(:spammer) { Fabricate(:user, trust_level: TrustLevel[0]) } + context "when spammer is a new user" do + fab!(:spammer) { Fabricate(:user, trust_level: TrustLevel[0]) } - context 'when spammer post is not flagged enough times' do - let!(:spam_post) { create_post(user: spammer) } + context "when spammer post is not flagged enough times" do + let!(:spam_post) { create_post(user: spammer) } let!(:spam_post2) { create_post(user: spammer) } - before do - PostActionCreator.create(user1, spam_post, :spam) - end + before { PostActionCreator.create(user1, spam_post, :spam) } - it 'should not hide the post' do + it "should not hide the post" do expect(spam_post.reload).to_not be_hidden end - context 'when spam posts are flagged enough times, but not by enough users' do - it 'should not hide the post' do + context "when spam posts are flagged enough times, but not by enough users" do + it "should not hide the post" do PostActionCreator.create(user1, spam_post2, :spam) expect(spam_post.reload).to_not be_hidden @@ -40,16 +38,30 @@ RSpec.describe "spam rules for users" do end end - context 'when one spam post is flagged enough times by enough users' do + context "when one spam post is flagged enough times by enough users" do fab!(:another_topic) { Fabricate(:topic) } let!(:private_messages_count) { spammer.private_topics_count } let!(:mod_pm_count) { moderator.private_topics_count } let!(:reviewable) { PostActionCreator.spam(user2, spam_post).reviewable } - it 'should hide the posts' do + it "should hide the posts" do expect(Guardian.new(spammer).can_create_topic?(nil)).to be(false) - expect { PostCreator.create(spammer, title: 'limited time offer for you', raw: 'better buy this stuff ok', archetype_id: 1) }.to raise_error(Discourse::InvalidAccess) - expect(PostCreator.create(spammer, topic_id: another_topic.id, raw: 'my reply is spam in your topic', archetype_id: 1)).to eq(nil) + expect { + PostCreator.create( + spammer, + title: "limited time offer for you", + raw: "better buy this stuff ok", + archetype_id: 1, + ) + }.to raise_error(Discourse::InvalidAccess) + expect( + PostCreator.create( + spammer, + topic_id: another_topic.id, + raw: "my reply is spam in your topic", + archetype_id: 1, + ), + ).to eq(nil) expect(spammer.reload).to be_silenced expect(spam_post.reload).to be_hidden expect(spam_post2.reload).to be_hidden @@ -57,22 +69,24 @@ RSpec.describe "spam rules for users" do end context "when a post is deleted" do - it 'should silence the spammer' do - spam_post.trash!(moderator); spammer.reload + it "should silence the spammer" do + spam_post.trash!(moderator) + spammer.reload expect(spammer.reload).to be_silenced end end context "when spammer becomes trust level 1" do - it 'should silence the spammer' do - spammer.change_trust_level!(TrustLevel[1]); spammer.reload + it "should silence the spammer" do + spammer.change_trust_level!(TrustLevel[1]) + spammer.reload expect(spammer.reload).to be_silenced end end end - context 'with hide_post_sensitivity' do - it 'should silence the spammer' do + context "with hide_post_sensitivity" do + it "should silence the spammer" do Reviewable.set_priorities(high: 2.0) SiteSetting.hide_post_sensitivity = Reviewable.sensitivities[:low] PostActionCreator.create(user2, spam_post, :spam) @@ -84,19 +98,26 @@ RSpec.describe "spam rules for users" do end context "when spammer has trust level basic" do - let(:spammer) { Fabricate(:user, trust_level: TrustLevel[1]) } + let(:spammer) { Fabricate(:user, trust_level: TrustLevel[1]) } - context 'when one spam post is flagged enough times by enough users' do - let!(:spam_post) { Fabricate(:post, user: spammer) } + context "when one spam post is flagged enough times by enough users" do + let!(:spam_post) { Fabricate(:post, user: spammer) } let!(:private_messages_count) { spammer.private_topics_count } - it 'should not allow spammer to create new posts' do + it "should not allow spammer to create new posts" do PostActionCreator.create(user1, spam_post, :spam) PostActionCreator.create(user2, spam_post, :spam) expect(spam_post.reload).to_not be_hidden expect(Guardian.new(spammer).can_create_topic?(nil)).to be(true) - expect { PostCreator.create(spammer, title: 'limited time offer for you', raw: 'better buy this stuff ok', archetype_id: 1) }.to_not raise_error + expect { + PostCreator.create( + spammer, + title: "limited time offer for you", + raw: "better buy this stuff ok", + archetype_id: 1, + ) + }.to_not raise_error expect(spammer.reload.private_topics_count).to eq(private_messages_count) end end @@ -104,11 +125,11 @@ RSpec.describe "spam rules for users" do [[:user, trust_level: TrustLevel[2]], [:admin], [:moderator]].each do |spammer_args| context "spammer is trusted #{spammer_args[0]}" do - let!(:spammer) { Fabricate(*spammer_args) } - let!(:spam_post) { Fabricate(:post, user: spammer) } + let!(:spammer) { Fabricate(*spammer_args) } + let!(:spam_post) { Fabricate(:post, user: spammer) } let!(:private_messages_count) { spammer.private_topics_count } - it 'should not hide the post' do + it "should not hide the post" do PostActionCreator.create(user1, spam_post, :spam) PostActionCreator.create(user2, spam_post, :spam) diff --git a/spec/integration/topic_auto_close_spec.rb b/spec/integration/topic_auto_close_spec.rb index 95821bc30e..d4daaa070a 100644 --- a/spec/integration/topic_auto_close_spec.rb +++ b/spec/integration/topic_auto_close_spec.rb @@ -4,36 +4,34 @@ RSpec.describe Topic do let(:job_klass) { Jobs::CloseTopic } - context 'when creating a topic without auto-close' do + context "when creating a topic without auto-close" do let(:topic) { Fabricate(:topic, category: category) } - context 'when uncategorized' do + context "when uncategorized" do let(:category) { nil } - it 'should not schedule the topic to auto-close' do + it "should not schedule the topic to auto-close" do expect(topic.public_topic_timer).to eq(nil) expect(job_klass.jobs).to eq([]) end end - context 'with category without default auto-close' do + context "with category without default auto-close" do let(:category) { Fabricate(:category, auto_close_hours: nil) } - it 'should not schedule the topic to auto-close' do + it "should not schedule the topic to auto-close" do expect(topic.public_topic_timer).to eq(nil) expect(job_klass.jobs).to eq([]) end end - context 'when jobs may be queued' do - before do - freeze_time - end + context "when jobs may be queued" do + before { freeze_time } - context 'when category has a default auto-close' do + context "when category has a default auto-close" do let(:category) { Fabricate(:category, auto_close_hours: 2.0) } - it 'should schedule the topic to auto-close' do + it "should schedule the topic to auto-close" do topic topic_status_update = TopicTimer.last @@ -42,11 +40,11 @@ RSpec.describe Topic do expect(topic.public_topic_timer.execute_at).to be_within_one_second_of(2.hours.from_now) end - context 'when topic was created by staff user' do + context "when topic was created by staff user" do let(:admin) { Fabricate(:admin) } let(:staff_topic) { Fabricate(:topic, user: admin, category: category) } - it 'should schedule the topic to auto-close' do + it "should schedule the topic to auto-close" do staff_topic topic_status_update = TopicTimer.last @@ -56,23 +54,24 @@ RSpec.describe Topic do expect(topic_status_update.user).to eq(Discourse.system_user) end - context 'when topic is closed manually' do - it 'should remove the schedule to auto-close the topic' do + context "when topic is closed manually" do + it "should remove the schedule to auto-close the topic" do topic_timer_id = staff_topic.public_topic_timer.id - staff_topic.update_status('closed', true, admin) + staff_topic.update_status("closed", true, admin) - expect(TopicTimer.with_deleted.find(topic_timer_id).deleted_at) - .to be_within_one_second_of(Time.zone.now) + expect( + TopicTimer.with_deleted.find(topic_timer_id).deleted_at, + ).to be_within_one_second_of(Time.zone.now) end end end - context 'when topic was created by a non-staff user' do + context "when topic was created by a non-staff user" do let(:regular_user) { Fabricate(:user) } let(:regular_user_topic) { Fabricate(:topic, user: regular_user, category: category) } - it 'should schedule the topic to auto-close' do + it "should schedule the topic to auto-close" do regular_user_topic topic_status_update = TopicTimer.last diff --git a/spec/integration/topic_thumbnail_spec.rb b/spec/integration/topic_thumbnail_spec.rb index 9a51836c5a..2674394097 100644 --- a/spec/integration/topic_thumbnail_spec.rb +++ b/spec/integration/topic_thumbnail_spec.rb @@ -9,11 +9,11 @@ RSpec.describe "Topic Thumbnails" do fab!(:topic) { Fabricate(:topic, image_upload_id: image.id) } fab!(:user) { Fabricate(:user) } - describe 'latest' do + describe "latest" do def get_topic Discourse.redis.del(topic.thumbnail_job_redis_key(Topic.thumbnail_sizes)) Discourse.redis.del(topic.thumbnail_job_redis_key([])) - get '/latest.json' + get "/latest.json" expect(response.status).to eq(200) response.parsed_body["topic_list"]["topics"][0] end @@ -27,11 +27,7 @@ RSpec.describe "Topic Thumbnails" do context "with a theme" do before do theme = Fabricate(:theme) - theme.theme_modifier_set.topic_thumbnail_sizes = [ - [10, 10], - [20, 20], - [30, 30] - ] + theme.theme_modifier_set.topic_thumbnail_sizes = [[10, 10], [20, 20], [30, 30]] theme.theme_modifier_set.save! theme.set_default! end @@ -39,15 +35,15 @@ RSpec.describe "Topic Thumbnails" do it "includes the theme specified resolutions" do topic_json = nil - expect do - topic_json = get_topic - end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(2) + expect do topic_json = get_topic end.to change { + Jobs::GenerateTopicThumbnails.jobs.size + }.by(2) - expect( - Jobs::GenerateTopicThumbnails.jobs.map { |j| j["args"][0]["extra_sizes"] } - ).to eq([ - nil, # Job for core/plugin sizes - [[10, 10], [20, 20], [30, 30]]] # Job for theme sizes + expect(Jobs::GenerateTopicThumbnails.jobs.map { |j| j["args"][0]["extra_sizes"] }).to eq( + [ + nil, # Job for core/plugin sizes + [[10, 10], [20, 20], [30, 30]], + ], # Job for theme sizes ) thumbnails = topic_json["thumbnails"] @@ -67,9 +63,9 @@ RSpec.describe "Topic Thumbnails" do Jobs::GenerateTopicThumbnails.new.execute(args.with_indifferent_access) # Request again - expect do - topic_json = get_topic - end.not_to change { Jobs::GenerateTopicThumbnails.jobs.size } + expect do topic_json = get_topic end.not_to change { + Jobs::GenerateTopicThumbnails.jobs.size + } thumbnails = topic_json["thumbnails"] @@ -82,7 +78,6 @@ RSpec.describe "Topic Thumbnails" do expect(thumbnails[1]["width"]).to eq(9) expect(thumbnails[1]["height"]).to eq(9) expect(thumbnails[1]["url"]).to include("/optimized/") - end end @@ -92,25 +87,23 @@ RSpec.describe "Topic Thumbnails" do plugin.register_topic_thumbnail_size [512, 512] end - after do - DiscoursePluginRegistry.reset! - end + after { DiscoursePluginRegistry.reset! } it "includes the theme specified resolutions" do topic_json = nil - expect do - topic_json = get_topic - end.to change { Jobs::GenerateTopicThumbnails.jobs.size }.by(1) + expect do topic_json = get_topic end.to change { + Jobs::GenerateTopicThumbnails.jobs.size + }.by(1) # Run the job args = Jobs::GenerateTopicThumbnails.jobs.last["args"].first Jobs::GenerateTopicThumbnails.new.execute(args.with_indifferent_access) # Request again - expect do - topic_json = get_topic - end.not_to change { Jobs::GenerateTopicThumbnails.jobs.size } + expect do topic_json = get_topic end.not_to change { + Jobs::GenerateTopicThumbnails.jobs.size + } thumbnails = topic_json["thumbnails"] diff --git a/spec/integration/twitter_omniauth_spec.rb b/spec/integration/twitter_omniauth_spec.rb index 4e4e54793e..f64960c6e9 100644 --- a/spec/integration/twitter_omniauth_spec.rb +++ b/spec/integration/twitter_omniauth_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -describe 'Twitter OAuth 1.0a' do +describe "Twitter OAuth 1.0a" do let(:access_token) { "twitter_access_token_448" } let(:consumer_key) { "abcdef11223344" } let(:consumer_secret) { "adddcccdddd99922" } @@ -14,14 +14,15 @@ describe 'Twitter OAuth 1.0a' do created_at: "Sat May 09 17:58:22 +0000 2009", default_profile: false, default_profile_image: false, - description: "I taught your phone that thing you like. The Mobile Partner Engineer @Twitter. ", + description: + "I taught your phone that thing you like. The Mobile Partner Engineer @Twitter. ", favourites_count: 588, follow_request_sent: nil, - followers_count: 10625, + followers_count: 10_625, following: nil, friends_count: 1181, geo_enabled: true, - id: 38895958, + id: 38_895_958, id_str: "38895958", is_translator: false, lang: "en", @@ -30,11 +31,14 @@ describe 'Twitter OAuth 1.0a' do name: "Sean Cook", notifications: nil, profile_background_color: "1A1B1F", - profile_background_image_url: "http://a0.twimg.com/profile_background_images/495742332/purty_wood.png", - profile_background_image_url_https: "https://si0.twimg.com/profile_background_images/495742332/purty_wood.png", + profile_background_image_url: + "http://a0.twimg.com/profile_background_images/495742332/purty_wood.png", + profile_background_image_url_https: + "https://si0.twimg.com/profile_background_images/495742332/purty_wood.png", profile_background_tile: true, profile_image_url: "http://a0.twimg.com/profile_images/1751506047/dead_sexy_normal.JPG", - profile_image_url_https: "https://si0.twimg.com/profile_images/1751506047/dead_sexy_normal.JPG", + profile_image_url_https: + "https://si0.twimg.com/profile_images/1751506047/dead_sexy_normal.JPG", profile_link_color: "2FC2EF", profile_sidebar_border_color: "181A1E", profile_sidebar_fill_color: "252429", @@ -46,19 +50,17 @@ describe 'Twitter OAuth 1.0a' do statuses_count: 2609, time_zone: "Pacific Time (US & Canada)", url: nil, - utc_offset: -28800, + utc_offset: -28_800, verified: true, - email: email + email: email, } - stub_request(:get, "https://api.twitter.com/1.1/account/verify_credentials.json") - .with( - query: { - include_email: true, - include_entities: false, - skip_status: true - } - ) - .to_return(status: 200, body: JSON.dump(body)) + stub_request(:get, "https://api.twitter.com/1.1/account/verify_credentials.json").with( + query: { + include_email: true, + include_entities: false, + skip_status: true, + }, + ).to_return(status: 200, body: JSON.dump(body)) end before do @@ -66,28 +68,28 @@ describe 'Twitter OAuth 1.0a' do SiteSetting.twitter_consumer_key = consumer_key SiteSetting.twitter_consumer_secret = consumer_secret - stub_request(:post, "https://api.twitter.com/oauth/request_token") - .to_return( - status: 200, - body: Rack::Utils.build_query( + stub_request(:post, "https://api.twitter.com/oauth/request_token").to_return( + status: 200, + body: + Rack::Utils.build_query( oauth_token: access_token, oauth_token_secret: oauth_token_secret, - oauth_callback_confirmed: true + oauth_callback_confirmed: true, ), - headers: { - "Content-Type" => "application/x-www-form-urlencoded" - } - ) - stub_request(:post, "https://api.twitter.com/oauth/access_token") - .to_return( - status: 200, - body: Rack::Utils.build_query( + headers: { + "Content-Type" => "application/x-www-form-urlencoded", + }, + ) + stub_request(:post, "https://api.twitter.com/oauth/access_token").to_return( + status: 200, + body: + Rack::Utils.build_query( oauth_token: access_token, oauth_token_secret: oauth_token_secret, user_id: "43423432422", - screen_name: "twitterapi" - ) - ) + screen_name: "twitterapi", + ), + ) end it "signs in the user if the API response from twitter includes an email (implies it's verified) and the email matches an existing user's" do diff --git a/spec/integration/watched_words_spec.rb b/spec/integration/watched_words_spec.rb index 2eef6fdc32..2304fb6bce 100644 --- a/spec/integration/watched_words_spec.rb +++ b/spec/integration/watched_words_spec.rb @@ -8,80 +8,121 @@ RSpec.describe WatchedWord do fab!(:topic) { Fabricate(:topic) } fab!(:first_post) { Fabricate(:post, topic: topic) } - let(:require_approval_word) { Fabricate(:watched_word, action: WatchedWord.actions[:require_approval]) } + let(:require_approval_word) do + Fabricate(:watched_word, action: WatchedWord.actions[:require_approval]) + end let(:flag_word) { Fabricate(:watched_word, action: WatchedWord.actions[:flag]) } let(:block_word) { Fabricate(:watched_word, action: WatchedWord.actions[:block]) } let(:another_block_word) { Fabricate(:watched_word, action: WatchedWord.actions[:block]) } - before_all do - WordWatcher.clear_cache! - end + before_all { WordWatcher.clear_cache! } - after do - WordWatcher.clear_cache! - end + after { WordWatcher.clear_cache! } context "with block" do def should_block_post(manager) expect { result = manager.perform expect(result).to_not be_success - expect(result.errors[:base]&.first).to eq(I18n.t('contains_blocked_word', word: block_word.word)) + expect(result.errors[:base]&.first).to eq( + I18n.t("contains_blocked_word", word: block_word.word), + ) }.to_not change { Post.count } end it "escapes the blocked word in error message" do block_word = Fabricate(:watched_word, action: WatchedWord.actions[:block], word: "") - manager = NewPostManager.new(tl2_user, raw: "Want some #{block_word.word} for cheap?", topic_id: topic.id) + manager = + NewPostManager.new( + tl2_user, + raw: "Want some #{block_word.word} for cheap?", + topic_id: topic.id, + ) result = manager.perform expect(result).to_not be_success - expect(result.errors[:base]&.first).to eq(I18n.t('contains_blocked_word', word: "<a>")) + expect(result.errors[:base]&.first).to eq(I18n.t("contains_blocked_word", word: "<a>")) end it "should prevent the post from being created" do - manager = NewPostManager.new(tl2_user, raw: "Want some #{block_word.word} for cheap?", topic_id: topic.id) + manager = + NewPostManager.new( + tl2_user, + raw: "Want some #{block_word.word} for cheap?", + topic_id: topic.id, + ) should_block_post(manager) end it "look at title too" do - manager = NewPostManager.new(tl2_user, title: "We sell #{block_word.word} online", raw: "Want some poutine for cheap?", topic_id: topic.id) + manager = + NewPostManager.new( + tl2_user, + title: "We sell #{block_word.word} online", + raw: "Want some poutine for cheap?", + topic_id: topic.id, + ) should_block_post(manager) end it "should block the post from admin" do - manager = NewPostManager.new(admin, raw: "Want some #{block_word.word} for cheap?", topic_id: topic.id) + manager = + NewPostManager.new( + admin, + raw: "Want some #{block_word.word} for cheap?", + topic_id: topic.id, + ) should_block_post(manager) end it "should block the post from moderator" do - manager = NewPostManager.new(moderator, raw: "Want some #{block_word.word} for cheap?", topic_id: topic.id) + manager = + NewPostManager.new( + moderator, + raw: "Want some #{block_word.word} for cheap?", + topic_id: topic.id, + ) should_block_post(manager) end it "should block the post if it contains multiple blocked words" do - manager = NewPostManager.new(moderator, raw: "Want some #{block_word.word} #{another_block_word.word} for cheap?", topic_id: topic.id) + manager = + NewPostManager.new( + moderator, + raw: "Want some #{block_word.word} #{another_block_word.word} for cheap?", + topic_id: topic.id, + ) expect { result = manager.perform expect(result).to_not be_success - expect(result.errors[:base]&.first).to eq(I18n.t('contains_blocked_words', words: [block_word.word, another_block_word.word].sort.join(', '))) + expect(result.errors[:base]&.first).to eq( + I18n.t( + "contains_blocked_words", + words: [block_word.word, another_block_word.word].sort.join(", "), + ), + ) }.to_not change { Post.count } end it "should block in a private message too" do - manager = NewPostManager.new( - tl2_user, - raw: "Want some #{block_word.word} for cheap?", - title: 'this is a new title', - archetype: Archetype.private_message, - target_usernames: Fabricate(:user, trust_level: TrustLevel[2]).username - ) + manager = + NewPostManager.new( + tl2_user, + raw: "Want some #{block_word.word} for cheap?", + title: "this is a new title", + archetype: Archetype.private_message, + target_usernames: Fabricate(:user, trust_level: TrustLevel[2]).username, + ) should_block_post(manager) end it "blocks on revisions" do post = Fabricate(:post, topic: Fabricate(:topic, user: tl2_user), user: tl2_user) expect { - PostRevisor.new(post).revise!(post.user, { raw: "Want some #{block_word.word} for cheap?" }, revised_at: post.updated_at + 10.seconds) + PostRevisor.new(post).revise!( + post.user, + { raw: "Want some #{block_word.word} for cheap?" }, + revised_at: post.updated_at + 10.seconds, + ) expect(post.errors).to be_present post.reload }.to_not change { post.raw } @@ -90,27 +131,48 @@ RSpec.describe WatchedWord do context "with require_approval" do it "should queue the post for approval" do - manager = NewPostManager.new(tl2_user, raw: "My dog's name is #{require_approval_word.word}.", topic_id: topic.id) + manager = + NewPostManager.new( + tl2_user, + raw: "My dog's name is #{require_approval_word.word}.", + topic_id: topic.id, + ) result = manager.perform expect(result.action).to eq(:enqueued) expect(result.reason).to eq(:watched_word) end it "looks at title too" do - manager = NewPostManager.new(tl2_user, title: "You won't believe these #{require_approval_word.word} dog names!", raw: "My dog's name is Porkins.", topic_id: topic.id) + manager = + NewPostManager.new( + tl2_user, + title: "You won't believe these #{require_approval_word.word} dog names!", + raw: "My dog's name is Porkins.", + topic_id: topic.id, + ) result = manager.perform expect(result.action).to eq(:enqueued) end it "should not queue posts from admin" do - manager = NewPostManager.new(admin, raw: "My dog's name is #{require_approval_word.word}.", topic_id: topic.id) + manager = + NewPostManager.new( + admin, + raw: "My dog's name is #{require_approval_word.word}.", + topic_id: topic.id, + ) result = manager.perform expect(result).to be_success expect(result.action).to eq(:create_post) end it "should not queue posts from moderator" do - manager = NewPostManager.new(moderator, raw: "My dog's name is #{require_approval_word.word}.", topic_id: topic.id) + manager = + NewPostManager.new( + moderator, + raw: "My dog's name is #{require_approval_word.word}.", + topic_id: topic.id, + ) result = manager.perform expect(result).to be_success expect(result.action).to eq(:create_post) @@ -118,13 +180,14 @@ RSpec.describe WatchedWord do it "doesn't need approval in a private message" do Group.refresh_automatic_groups! - manager = NewPostManager.new( - tl2_user, - raw: "Want some #{require_approval_word.word} for cheap?", - title: 'this is a new title', - archetype: Archetype.private_message, - target_usernames: Fabricate(:user, trust_level: TrustLevel[2]).username - ) + manager = + NewPostManager.new( + tl2_user, + raw: "Want some #{require_approval_word.word} for cheap?", + title: "this is a new title", + archetype: Archetype.private_message, + target_usernames: Fabricate(:user, trust_level: TrustLevel[2]).username, + ) result = manager.perform expect(result).to be_success expect(result.action).to eq(:create_post) @@ -134,74 +197,122 @@ RSpec.describe WatchedWord do context "with flag" do def should_flag_post(author, raw, topic) post = Fabricate(:post, raw: raw, topic: topic, user: author) - expect { - Jobs::ProcessPost.new.execute(post_id: post.id) - }.to change { PostAction.count }.by(1) - expect(PostAction.where(post_id: post.id, post_action_type_id: PostActionType.types[:inappropriate]).exists?).to eq(true) + expect { Jobs::ProcessPost.new.execute(post_id: post.id) }.to change { PostAction.count }.by( + 1, + ) + expect( + PostAction.where( + post_id: post.id, + post_action_type_id: PostActionType.types[:inappropriate], + ).exists?, + ).to eq(true) end def should_not_flag_post(author, raw, topic) post = Fabricate(:post, raw: raw, topic: topic, user: author) - expect { - Jobs::ProcessPost.new.execute(post_id: post.id) - }.to_not change { PostAction.count } + expect { Jobs::ProcessPost.new.execute(post_id: post.id) }.to_not change { PostAction.count } end it "should flag the post as inappropriate" do topic = Fabricate(:topic, user: tl2_user) post = Fabricate(:post, raw: "I said.... #{flag_word.word}", topic: topic, user: tl2_user) Jobs::ProcessPost.new.execute(post_id: post.id) - expect(PostAction.where(post_id: post.id, post_action_type_id: PostActionType.types[:inappropriate]).exists?).to eq(true) + expect( + PostAction.where( + post_id: post.id, + post_action_type_id: PostActionType.types[:inappropriate], + ).exists?, + ).to eq(true) reviewable = ReviewableFlaggedPost.where(target: post) expect(reviewable).to be_present - expect(ReviewableScore.where(reviewable: reviewable, reason: 'watched_word')).to be_present + expect(ReviewableScore.where(reviewable: reviewable, reason: "watched_word")).to be_present end it "should look at the title too" do - should_flag_post(tl2_user, "I thought the movie was not bad actually.", Fabricate(:topic, user: tl2_user, title: "Read my #{flag_word.word} review!")) + should_flag_post( + tl2_user, + "I thought the movie was not bad actually.", + Fabricate(:topic, user: tl2_user, title: "Read my #{flag_word.word} review!"), + ) end it "shouldn't flag posts by admin" do - should_not_flag_post(admin, "I thought the #{flag_word.word} was bad.", Fabricate(:topic, user: admin)) + should_not_flag_post( + admin, + "I thought the #{flag_word.word} was bad.", + Fabricate(:topic, user: admin), + ) end it "shouldn't flag posts by moderator" do - should_not_flag_post(moderator, "I thought the #{flag_word.word} was bad.", Fabricate(:topic, user: moderator)) + should_not_flag_post( + moderator, + "I thought the #{flag_word.word} was bad.", + Fabricate(:topic, user: moderator), + ) end it "is compatible with flag_sockpuppets" do SiteSetting.flag_sockpuppets = true - ip_address = '182.189.119.174' + ip_address = "182.189.119.174" user1 = Fabricate(:user, ip_address: ip_address, created_at: 2.days.ago) user2 = Fabricate(:user, ip_address: ip_address) first = create_post(user: user1, created_at: 2.days.ago) - sockpuppet_post = create_post(user: user2, topic: first.topic, raw: "I thought the #{flag_word.word} was bad.") + sockpuppet_post = + create_post( + user: user2, + topic: first.topic, + raw: "I thought the #{flag_word.word} was bad.", + ) expect(PostAction.where(post_id: sockpuppet_post.id).count).to eq(1) end it "flags in private message too" do - post = Fabricate(:private_message_post, raw: "Want some #{flag_word.word} for cheap?", user: tl2_user) - expect { - Jobs::ProcessPost.new.execute(post_id: post.id) - }.to change { PostAction.count }.by(1) - expect(PostAction.where(post_id: post.id, post_action_type_id: PostActionType.types[:inappropriate]).exists?).to eq(true) + post = + Fabricate( + :private_message_post, + raw: "Want some #{flag_word.word} for cheap?", + user: tl2_user, + ) + expect { Jobs::ProcessPost.new.execute(post_id: post.id) }.to change { PostAction.count }.by( + 1, + ) + expect( + PostAction.where( + post_id: post.id, + post_action_type_id: PostActionType.types[:inappropriate], + ).exists?, + ).to eq(true) end it "flags on revisions" do Jobs.run_immediately! post = Fabricate(:post, topic: Fabricate(:topic, user: tl2_user), user: tl2_user) expect { - PostRevisor.new(post).revise!(post.user, { raw: "Want some #{flag_word.word} for cheap?" }, revised_at: post.updated_at + 10.seconds) + PostRevisor.new(post).revise!( + post.user, + { raw: "Want some #{flag_word.word} for cheap?" }, + revised_at: post.updated_at + 10.seconds, + ) }.to change { PostAction.count }.by(1) - expect(PostAction.where(post_id: post.id, post_action_type_id: PostActionType.types[:inappropriate]).exists?).to eq(true) + expect( + PostAction.where( + post_id: post.id, + post_action_type_id: PostActionType.types[:inappropriate], + ).exists?, + ).to eq(true) end it "should not flag on rebake" do - post = Fabricate(:post, topic: Fabricate(:topic, user: tl2_user), user: tl2_user, raw: "I have coupon codes. Message me.") + post = + Fabricate( + :post, + topic: Fabricate(:topic, user: tl2_user), + user: tl2_user, + raw: "I have coupon codes. Message me.", + ) Fabricate(:watched_word, action: WatchedWord.actions[:flag], word: "coupon") - expect { - post.rebake! - }.to_not change { PostAction.count } + expect { post.rebake! }.to_not change { PostAction.count } end end end diff --git a/spec/integrity/coding_style_spec.rb b/spec/integrity/coding_style_spec.rb index b51e6da61f..ebd3ade729 100644 --- a/spec/integrity/coding_style_spec.rb +++ b/spec/integrity/coding_style_spec.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -def list_files(base_dir, pattern = '*') +def list_files(base_dir, pattern = "*") Dir[File.join("#{base_dir}", pattern)] end def list_js_files(base_dir) - list_files(base_dir, '**/*.es6') + list_files(base_dir, "**/*.es6") end def grep_files(files, regex) @@ -17,10 +17,10 @@ def grep_file(file, regex) lines.count > 0 ? file : nil end -RSpec.describe 'Coding style' do - describe 'Javascript' do +RSpec.describe "Coding style" do + describe "Javascript" do it 'prevents this.get("foo") pattern' do - js_files = list_js_files('app/assets/javascripts') + js_files = list_js_files("app/assets/javascripts") offenses = grep_files(js_files, /this\.get\("\w+"\)/) expect(offenses).to be_empty, <<~TEXT @@ -33,7 +33,7 @@ RSpec.describe 'Coding style' do end end - describe 'Post Migrations' do + describe "Post Migrations" do def check_offenses(files, method_name, constant_name) method_name_regex = /#{Regexp.escape(method_name)}/ constant_name_regex = /#{Regexp.escape(constant_name)}/ @@ -57,8 +57,8 @@ RSpec.describe 'Coding style' do contains_method_name ? contains_constant_name : true end - it 'ensures dropped tables and columns are stored in constants' do - migration_files = list_files('db/post_migrate', '**/*.rb') + it "ensures dropped tables and columns are stored in constants" do + migration_files = list_files("db/post_migrate", "**/*.rb") check_offenses(migration_files, "ColumnDropper.execute_drop", "DROPPED_COLUMNS") check_offenses(migration_files, "TableDropper.execute_drop", "DROPPED_TABLES") diff --git a/spec/integrity/common_mark_spec.rb b/spec/integrity/common_mark_spec.rb index 01cc77683f..c89f9ff0e8 100644 --- a/spec/integrity/common_mark_spec.rb +++ b/spec/integrity/common_mark_spec.rb @@ -1,76 +1,72 @@ # frozen_string_literal: true RSpec.describe "CommonMark" do - it 'passes spec' do - + it "passes spec" do SiteSetting.traditional_markdown_linebreaks = true SiteSetting.enable_markdown_typographer = false - SiteSetting.highlighted_languages = 'ruby|aa' + SiteSetting.highlighted_languages = "ruby|aa" html, state, md = nil failed = 0 - File.readlines(Rails.root + 'spec/fixtures/md/spec.txt').each do |line| - if line == "```````````````````````````````` example\n" - state = :example - next - end - - if line == "````````````````````````````````\n" - md.gsub!('→', "\t") - html ||= String.new - html.gsub!('→', "\t") - html.strip! - - # normalize brs - html.gsub!('
    ', '
    ') - html.gsub!('
    ', '
    ') - html.gsub!(/]+) \/>/, "") - - SiteSetting.enable_markdown_linkify = false - cooked = PrettyText.markdown(md, sanitize: false) - cooked.strip! - cooked.gsub!(" class=\"lang-auto\"", '') - cooked.gsub!(/(.*)<\/span>/, "\\1") - cooked.gsub!(/
    <\/a>/, "") - # we support data-attributes which is not in the spec - cooked.gsub!("
    ", '
    ')
    -        # we don't care about this
    -        cooked.gsub!("
    \n
    ", "
    ") - html.gsub!("
    \n
    ", "
    ") - html.gsub!("language-ruby", "lang-ruby") - html.gsub!("language-aa", "lang-aa") - # strip out unsupported languages - html.gsub!(/ class="language-[;f].*"/, "") - - unless cooked == html - failed += 1 - puts "FAILED SPEC" - puts "Expected: " - puts html - puts "Got: " - puts cooked - puts "Markdown: " - puts md - puts + File + .readlines(Rails.root + "spec/fixtures/md/spec.txt") + .each do |line| + if line == "```````````````````````````````` example\n" + state = :example + next end - html, state, md = nil - next - end - if state == :example && line == ".\n" - state = :html - next - end + if line == "````````````````````````````````\n" + md.gsub!("→", "\t") + html ||= String.new + html.gsub!("→", "\t") + html.strip! - if state == :example - md = (md || String.new) << line - end + # normalize brs + html.gsub!("
    ", "
    ") + html.gsub!("
    ", "
    ") + html.gsub!(%r{]+) />}, "") - if state == :html - html = (html || String.new) << line - end + SiteSetting.enable_markdown_linkify = false + cooked = PrettyText.markdown(md, sanitize: false) + cooked.strip! + cooked.gsub!(" class=\"lang-auto\"", "") + cooked.gsub!(%r{(.*)}, "\\1") + cooked.gsub!(%r{
    }, "") + # we support data-attributes which is not in the spec + cooked.gsub!("
    ", "
    ")
    +          # we don't care about this
    +          cooked.gsub!("
    \n
    ", "
    ") + html.gsub!("
    \n
    ", "
    ") + html.gsub!("language-ruby", "lang-ruby") + html.gsub!("language-aa", "lang-aa") + # strip out unsupported languages + html.gsub!(%r{ class="language-[;f].*"}, "") - end + unless cooked == html + failed += 1 + puts "FAILED SPEC" + puts "Expected: " + puts html + puts "Got: " + puts cooked + puts "Markdown: " + puts md + puts + end + html, state, md = nil + next + end + + if state == :example && line == ".\n" + state = :html + next + end + + md = (md || String.new) << line if state == :example + + html = (html || String.new) << line if state == :html + end expect(failed).to eq(0) end diff --git a/spec/integrity/i18n_spec.rb b/spec/integrity/i18n_spec.rb index a78811a395..edf7bea714 100644 --- a/spec/integrity/i18n_spec.rb +++ b/spec/integrity/i18n_spec.rb @@ -37,10 +37,12 @@ RSpec.describe "i18n integrity checks" do end it "has an i18n key for each Badge description" do - Badge.where(system: true).each do |b| - expect(b.long_description).to be_present - expect(b.description).to be_present - end + Badge + .where(system: true) + .each do |b| + expect(b.long_description).to be_present + expect(b.description).to be_present + end end Dir["#{Rails.root}/config/locales/{client,server}.*.yml"].each do |path| @@ -116,20 +118,14 @@ RSpec.describe "fallbacks" do it "finds the fallback translation" do I18n.backend.store_translations(:en, test: "en test") - I18n.with_locale("pl_PL") do - expect(I18n.t("test")).to eq("en test") - end + I18n.with_locale("pl_PL") { expect(I18n.t("test")).to eq("en test") } end context "when in a multi-threaded environment" do it "finds the fallback translation" do I18n.backend.store_translations(:en, test: "en test") - thread = Thread.new do - I18n.with_locale("pl_PL") do - expect(I18n.t("test")).to eq("en test") - end - end + thread = Thread.new { I18n.with_locale("pl_PL") { expect(I18n.t("test")).to eq("en test") } } begin thread.join diff --git a/spec/integrity/js_constants_spec.rb b/spec/integrity/js_constants_spec.rb index fe13669d54..559f2c4533 100644 --- a/spec/integrity/js_constants_spec.rb +++ b/spec/integrity/js_constants_spec.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true RSpec.describe "constants match ruby" do - let(:ctx) { MiniRacer::Context.new } def parse(file) # mini racer doesn't handle JS modules so we'll do this hack source = File.read("#{Rails.root}/app/assets/javascripts/#{file}") - source.gsub!(/^export */, '') + source.gsub!(/^export */, "") ctx.eval(source) end @@ -16,12 +15,9 @@ RSpec.describe "constants match ruby" do parse("pretty-text/addon/emoji/version.js") priorities = ctx.eval("SEARCH_PRIORITIES") - Searchable::PRIORITIES.each do |key, value| - expect(priorities[key.to_s]).to eq(value) - end + Searchable::PRIORITIES.each { |key, value| expect(priorities[key.to_s]).to eq(value) } expect(ctx.eval("SEARCH_PHRASE_REGEXP")).to eq(Search::PHRASE_MATCH_REGEXP_PATTERN) expect(ctx.eval("IMAGE_VERSION")).to eq(Emoji::EMOJI_VERSION) end - end diff --git a/spec/integrity/oj_spec.rb b/spec/integrity/oj_spec.rb index 799f7578f2..ede78a648a 100644 --- a/spec/integrity/oj_spec.rb +++ b/spec/integrity/oj_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -RSpec.describe 'Oj' do +RSpec.describe "Oj" do it "is enabled" do classes = Set.new tracer = TracePoint.new(:c_call) { |tp| classes << tp.defined_class } @@ -11,7 +11,7 @@ RSpec.describe 'Oj' do it "escapes HTML entities the same as ActiveSupport" do expect("hello".to_json).to eq("\"\\u003cb\\u003ehello\\u003c/b\\u003e\"") - expect('"hello world"'.to_json). to eq('"\"hello world\""') + expect('"hello world"'.to_json).to eq('"\"hello world\""') expect("\u2028\u2029><&".to_json).to eq('"\u2028\u2029\u003e\u003c\u0026"') end end diff --git a/spec/integrity/onceoff_integrity_spec.rb b/spec/integrity/onceoff_integrity_spec.rb index e913d2c816..2952cfa1be 100644 --- a/spec/integrity/onceoff_integrity_spec.rb +++ b/spec/integrity/onceoff_integrity_spec.rb @@ -3,11 +3,12 @@ RSpec.describe ::Jobs::Onceoff do it "can run all once off jobs without errors" do # Load all once offs - Dir[Rails.root + 'app/jobs/onceoff/*.rb'].each do |f| - require_relative '../../app/jobs/onceoff/' + File.basename(f) + Dir[Rails.root + "app/jobs/onceoff/*.rb"].each do |f| + require_relative "../../app/jobs/onceoff/" + File.basename(f) end - ObjectSpace.each_object(Class) + ObjectSpace + .each_object(Class) .select { |klass| klass.superclass == ::Jobs::Onceoff } .each { |job| job.new.execute_onceoff(nil) } end diff --git a/spec/integrity/site_setting_spec.rb b/spec/integrity/site_setting_spec.rb index 8779af4576..412902d874 100644 --- a/spec/integrity/site_setting_spec.rb +++ b/spec/integrity/site_setting_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true RSpec.describe "site setting integrity checks" do - let(:site_setting_file) { File.join(Rails.root, 'config', 'site_settings.yml') } + let(:site_setting_file) { File.join(Rails.root, "config", "site_settings.yml") } let(:yaml) { YAML.load_file(site_setting_file) } - %w(hidden client).each do |property| + %w[hidden client].each do |property| it "set #{property} value as true or not set" do yaml.each_value do |category| category.each_value do |setting| @@ -24,16 +24,16 @@ RSpec.describe "site setting integrity checks" do yaml.each_value do |category| category.each do |setting_name, setting| next unless setting.is_a?(Hash) - if setting['locale_default'] - setting['locale_default'].each_pair do |k, v| + if setting["locale_default"] + setting["locale_default"].each_pair do |k, v| expect(LocaleSiteSetting.valid_value?(k.to_s)).to be_truthy, - "'#{k}' is not a valid locale_default key for '#{setting_name}' site setting" + "'#{k}' is not a valid locale_default key for '#{setting_name}' site setting" - case setting['default'] + case setting["default"] when TrueClass, FalseClass expect(v.class == TrueClass || v.class == FalseClass).to be_truthy else - expect(v).to be_a_kind_of(setting['default'].class) + expect(v).to be_a_kind_of(setting["default"].class) end end end diff --git a/spec/jobs/about_stats_spec.rb b/spec/jobs/about_stats_spec.rb index 3a2e5d6aa9..fa6ecaa016 100644 --- a/spec/jobs/about_stats_spec.rb +++ b/spec/jobs/about_stats_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe Jobs::AboutStats do - it 'caches the stats' do + it "caches the stats" do begin stats = About.fetch_stats.to_json cache_key = About.stats_cache_key diff --git a/spec/jobs/activation_reminder_emails_spec.rb b/spec/jobs/activation_reminder_emails_spec.rb index d7956d3dcd..e70cc9abaa 100644 --- a/spec/jobs/activation_reminder_emails_spec.rb +++ b/spec/jobs/activation_reminder_emails_spec.rb @@ -6,33 +6,33 @@ RSpec.describe Jobs::ActivationReminderEmails do # should be between 2 and 3 days let(:created_at) { 50.hours.ago } - it 'should email inactive users' do + it "should email inactive users" do user = Fabricate(:user, active: false, created_at: created_at) - expect { described_class.new.execute({}) } - .to change { ActionMailer::Base.deliveries.size }.by(1) - .and change { user.email_tokens.count }.by(1) + expect { described_class.new.execute({}) }.to change { ActionMailer::Base.deliveries.size }.by( + 1, + ).and change { user.email_tokens.count }.by(1) - expect(user.custom_fields['activation_reminder']).to eq("t") + expect(user.custom_fields["activation_reminder"]).to eq("t") expect { described_class.new.execute({}) }.not_to change { ActionMailer::Base.deliveries.size } user.activate - expect(user.reload.custom_fields['activation_reminder']).to eq(nil) + expect(user.reload.custom_fields["activation_reminder"]).to eq(nil) end - it 'should not email active users' do + it "should not email active users" do user = Fabricate(:user, active: true, created_at: created_at) - expect { described_class.new.execute({}) } - .to not_change { ActionMailer::Base.deliveries.size } - .and not_change { user.email_tokens.count } + expect { described_class.new.execute({}) }.to not_change { + ActionMailer::Base.deliveries.size + }.and not_change { user.email_tokens.count } end - it 'should not email staged users' do + it "should not email staged users" do user = Fabricate(:user, active: false, staged: true, created_at: created_at) - expect { described_class.new.execute({}) } - .to not_change { ActionMailer::Base.deliveries.size } - .and not_change { user.email_tokens.count } + expect { described_class.new.execute({}) }.to not_change { + ActionMailer::Base.deliveries.size + }.and not_change { user.email_tokens.count } end end diff --git a/spec/jobs/auto_expire_user_api_keys_spec.rb b/spec/jobs/auto_expire_user_api_keys_spec.rb index abed8ceba6..5c6ce1c8a6 100644 --- a/spec/jobs/auto_expire_user_api_keys_spec.rb +++ b/spec/jobs/auto_expire_user_api_keys_spec.rb @@ -4,12 +4,10 @@ RSpec.describe Jobs::AutoExpireUserApiKeys do fab!(:key1) { Fabricate(:readonly_user_api_key) } fab!(:key2) { Fabricate(:readonly_user_api_key) } - context 'when user api key is unused in last 1 days' do - before do - SiteSetting.expire_user_api_keys_days = 1 - end + context "when user api key is unused in last 1 days" do + before { SiteSetting.expire_user_api_keys_days = 1 } - it 'should revoke the key' do + it "should revoke the key" do freeze_time key1.update!(last_used_at: 2.days.ago) diff --git a/spec/jobs/auto_queue_handler_spec.rb b/spec/jobs/auto_queue_handler_spec.rb index 6fb6c2a900..f4d1ada3af 100644 --- a/spec/jobs/auto_queue_handler_spec.rb +++ b/spec/jobs/auto_queue_handler_spec.rb @@ -9,15 +9,15 @@ RSpec.describe Jobs::AutoQueueHandler do Fabricate(:user), Fabricate(:post), PostActionType.types[:spam], - message: 'this is the initial message' + message: "this is the initial message", ).perform end fab!(:post_action) { spam_result.post_action } - fab!(:old) { + fab!(:old) do spam_result.reviewable.update_column(:created_at, 61.days.ago) spam_result.reviewable - } + end fab!(:not_old) { Fabricate(:reviewable_flagged_post, created_at: 59.days.ago) } diff --git a/spec/jobs/automatic_group_membership_spec.rb b/spec/jobs/automatic_group_membership_spec.rb index 76efc6cd8a..ce130987c0 100644 --- a/spec/jobs/automatic_group_membership_spec.rb +++ b/spec/jobs/automatic_group_membership_spec.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true RSpec.describe Jobs::AutomaticGroupMembership do - it "raises an error when the group id is missing" do - expect { Jobs::AutomaticGroupMembership.new.execute({}) }.to raise_error(Discourse::InvalidParameters) + expect { Jobs::AutomaticGroupMembership.new.execute({}) }.to raise_error( + Discourse::InvalidParameters, + ) end it "updates the membership" do @@ -14,19 +15,28 @@ RSpec.describe Jobs::AutomaticGroupMembership do user4 = Fabricate(:user, email: "yes@wat.com") EmailToken.confirm(Fabricate(:email_token, user: user4).token) user5 = Fabricate(:user, email: "sso@wat.com") - user5.create_single_sign_on_record(external_id: 123, external_email: "hacker@wat.com", last_payload: "") + user5.create_single_sign_on_record( + external_id: 123, + external_email: "hacker@wat.com", + last_payload: "", + ) user6 = Fabricate(:user, email: "sso2@wat.com") - user6.create_single_sign_on_record(external_id: 456, external_email: "sso2@wat.com", last_payload: "") + user6.create_single_sign_on_record( + external_id: 456, + external_email: "sso2@wat.com", + last_payload: "", + ) group = Fabricate(:group, automatic_membership_email_domains: "wat.com") automatic = nil called = false - blk = Proc.new do |_u, _g, options| - automatic = options[:automatic] - called = true - end + blk = + Proc.new do |_u, _g, options| + automatic = options[:automatic] + called = true + end begin DiscourseEvent.on(:user_added_to_group, &blk) @@ -47,5 +57,4 @@ RSpec.describe Jobs::AutomaticGroupMembership do expect(group.users.include?(user6)).to eq(true) expect(group.user_count).to eq(2) end - end diff --git a/spec/jobs/bookmark_reminder_notifications_spec.rb b/spec/jobs/bookmark_reminder_notifications_spec.rb index 6a68eb439e..22a9a17f83 100644 --- a/spec/jobs/bookmark_reminder_notifications_spec.rb +++ b/spec/jobs/bookmark_reminder_notifications_spec.rb @@ -8,13 +8,7 @@ RSpec.describe Jobs::BookmarkReminderNotifications do let(:bookmark1) { Fabricate(:bookmark, user: user) } let(:bookmark2) { Fabricate(:bookmark, user: user) } let(:bookmark3) { Fabricate(:bookmark, user: user) } - let!(:bookmarks) do - [ - bookmark1, - bookmark2, - bookmark3 - ] - end + let!(:bookmarks) { [bookmark1, bookmark2, bookmark3] } before do # this is done to avoid model validations on Bookmark @@ -66,10 +60,12 @@ RSpec.describe Jobs::BookmarkReminderNotifications do end end - it 'will not send notification when topic is not available' do + it "will not send notification when topic is not available" do bookmark1.bookmarkable.topic.destroy bookmark2.bookmarkable.topic.destroy bookmark3.bookmarkable.topic.destroy - expect { subject.execute }.not_to change { Notification.where(notification_type: Notification.types[:bookmark_reminder]).count } + expect { subject.execute }.not_to change { + Notification.where(notification_type: Notification.types[:bookmark_reminder]).count + } end end diff --git a/spec/jobs/bulk_grant_trust_level_spec.rb b/spec/jobs/bulk_grant_trust_level_spec.rb index bfda953eba..b2963069da 100644 --- a/spec/jobs/bulk_grant_trust_level_spec.rb +++ b/spec/jobs/bulk_grant_trust_level_spec.rb @@ -1,13 +1,16 @@ # frozen_string_literal: true RSpec.describe Jobs::BulkGrantTrustLevel do - it "raises an error when trust_level is missing" do - expect { Jobs::BulkGrantTrustLevel.new.execute(user_ids: [1, 2]) }.to raise_error(Discourse::InvalidParameters) + expect { Jobs::BulkGrantTrustLevel.new.execute(user_ids: [1, 2]) }.to raise_error( + Discourse::InvalidParameters, + ) end it "raises an error when user_ids are missing" do - expect { Jobs::BulkGrantTrustLevel.new.execute(trust_level: 0) }.to raise_error(Discourse::InvalidParameters) + expect { Jobs::BulkGrantTrustLevel.new.execute(trust_level: 0) }.to raise_error( + Discourse::InvalidParameters, + ) end it "updates the trust_level" do diff --git a/spec/jobs/bulk_invite_spec.rb b/spec/jobs/bulk_invite_spec.rb index 53d38d7888..3a8d370ab7 100644 --- a/spec/jobs/bulk_invite_spec.rb +++ b/spec/jobs/bulk_invite_spec.rb @@ -1,34 +1,43 @@ # frozen_string_literal: true RSpec.describe Jobs::BulkInvite do - describe '#execute' do + describe "#execute" do fab!(:user) { Fabricate(:user) } fab!(:admin) { Fabricate(:admin) } - fab!(:group1) { Fabricate(:group, name: 'group1') } - fab!(:group2) { Fabricate(:group, name: 'group2') } + fab!(:group1) { Fabricate(:group, name: "group1") } + fab!(:group2) { Fabricate(:group, name: "group2") } fab!(:topic) { Fabricate(:topic) } let(:staged_user) { Fabricate(:user, staged: true, active: false) } - let(:email) { 'test@discourse.org' } - let(:invites) { [{ email: user.email }, { email: staged_user.email }, { email: 'test2@discourse.org' }, { email: 'test@discourse.org', groups: 'GROUP1;group2', topic_id: topic.id }, { email: 'invalid' }] } - - it 'raises an error when the invites array is missing' do - expect { Jobs::BulkInvite.new.execute(current_user_id: user.id) } - .to raise_error(Discourse::InvalidParameters, /invites/) + let(:email) { "test@discourse.org" } + let(:invites) do + [ + { email: user.email }, + { email: staged_user.email }, + { email: "test2@discourse.org" }, + { email: "test@discourse.org", groups: "GROUP1;group2", topic_id: topic.id }, + { email: "invalid" }, + ] end - it 'raises an error when current_user_id is not valid' do - expect { Jobs::BulkInvite.new.execute(invites: invites) } - .to raise_error(Discourse::InvalidParameters, /current_user_id/) - end - - it 'creates the right invites' do - described_class.new.execute( - current_user_id: admin.id, - invites: invites + it "raises an error when the invites array is missing" do + expect { Jobs::BulkInvite.new.execute(current_user_id: user.id) }.to raise_error( + Discourse::InvalidParameters, + /invites/, ) + end + + it "raises an error when current_user_id is not valid" do + expect { Jobs::BulkInvite.new.execute(invites: invites) }.to raise_error( + Discourse::InvalidParameters, + /current_user_id/, + ) + end + + it "creates the right invites" do + described_class.new.execute(current_user_id: admin.id, invites: invites) expect(Invite.exists?(email: staged_user.email)).to eq(true) - expect(Invite.exists?(email: 'test2@discourse.org')).to eq(true) + expect(Invite.exists?(email: "test2@discourse.org")).to eq(true) invite = Invite.last expect(invite.email).to eq(email) @@ -42,13 +51,10 @@ RSpec.describe Jobs::BulkInvite do expect(post.raw).to include("1 error") end - it 'does not create invited groups for automatic groups' do + it "does not create invited groups for automatic groups" do group2.update!(automatic: true) - described_class.new.execute( - current_user_id: admin.id, - invites: invites - ) + described_class.new.execute(current_user_id: admin.id, invites: invites) invite = Invite.last expect(invite.email).to eq(email) @@ -58,37 +64,31 @@ RSpec.describe Jobs::BulkInvite do expect(post.raw).to include("1 warning") end - it 'does not create invited groups record if the user can not manage the group' do + it "does not create invited groups record if the user can not manage the group" do group1.add_owner(user) - described_class.new.execute( - current_user_id: user.id, - invites: invites - ) + described_class.new.execute(current_user_id: user.id, invites: invites) invite = Invite.last expect(invite.email).to eq(email) expect(invite.invited_groups.pluck(:group_id)).to contain_exactly(group1.id) end - it 'adds existing users to valid groups' do - existing_user = Fabricate(:user, email: 'test@discourse.org') + it "adds existing users to valid groups" do + existing_user = Fabricate(:user, email: "test@discourse.org") group2.update!(automatic: true) expect do - described_class.new.execute( - current_user_id: admin.id, - invites: invites - ) + described_class.new.execute(current_user_id: admin.id, invites: invites) end.to change { Invite.count }.by(2) expect(Invite.exists?(email: staged_user.email)).to eq(true) - expect(Invite.exists?(email: 'test2@discourse.org')).to eq(true) + expect(Invite.exists?(email: "test2@discourse.org")).to eq(true) expect(existing_user.reload.groups).to eq([group1]) end - it 'can create staged users and prepopulate user fields' do + it "can create staged users and prepopulate user fields" do user_field = Fabricate(:user_field, name: "Location") user_field_color = Fabricate(:user_field, field_type: "dropdown", name: "Color") user_field_color.user_field_options.create!(value: "Red") @@ -98,40 +98,33 @@ RSpec.describe Jobs::BulkInvite do described_class.new.execute( current_user_id: admin.id, invites: [ - { email: 'test@discourse.org' }, # new user without user fields - { email: user.email, location: 'value 1', color: 'blue' }, # existing user with user fields - { email: staged_user.email, location: 'value 2', color: 'redd' }, # existing staged user with user fields - { email: 'test2@discourse.org', location: 'value 3' } # new staged user with user fields - ] + { email: "test@discourse.org" }, # new user without user fields + { email: user.email, location: "value 1", color: "blue" }, # existing user with user fields + { email: staged_user.email, location: "value 2", color: "redd" }, # existing staged user with user fields + { email: "test2@discourse.org", location: "value 3" }, # new staged user with user fields + ], ) expect(Invite.count).to eq(3) - expect(User.where(staged: true).find_by_email('test@discourse.org')).to eq(nil) - expect(user.user_fields[user_field.id.to_s]).to eq('value 1') - expect(user.user_fields[user_field_color.id.to_s]).to eq('Blue') - expect(staged_user.user_fields[user_field.id.to_s]).to eq('value 2') + expect(User.where(staged: true).find_by_email("test@discourse.org")).to eq(nil) + expect(user.user_fields[user_field.id.to_s]).to eq("value 1") + expect(user.user_fields[user_field_color.id.to_s]).to eq("Blue") + expect(staged_user.user_fields[user_field.id.to_s]).to eq("value 2") expect(staged_user.user_fields[user_field_color.id.to_s]).to eq(nil) - new_staged_user = User.where(staged: true).find_by_email('test2@discourse.org') - expect(new_staged_user.user_fields[user_field.id.to_s]).to eq('value 3') + new_staged_user = User.where(staged: true).find_by_email("test2@discourse.org") + expect(new_staged_user.user_fields[user_field.id.to_s]).to eq("value 3") end - context 'when there are more than 200 invites' do + context "when there are more than 200 invites" do let(:bulk_invites) { [] } - before do - 202.times do |i| - bulk_invites << { "email": "test_#{i}@discourse.org" } - end - end + before { 202.times { |i| bulk_invites << { email: "test_#{i}@discourse.org" } } } - it 'rate limits email sending' do - described_class.new.execute( - current_user_id: admin.id, - invites: bulk_invites - ) + it "rate limits email sending" do + described_class.new.execute(current_user_id: admin.id, invites: bulk_invites) invite = Invite.last - expect(invite.email).to eq('test_201@discourse.org') + expect(invite.email).to eq("test_201@discourse.org") expect(invite.emailed_status).to eq(Invite.emailed_status_types[:bulk_pending]) expect(Jobs::ProcessBulkInviteEmails.jobs.size).to eq(1) end diff --git a/spec/jobs/bump_topic_spec.rb b/spec/jobs/bump_topic_spec.rb index ed7668d9c5..ce4d2aecbe 100644 --- a/spec/jobs/bump_topic_spec.rb +++ b/spec/jobs/bump_topic_spec.rb @@ -31,5 +31,4 @@ RSpec.describe Jobs::BumpTopic do expect(topic.reload.public_topic_timer).to eq(nil) end - end diff --git a/spec/jobs/check_new_features_spec.rb b/spec/jobs/check_new_features_spec.rb index ab20272145..43fb9be37f 100644 --- a/spec/jobs/check_new_features_spec.rb +++ b/spec/jobs/check_new_features_spec.rb @@ -4,53 +4,40 @@ RSpec.describe Jobs::CheckNewFeatures do def build_feature_hash(id:, created_at:, discourse_version: "2.9.0.beta10") { id: id, - user_id: 89432, + user_id: 89_432, emoji: "👤", title: "New fancy feature!", description: "", link: "https://meta.discourse.org/t/-/238821", created_at: created_at.iso8601, updated_at: (created_at + 1.minutes).iso8601, - discourse_version: discourse_version + discourse_version: discourse_version, } end def stub_meta_new_features_endpoint(*features) - stub_request(:get, "https://meta.discourse.org/new-features.json") - .to_return( - status: 200, - body: JSON.dump(features), - headers: { - "Content-Type" => "application/json" - } - ) + stub_request(:get, "https://meta.discourse.org/new-features.json").to_return( + status: 200, + body: JSON.dump(features), + headers: { + "Content-Type" => "application/json", + }, + ) end fab!(:admin1) { Fabricate(:admin) } fab!(:admin2) { Fabricate(:admin) } let(:feature1) do - build_feature_hash( - id: 35, - created_at: 3.days.ago, - discourse_version: "2.8.1.beta12" - ) + build_feature_hash(id: 35, created_at: 3.days.ago, discourse_version: "2.8.1.beta12") end let(:feature2) do - build_feature_hash( - id: 34, - created_at: 2.days.ago, - discourse_version: "2.8.1.beta13" - ) + build_feature_hash(id: 34, created_at: 2.days.ago, discourse_version: "2.8.1.beta13") end let(:pending_feature) do - build_feature_hash( - id: 37, - created_at: 1.day.ago, - discourse_version: "2.8.1.beta14" - ) + build_feature_hash(id: 37, created_at: 1.day.ago, discourse_version: "2.8.1.beta14") end before do @@ -59,9 +46,7 @@ RSpec.describe Jobs::CheckNewFeatures do stub_meta_new_features_endpoint(feature1, feature2, pending_feature) end - after do - DiscourseUpdates.clean_state - end + after { DiscourseUpdates.clean_state } it "backfills last viewed feature for admins who don't have last viewed feature" do DiscourseUpdates.stubs(:current_version).returns("2.8.1.beta12") @@ -70,8 +55,12 @@ RSpec.describe Jobs::CheckNewFeatures do described_class.new.execute({}) - expect(DiscourseUpdates.get_last_viewed_feature_date(admin2.id).iso8601).to eq(feature1[:created_at]) - expect(DiscourseUpdates.get_last_viewed_feature_date(admin1.id).iso8601).to eq(Time.zone.now.iso8601) + expect(DiscourseUpdates.get_last_viewed_feature_date(admin2.id).iso8601).to eq( + feature1[:created_at], + ) + expect(DiscourseUpdates.get_last_viewed_feature_date(admin1.id).iso8601).to eq( + Time.zone.now.iso8601, + ) end it "notifies admins about new features that are available in the site's version" do @@ -79,14 +68,18 @@ RSpec.describe Jobs::CheckNewFeatures do described_class.new.execute({}) - expect(admin1.notifications.where( - notification_type: Notification.types[:new_features], - read: false - ).count).to eq(1) - expect(admin2.notifications.where( - notification_type: Notification.types[:new_features], - read: false - ).count).to eq(1) + expect( + admin1 + .notifications + .where(notification_type: Notification.types[:new_features], read: false) + .count, + ).to eq(1) + expect( + admin2 + .notifications + .where(notification_type: Notification.types[:new_features], read: false) + .count, + ).to eq(1) end it "consolidates new features notifications" do @@ -94,10 +87,11 @@ RSpec.describe Jobs::CheckNewFeatures do described_class.new.execute({}) - notification = admin1.notifications.where( - notification_type: Notification.types[:new_features], - read: false - ).first + notification = + admin1 + .notifications + .where(notification_type: Notification.types[:new_features], read: false) + .first expect(notification).to be_present DiscourseUpdates.stubs(:current_version).returns("2.8.1.beta14") @@ -106,10 +100,11 @@ RSpec.describe Jobs::CheckNewFeatures do # old notification is destroyed expect(Notification.find_by(id: notification.id)).to eq(nil) - notification = admin1.notifications.where( - notification_type: Notification.types[:new_features], - read: false - ).first + notification = + admin1 + .notifications + .where(notification_type: Notification.types[:new_features], read: false) + .first # new notification is created expect(notification).to be_present end @@ -121,6 +116,8 @@ RSpec.describe Jobs::CheckNewFeatures do described_class.new.execute({}) expect(admin1.notifications.count).to eq(0) - expect(admin2.notifications.where(notification_type: Notification.types[:new_features]).count).to eq(1) + expect( + admin2.notifications.where(notification_type: Notification.types[:new_features]).count, + ).to eq(1) end end diff --git a/spec/jobs/clean_dismissed_topic_users_spec.rb b/spec/jobs/clean_dismissed_topic_users_spec.rb index 187ce7c79d..b6e411814b 100644 --- a/spec/jobs/clean_dismissed_topic_users_spec.rb +++ b/spec/jobs/clean_dismissed_topic_users_spec.rb @@ -5,13 +5,13 @@ RSpec.describe Jobs::CleanDismissedTopicUsers do fab!(:topic) { Fabricate(:topic, created_at: 5.hours.ago) } fab!(:dismissed_topic_user) { Fabricate(:dismissed_topic_user, user: user, topic: topic) } - describe '#delete_overdue_dismissals!' do - it 'does not delete when new_topic_duration_minutes is set to always' do + describe "#delete_overdue_dismissals!" do + it "does not delete when new_topic_duration_minutes is set to always" do user.user_option.update(new_topic_duration_minutes: User::NewTopicDuration::ALWAYS) expect { described_class.new.execute({}) }.not_to change { DismissedTopicUser.count } end - it 'deletes when new_topic_duration_minutes is set to since last visit' do + it "deletes when new_topic_duration_minutes is set to since last visit" do user.user_option.update(new_topic_duration_minutes: User::NewTopicDuration::LAST_VISIT) expect { described_class.new.execute({}) }.not_to change { DismissedTopicUser.count } @@ -19,7 +19,7 @@ RSpec.describe Jobs::CleanDismissedTopicUsers do expect { described_class.new.execute({}) }.to change { DismissedTopicUser.count }.by(-1) end - it 'deletes when new_topic_duration_minutes is set to created in the last day' do + it "deletes when new_topic_duration_minutes is set to created in the last day" do user.user_option.update(new_topic_duration_minutes: 1440) expect { described_class.new.execute({}) }.not_to change { DismissedTopicUser.count } @@ -28,7 +28,7 @@ RSpec.describe Jobs::CleanDismissedTopicUsers do end end - describe '#delete_over_the_limit_dismissals!' do + describe "#delete_over_the_limit_dismissals!" do fab!(:user2) { Fabricate(:user, created_at: 1.days.ago, previous_visit_at: 1.days.ago) } fab!(:topic2) { Fabricate(:topic, created_at: 6.hours.ago) } fab!(:topic3) { Fabricate(:topic, created_at: 2.hours.ago) } @@ -41,7 +41,7 @@ RSpec.describe Jobs::CleanDismissedTopicUsers do user2.user_option.update(new_topic_duration_minutes: User::NewTopicDuration::ALWAYS) end - it 'deletes over the limit dismissals' do + it "deletes over the limit dismissals" do described_class.new.execute({}) expect(dismissed_topic_user.reload).to be_present expect(dismissed_topic_user2.reload).to be_present diff --git a/spec/jobs/clean_up_associated_accounts_spec.rb b/spec/jobs/clean_up_associated_accounts_spec.rb index 433c5fcaac..cefcac82bd 100644 --- a/spec/jobs/clean_up_associated_accounts_spec.rb +++ b/spec/jobs/clean_up_associated_accounts_spec.rb @@ -6,12 +6,27 @@ RSpec.describe Jobs::CleanUpAssociatedAccounts do it "deletes the correct records" do freeze_time - last_week = UserAssociatedAccount.create!(provider_name: "twitter", provider_uid: "1", updated_at: 7.days.ago) - today = UserAssociatedAccount.create!(provider_name: "twitter", provider_uid: "12", updated_at: 12.hours.ago) - connected = UserAssociatedAccount.create!(provider_name: "twitter", provider_uid: "123", user: Fabricate(:user), updated_at: 12.hours.ago) + last_week = + UserAssociatedAccount.create!( + provider_name: "twitter", + provider_uid: "1", + updated_at: 7.days.ago, + ) + today = + UserAssociatedAccount.create!( + provider_name: "twitter", + provider_uid: "12", + updated_at: 12.hours.ago, + ) + connected = + UserAssociatedAccount.create!( + provider_name: "twitter", + provider_uid: "123", + user: Fabricate(:user), + updated_at: 12.hours.ago, + ) expect { subject }.to change { UserAssociatedAccount.count }.by(-1) expect(UserAssociatedAccount.all).to contain_exactly(today, connected) end - end diff --git a/spec/jobs/clean_up_email_logs_spec.rb b/spec/jobs/clean_up_email_logs_spec.rb index 3d0ec29b09..b4e7669675 100644 --- a/spec/jobs/clean_up_email_logs_spec.rb +++ b/spec/jobs/clean_up_email_logs_spec.rb @@ -5,9 +5,7 @@ RSpec.describe Jobs::CleanUpEmailLogs do fab!(:email_log2) { Fabricate(:email_log, created_at: 2.weeks.ago) } fab!(:email_log3) { Fabricate(:email_log, created_at: 2.days.ago) } - let!(:skipped_email_log) do - Fabricate(:skipped_email_log, created_at: 2.years.ago) - end + let!(:skipped_email_log) { Fabricate(:skipped_email_log, created_at: 2.years.ago) } fab!(:skipped_email_log2) { Fabricate(:skipped_email_log) } @@ -23,10 +21,6 @@ RSpec.describe Jobs::CleanUpEmailLogs do expect(EmailLog.all).to contain_exactly(email_log, email_log2, email_log3) - expect(SkippedEmailLog.all).to contain_exactly( - skipped_email_log, - skipped_email_log2 - ) + expect(SkippedEmailLog.all).to contain_exactly(skipped_email_log, skipped_email_log2) end - end diff --git a/spec/jobs/clean_up_inactive_users_spec.rb b/spec/jobs/clean_up_inactive_users_spec.rb index 55bc28625f..985659519d 100644 --- a/spec/jobs/clean_up_inactive_users_spec.rb +++ b/spec/jobs/clean_up_inactive_users_spec.rb @@ -4,22 +4,16 @@ RSpec.describe Jobs::CleanUpInactiveUsers do it "should clean up new users that have been inactive" do SiteSetting.clean_up_inactive_users_after_days = 0 - user = Fabricate(:user, - last_seen_at: 5.days.ago, - trust_level: TrustLevel.levels[:newuser] - ) + user = Fabricate(:user, last_seen_at: 5.days.ago, trust_level: TrustLevel.levels[:newuser]) Fabricate(:active_user) - Fabricate(:post, user: Fabricate(:user, - trust_level: TrustLevel.levels[:newuser], - last_seen_at: 5.days.ago - )).user + Fabricate( + :post, + user: Fabricate(:user, trust_level: TrustLevel.levels[:newuser], last_seen_at: 5.days.ago), + ).user - Fabricate(:user, - trust_level: TrustLevel.levels[:newuser], - last_seen_at: 2.days.ago - ) + Fabricate(:user, trust_level: TrustLevel.levels[:newuser], last_seen_at: 2.days.ago) Fabricate(:user, trust_level: TrustLevel.levels[:basic]) @@ -27,8 +21,7 @@ RSpec.describe Jobs::CleanUpInactiveUsers do SiteSetting.clean_up_inactive_users_after_days = 4 - expect { described_class.new.execute({}) } - .to change { User.count }.by(-1) + expect { described_class.new.execute({}) }.to change { User.count }.by(-1) expect(User.exists?(id: user.id)).to eq(false) end @@ -43,7 +36,8 @@ RSpec.describe Jobs::CleanUpInactiveUsers do it "doesn't delete inactive mods" do SiteSetting.clean_up_inactive_users_after_days = 4 - moderator = Fabricate(:moderator, last_seen_at: 5.days.ago, trust_level: TrustLevel.levels[:newuser]) + moderator = + Fabricate(:moderator, last_seen_at: 5.days.ago, trust_level: TrustLevel.levels[:newuser]) expect { described_class.new.execute({}) }.to_not change { User.count } expect(User.exists?(moderator.id)).to eq(true) diff --git a/spec/jobs/clean_up_post_reply_keys_spec.rb b/spec/jobs/clean_up_post_reply_keys_spec.rb index 10594c1ac5..bd20b4f1df 100644 --- a/spec/jobs/clean_up_post_reply_keys_spec.rb +++ b/spec/jobs/clean_up_post_reply_keys_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe Jobs::CleanUpPostReplyKeys do - it 'removes old post_reply_keys' do + it "removes old post_reply_keys" do freeze_time reply_key1 = Fabricate(:post_reply_key, created_at: 1.day.ago) @@ -10,16 +10,12 @@ RSpec.describe Jobs::CleanUpPostReplyKeys do SiteSetting.disallow_reply_by_email_after_days = 0 - expect { Jobs::CleanUpPostReplyKeys.new.execute({}) } - .not_to change { PostReplyKey.count } + expect { Jobs::CleanUpPostReplyKeys.new.execute({}) }.not_to change { PostReplyKey.count } SiteSetting.disallow_reply_by_email_after_days = 2 - expect { Jobs::CleanUpPostReplyKeys.new.execute({}) } - .to change { PostReplyKey.count }.by(-1) + expect { Jobs::CleanUpPostReplyKeys.new.execute({}) }.to change { PostReplyKey.count }.by(-1) - expect(PostReplyKey.all).to contain_exactly( - reply_key1, reply_key2 - ) + expect(PostReplyKey.all).to contain_exactly(reply_key1, reply_key2) end end diff --git a/spec/jobs/clean_up_unused_staged_users_spec.rb b/spec/jobs/clean_up_unused_staged_users_spec.rb index daaad64912..f66bc60460 100644 --- a/spec/jobs/clean_up_unused_staged_users_spec.rb +++ b/spec/jobs/clean_up_unused_staged_users_spec.rb @@ -37,7 +37,7 @@ RSpec.describe Jobs::CleanUpUnusedStagedUsers do end end - context 'when staged user is not old enough' do + context "when staged user is not old enough" do before { staged_user.update!(created_at: 5.months.ago) } include_examples "does not delete" end diff --git a/spec/jobs/clean_up_uploads_spec.rb b/spec/jobs/clean_up_uploads_spec.rb index 461123394b..39ba932329 100644 --- a/spec/jobs/clean_up_uploads_spec.rb +++ b/spec/jobs/clean_up_uploads_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true RSpec.describe Jobs::CleanUpUploads do - def fabricate_upload(attributes = {}) Fabricate(:upload, { created_at: 2.hours.ago }.merge(attributes)) end @@ -22,7 +21,6 @@ RSpec.describe Jobs::CleanUpUploads do end it "only runs upload cleanup every grace period / 2 time" do - SiteSetting.clean_orphan_uploads_grace_period_hours = 48 expired = fabricate_upload(created_at: 49.hours.ago) Jobs::CleanUpUploads.new.execute(nil) @@ -38,44 +36,31 @@ RSpec.describe Jobs::CleanUpUploads do Jobs::CleanUpUploads.new.execute(nil) expect(Upload.exists?(id: upload.id)).to eq(false) - end it "deletes orphan uploads" do - expect do - Jobs::CleanUpUploads.new.execute(nil) - end.to change { Upload.count }.by(-1) + expect do Jobs::CleanUpUploads.new.execute(nil) end.to change { Upload.count }.by(-1) expect(Upload.exists?(id: expired_upload.id)).to eq(false) end - describe 'unused callbacks' do - before do - Upload.add_unused_callback do |uploads| - uploads.where.not(id: expired_upload.id) - end - end + describe "unused callbacks" do + before { Upload.add_unused_callback { |uploads| uploads.where.not(id: expired_upload.id) } } - after do - Upload.reset_unused_callbacks - end + after { Upload.reset_unused_callbacks } - it 'does not delete uploads skipped by an unused callback' do - expect do - Jobs::CleanUpUploads.new.execute(nil) - end.not_to change { Upload.count } + it "does not delete uploads skipped by an unused callback" do + expect do Jobs::CleanUpUploads.new.execute(nil) end.not_to change { Upload.count } expect(Upload.exists?(id: expired_upload.id)).to eq(true) end - it 'deletes other uploads not skipped by an unused callback' do + it "deletes other uploads not skipped by an unused callback" do expired_upload2 = fabricate_upload upload = fabricate_upload UploadReference.create(target: Fabricate(:post), upload: upload) - expect do - Jobs::CleanUpUploads.new.execute(nil) - end.to change { Upload.count }.by(-1) + expect do Jobs::CleanUpUploads.new.execute(nil) end.to change { Upload.count }.by(-1) expect(Upload.exists?(id: expired_upload.id)).to eq(true) expect(Upload.exists?(id: expired_upload2.id)).to eq(false) @@ -83,33 +68,23 @@ RSpec.describe Jobs::CleanUpUploads do end end - describe 'in use callbacks' do - before do - Upload.add_in_use_callback do |upload| - expired_upload.id == upload.id - end - end + describe "in use callbacks" do + before { Upload.add_in_use_callback { |upload| expired_upload.id == upload.id } } - after do - Upload.reset_in_use_callbacks - end + after { Upload.reset_in_use_callbacks } - it 'does not delete uploads that are in use by callback' do - expect do - Jobs::CleanUpUploads.new.execute(nil) - end.not_to change { Upload.count } + it "does not delete uploads that are in use by callback" do + expect do Jobs::CleanUpUploads.new.execute(nil) end.not_to change { Upload.count } expect(Upload.exists?(id: expired_upload.id)).to eq(true) end - it 'deletes other uploads that are not in use by callback' do + it "deletes other uploads that are not in use by callback" do expired_upload2 = fabricate_upload upload = fabricate_upload UploadReference.create(target: Fabricate(:post), upload: upload) - expect do - Jobs::CleanUpUploads.new.execute(nil) - end.to change { Upload.count }.by(-1) + expect do Jobs::CleanUpUploads.new.execute(nil) end.to change { Upload.count }.by(-1) expect(Upload.exists?(id: expired_upload.id)).to eq(true) expect(Upload.exists?(id: expired_upload2.id)).to eq(false) @@ -117,27 +92,20 @@ RSpec.describe Jobs::CleanUpUploads do end end - describe 'when clean_up_uploads is disabled' do - before do - SiteSetting.clean_up_uploads = false - end + describe "when clean_up_uploads is disabled" do + before { SiteSetting.clean_up_uploads = false } - it 'should still delete invalid upload records' do - upload2 = fabricate_upload( - url: "", - retain_hours: nil - ) + it "should still delete invalid upload records" do + upload2 = fabricate_upload(url: "", retain_hours: nil) - expect do - Jobs::CleanUpUploads.new.execute(nil) - end.to change { Upload.count }.by(-1) + expect do Jobs::CleanUpUploads.new.execute(nil) end.to change { Upload.count }.by(-1) expect(Upload.exists?(id: expired_upload.id)).to eq(true) expect(Upload.exists?(id: upload2.id)).to eq(false) end end - it 'does not clean up upload site settings' do + it "does not clean up upload site settings" do begin original_provider = SiteSetting.provider SiteSetting.provider = SiteSettings::DbProvider.new(SiteSetting) @@ -161,8 +129,7 @@ RSpec.describe Jobs::CleanUpUploads do SiteSetting.large_icon = large_icon_upload SiteSetting.opengraph_image = opengraph_image_upload - SiteSetting.twitter_summary_large_image = - twitter_summary_large_image_upload + SiteSetting.twitter_summary_large_image = twitter_summary_large_image_upload SiteSetting.favicon = favicon_upload SiteSetting.apple_touch_icon = apple_touch_icon_upload @@ -170,20 +137,13 @@ RSpec.describe Jobs::CleanUpUploads do Jobs::CleanUpUploads.new.execute(nil) [ - logo_upload, - logo_small_upload, - digest_logo_upload, - mobile_logo_upload, - large_icon_upload, - opengraph_image_upload, - twitter_summary_large_image_upload, - favicon_upload, - apple_touch_icon_upload, - system_upload + logo_upload, logo_small_upload, digest_logo_upload, mobile_logo_upload, large_icon_upload, + opengraph_image_upload, twitter_summary_large_image_upload, favicon_upload, + apple_touch_icon_upload, system_upload, ].each { |record| expect(Upload.exists?(id: record.id)).to eq(true) } fabricate_upload - SiteSetting.opengraph_image = '' + SiteSetting.opengraph_image = "" Jobs::CleanUpUploads.new.execute(nil) ensure @@ -307,15 +267,19 @@ RSpec.describe Jobs::CleanUpUploads do upload2 = fabricate_upload upload3 = fabricate_upload - Fabricate(:reviewable_queued_post_topic, payload: { - raw: "#{upload.short_url}\n#{upload2.short_url}" - }) - - Fabricate(:reviewable_queued_post_topic, + Fabricate( + :reviewable_queued_post_topic, payload: { - raw: "#{upload3.short_url}" + raw: "#{upload.short_url}\n#{upload2.short_url}", }, - status: Reviewable.statuses[:rejected] + ) + + Fabricate( + :reviewable_queued_post_topic, + payload: { + raw: "#{upload3.short_url}", + }, + status: Reviewable.statuses[:rejected], ) Jobs::CleanUpUploads.new.execute(nil) @@ -350,7 +314,7 @@ RSpec.describe Jobs::CleanUpUploads do it "does not delete custom emojis" do upload = fabricate_upload - CustomEmoji.create!(name: 'test', upload: upload) + CustomEmoji.create!(name: "test", upload: upload) Jobs::CleanUpUploads.new.execute(nil) @@ -371,7 +335,12 @@ RSpec.describe Jobs::CleanUpUploads do it "does not delete theme setting uploads" do theme = Fabricate(:theme) theme_upload = fabricate_upload - ThemeSetting.create!(theme: theme, data_type: ThemeSetting.types[:upload], value: theme_upload.id.to_s, name: "my_setting_name") + ThemeSetting.create!( + theme: theme, + data_type: ThemeSetting.types[:upload], + value: theme_upload.id.to_s, + name: "my_setting_name", + ) Jobs::CleanUpUploads.new.execute(nil) @@ -390,10 +359,30 @@ RSpec.describe Jobs::CleanUpUploads do end it "deletes external upload stubs that have expired" do - external_stub1 = Fabricate(:external_upload_stub, status: ExternalUploadStub.statuses[:created], created_at: 10.minutes.ago) - external_stub2 = Fabricate(:external_upload_stub, status: ExternalUploadStub.statuses[:created], created_at: (ExternalUploadStub::CREATED_EXPIRY_HOURS.hours + 10.minutes).ago) - external_stub3 = Fabricate(:external_upload_stub, status: ExternalUploadStub.statuses[:uploaded], created_at: 10.minutes.ago) - external_stub4 = Fabricate(:external_upload_stub, status: ExternalUploadStub.statuses[:uploaded], created_at: (ExternalUploadStub::UPLOADED_EXPIRY_HOURS.hours + 10.minutes).ago) + external_stub1 = + Fabricate( + :external_upload_stub, + status: ExternalUploadStub.statuses[:created], + created_at: 10.minutes.ago, + ) + external_stub2 = + Fabricate( + :external_upload_stub, + status: ExternalUploadStub.statuses[:created], + created_at: (ExternalUploadStub::CREATED_EXPIRY_HOURS.hours + 10.minutes).ago, + ) + external_stub3 = + Fabricate( + :external_upload_stub, + status: ExternalUploadStub.statuses[:uploaded], + created_at: 10.minutes.ago, + ) + external_stub4 = + Fabricate( + :external_upload_stub, + status: ExternalUploadStub.statuses[:uploaded], + created_at: (ExternalUploadStub::UPLOADED_EXPIRY_HOURS.hours + 10.minutes).ago, + ) Jobs::CleanUpUploads.new.execute(nil) expect(ExternalUploadStub.pluck(:id)).to contain_exactly(external_stub1.id, external_stub3.id) end diff --git a/spec/jobs/clean_up_user_export_topics_spec.rb b/spec/jobs/clean_up_user_export_topics_spec.rb index 3bf13967d8..e26d612ad5 100644 --- a/spec/jobs/clean_up_user_export_topics_spec.rb +++ b/spec/jobs/clean_up_user_export_topics_spec.rb @@ -3,27 +3,29 @@ RSpec.describe Jobs::CleanUpUserExportTopics do fab!(:user) { Fabricate(:user) } - it 'should delete ancient user export system messages' do - post_en = SystemMessage.create_from_system_user( - user, - :csv_export_succeeded, - download_link: "http://example.com/download", - file_name: "xyz_en.gz", - file_size: "55", - export_title: "user_archive" - ) + it "should delete ancient user export system messages" do + post_en = + SystemMessage.create_from_system_user( + user, + :csv_export_succeeded, + download_link: "http://example.com/download", + file_name: "xyz_en.gz", + file_size: "55", + export_title: "user_archive", + ) topic_en = post_en.topic topic_en.update!(created_at: 5.days.ago) I18n.locale = :fr - post_fr = SystemMessage.create_from_system_user( - user, - :csv_export_succeeded, - download_link: "http://example.com/download", - file_name: "xyz_fr.gz", - file_size: "56", - export_title: "user_archive" - ) + post_fr = + SystemMessage.create_from_system_user( + user, + :csv_export_succeeded, + download_link: "http://example.com/download", + file_name: "xyz_fr.gz", + file_size: "56", + export_title: "user_archive", + ) topic_fr = post_fr.topic topic_fr.update!(created_at: 5.days.ago) diff --git a/spec/jobs/close_topic_spec.rb b/spec/jobs/close_topic_spec.rb index a3b24a82fd..fd7476897a 100644 --- a/spec/jobs/close_topic_spec.rb +++ b/spec/jobs/close_topic_spec.rb @@ -3,22 +3,15 @@ RSpec.describe Jobs::CloseTopic do fab!(:admin) { Fabricate(:admin) } - fab!(:topic) do - Fabricate(:topic_timer, user: admin).topic - end + fab!(:topic) { Fabricate(:topic_timer, user: admin).topic } - it 'should be able to close a topic' do + it "should be able to close a topic" do freeze_time(61.minutes.from_now) do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: true - ) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: true) expect(topic.reload.closed).to eq(true) - expect(Post.last.raw).to eq(I18n.t( - 'topic_statuses.autoclosed_enabled_minutes', count: 61 - )) + expect(Post.last.raw).to eq(I18n.t("topic_statuses.autoclosed_enabled_minutes", count: 61)) end end @@ -30,61 +23,47 @@ RSpec.describe Jobs::CloseTopic do end end - describe 'when trying to close a topic that has already been closed' do - it 'should delete the topic timer' do + describe "when trying to close a topic that has already been closed" do + it "should delete the topic timer" do freeze_time(topic.public_topic_timer.execute_at + 1.minute) topic.update!(closed: true) expect do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: true - ) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: true) end.to change { TopicTimer.exists?(topic_id: topic.id) }.from(true).to(false) end end - describe 'when trying to close a topic that has been deleted' do - it 'should delete the topic timer' do + describe "when trying to close a topic that has been deleted" do + it "should delete the topic timer" do freeze_time(topic.public_topic_timer.execute_at + 1.minute) topic.trash! expect do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: true - ) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: true) end.to change { TopicTimer.exists?(topic_id: topic.id) }.from(true).to(false) end end - describe 'when user is no longer authorized to close topics' do + describe "when user is no longer authorized to close topics" do fab!(:user) { Fabricate(:user) } - fab!(:topic) do - Fabricate(:topic_timer, user: user).topic - end + fab!(:topic) { Fabricate(:topic_timer, user: user).topic } - it 'should destroy the topic timer' do + it "should destroy the topic timer" do freeze_time(topic.public_topic_timer.execute_at + 1.minute) expect do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: true - ) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: true) end.to change { TopicTimer.exists?(topic_id: topic.id) }.from(true).to(false) expect(topic.reload.closed).to eq(false) end it "should reconfigure topic timer if category's topics are set to autoclose" do - category = Fabricate(:category, - auto_close_based_on_last_post: true, - auto_close_hours: 5 - ) + category = Fabricate(:category, auto_close_based_on_last_post: true, auto_close_hours: 5) topic = Fabricate(:topic, category: category) topic.public_topic_timer.update!(user: user) @@ -92,12 +71,10 @@ RSpec.describe Jobs::CloseTopic do freeze_time(topic.public_topic_timer.execute_at + 1.minute) expect do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: true - ) - end.to change { topic.reload.public_topic_timer.user }.from(user).to(Discourse.system_user) - .and change { topic.public_topic_timer.id } + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: true) + end.to change { topic.reload.public_topic_timer.user }.from(user).to( + Discourse.system_user, + ).and change { topic.public_topic_timer.id } expect(topic.reload.closed).to eq(false) end diff --git a/spec/jobs/correct_missing_dualstack_urls_spec.rb b/spec/jobs/correct_missing_dualstack_urls_spec.rb index 09c35a3abd..7af8e9ece7 100644 --- a/spec/jobs/correct_missing_dualstack_urls_spec.rb +++ b/spec/jobs/correct_missing_dualstack_urls_spec.rb @@ -1,60 +1,70 @@ # frozen_string_literal: true RSpec.describe Jobs::CorrectMissingDualstackUrls do - it 'corrects the urls' do + it "corrects the urls" do setup_s3 SiteSetting.s3_region = "us-east-1" SiteSetting.s3_upload_bucket = "custom-bucket" # we will only correct for our base_url, random urls will be left alone - expect(Discourse.store.absolute_base_url).to eq('//custom-bucket.s3.dualstack.us-east-1.amazonaws.com') - - current_upload = Upload.create!( - url: '//custom-bucket.s3-us-east-1.amazonaws.com/somewhere/a.png', - original_filename: 'a.png', - filesize: 100, - user_id: -1, + expect(Discourse.store.absolute_base_url).to eq( + "//custom-bucket.s3.dualstack.us-east-1.amazonaws.com", ) - bad_upload = Upload.create!( - url: '//custom-bucket.s3-us-west-1.amazonaws.com/somewhere/a.png', - original_filename: 'a.png', - filesize: 100, - user_id: -1, - ) + current_upload = + Upload.create!( + url: "//custom-bucket.s3-us-east-1.amazonaws.com/somewhere/a.png", + original_filename: "a.png", + filesize: 100, + user_id: -1, + ) - current_optimized = OptimizedImage.create!( - url: '//custom-bucket.s3-us-east-1.amazonaws.com/somewhere/a.png', - filesize: 100, - upload_id: current_upload.id, - width: 100, - height: 100, - sha1: 'xxx', - extension: '.png' - ) + bad_upload = + Upload.create!( + url: "//custom-bucket.s3-us-west-1.amazonaws.com/somewhere/a.png", + original_filename: "a.png", + filesize: 100, + user_id: -1, + ) - bad_optimized = OptimizedImage.create!( - url: '//custom-bucket.s3-us-west-1.amazonaws.com/somewhere/a.png', - filesize: 100, - upload_id: current_upload.id, - width: 110, - height: 100, - sha1: 'xxx', - extension: '.png' - ) + current_optimized = + OptimizedImage.create!( + url: "//custom-bucket.s3-us-east-1.amazonaws.com/somewhere/a.png", + filesize: 100, + upload_id: current_upload.id, + width: 100, + height: 100, + sha1: "xxx", + extension: ".png", + ) + + bad_optimized = + OptimizedImage.create!( + url: "//custom-bucket.s3-us-west-1.amazonaws.com/somewhere/a.png", + filesize: 100, + upload_id: current_upload.id, + width: 110, + height: 100, + sha1: "xxx", + extension: ".png", + ) Jobs::CorrectMissingDualstackUrls.new.execute_onceoff(nil) bad_upload.reload - expect(bad_upload.url).to eq('//custom-bucket.s3-us-west-1.amazonaws.com/somewhere/a.png') + expect(bad_upload.url).to eq("//custom-bucket.s3-us-west-1.amazonaws.com/somewhere/a.png") current_upload.reload - expect(current_upload.url).to eq('//custom-bucket.s3.dualstack.us-east-1.amazonaws.com/somewhere/a.png') + expect(current_upload.url).to eq( + "//custom-bucket.s3.dualstack.us-east-1.amazonaws.com/somewhere/a.png", + ) bad_optimized.reload - expect(bad_optimized.url).to eq('//custom-bucket.s3-us-west-1.amazonaws.com/somewhere/a.png') + expect(bad_optimized.url).to eq("//custom-bucket.s3-us-west-1.amazonaws.com/somewhere/a.png") current_optimized.reload - expect(current_optimized.url).to eq('//custom-bucket.s3.dualstack.us-east-1.amazonaws.com/somewhere/a.png') + expect(current_optimized.url).to eq( + "//custom-bucket.s3.dualstack.us-east-1.amazonaws.com/somewhere/a.png", + ) end end diff --git a/spec/jobs/crawl_topic_link_spec.rb b/spec/jobs/crawl_topic_link_spec.rb index 2ced470cff..95818fb234 100644 --- a/spec/jobs/crawl_topic_link_spec.rb +++ b/spec/jobs/crawl_topic_link_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true RSpec.describe Jobs::CrawlTopicLink do - let(:job) { Jobs::CrawlTopicLink.new } it "needs a topic_link_id" do diff --git a/spec/jobs/create_linked_topic_spec.rb b/spec/jobs/create_linked_topic_spec.rb index 9583116469..973492bbcd 100644 --- a/spec/jobs/create_linked_topic_spec.rb +++ b/spec/jobs/create_linked_topic_spec.rb @@ -5,12 +5,10 @@ RSpec.describe Jobs::CreateLinkedTopic do expect { Jobs::CreateLinkedTopic.new.perform(post_id: 1, sync_exec: true) }.not_to raise_error end - context 'with a post' do + context "with a post" do fab!(:category) { Fabricate(:category) } fab!(:topic) { Fabricate(:topic, category: category) } - fab!(:post) do - Fabricate(:post, topic: topic) - end + fab!(:post) { Fabricate(:post, topic: topic) } fab!(:user_1) { Fabricate(:user) } fab!(:user_2) { Fabricate(:user) } @@ -32,21 +30,40 @@ RSpec.describe Jobs::CreateLinkedTopic do Fabricate(:topic_user, notification_level: muted, topic: topic, user: user_2) end - it 'creates a linked topic' do - small_action_post = Fabricate(:post, topic: topic, post_type: Post.types[:small_action], action_code: "closed.enabled") + it "creates a linked topic" do + small_action_post = + Fabricate( + :post, + topic: topic, + post_type: Post.types[:small_action], + action_code: "closed.enabled", + ) Jobs::CreateLinkedTopic.new.execute(post_id: post.id) raw_title = topic.title topic.reload new_topic = Topic.last linked_topic = new_topic.linked_topic - expect(topic.title).to include(I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: raw_title, count: 1)) - expect(topic.posts.last.raw).to include(I18n.t('create_linked_topic.small_action_post_raw', new_title: "[#{new_topic.title}](#{new_topic.url})")) - expect(new_topic.title).to include(I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: raw_title, count: 2)) + expect(topic.title).to include( + I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: raw_title, count: 1), + ) + expect(topic.posts.last.raw).to include( + I18n.t( + "create_linked_topic.small_action_post_raw", + new_title: "[#{new_topic.title}](#{new_topic.url})", + ), + ) + expect(new_topic.title).to include( + I18n.t("create_linked_topic.topic_title_with_sequence", topic_title: raw_title, count: 2), + ) expect(new_topic.first_post.raw).to include(topic.url) expect(new_topic.category.id).to eq(category.id) expect(new_topic.topic_users.count).to eq(3) - expect(new_topic.topic_users.pluck(:notification_level)).to contain_exactly(muted, tracking, watching) + expect(new_topic.topic_users.pluck(:notification_level)).to contain_exactly( + muted, + tracking, + watching, + ) expect(linked_topic.topic_id).to eq(new_topic.id) expect(linked_topic.original_topic_id).to eq(topic.id) expect(linked_topic.sequence).to eq(2) diff --git a/spec/jobs/create_recent_post_search_indexes_spec.rb b/spec/jobs/create_recent_post_search_indexes_spec.rb index fa7e9a931e..1d350d254b 100644 --- a/spec/jobs/create_recent_post_search_indexes_spec.rb +++ b/spec/jobs/create_recent_post_search_indexes_spec.rb @@ -13,21 +13,19 @@ RSpec.describe Jobs::CreateRecentPostSearchIndexes do Fabricate(:post) end - before do - SearchIndexer.enable - end + before { SearchIndexer.enable } - describe '#execute' do - it 'should not create the index if requried posts size has not been reached' do + describe "#execute" do + it "should not create the index if requried posts size has not been reached" do SiteSetting.search_recent_posts_size = 1 SiteSetting.search_enable_recent_regular_posts_offset_size = 3 - expect do - subject.execute({}) - end.to_not change { SiteSetting.search_recent_regular_posts_offset_post_id } + expect do subject.execute({}) end.to_not change { + SiteSetting.search_recent_regular_posts_offset_post_id + } end - it 'should create the right index' do + it "should create the right index" do SiteSetting.search_recent_posts_size = 1 SiteSetting.search_enable_recent_regular_posts_offset_size = 1 diff --git a/spec/jobs/create_user_reviewable_spec.rb b/spec/jobs/create_user_reviewable_spec.rb index 1a9634af94..3c9a2c2b37 100644 --- a/spec/jobs/create_user_reviewable_spec.rb +++ b/spec/jobs/create_user_reviewable_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true RSpec.describe Jobs::CreateUserReviewable do - let(:user) { Fabricate(:user) } it "creates the reviewable" do @@ -11,9 +10,9 @@ RSpec.describe Jobs::CreateUserReviewable do reviewable = Reviewable.find_by(target: user) expect(reviewable).to be_present expect(reviewable.pending?).to eq(true) - expect(reviewable.payload['username']).to eq(user.username) - expect(reviewable.payload['name']).to eq(user.name) - expect(reviewable.payload['email']).to eq(user.email) + expect(reviewable.payload["username"]).to eq(user.username) + expect(reviewable.payload["name"]).to eq(user.name) + expect(reviewable.payload["email"]).to eq(user.email) end it "should not raise an error if there is a reviewable already" do @@ -37,7 +36,7 @@ RSpec.describe Jobs::CreateUserReviewable do reviewable = Reviewable.find_by(target: user) score = reviewable.reviewable_scores.first expect(score).to be_present - expect(score.reason).to eq('must_approve_users') + expect(score.reason).to eq("must_approve_users") end it "adds invite_only if enabled" do @@ -46,8 +45,7 @@ RSpec.describe Jobs::CreateUserReviewable do reviewable = Reviewable.find_by(target: user) score = reviewable.reviewable_scores.first expect(score).to be_present - expect(score.reason).to eq('invite_only') + expect(score.reason).to eq("invite_only") end end - end diff --git a/spec/jobs/dashboard_stats_spec.rb b/spec/jobs/dashboard_stats_spec.rb index 3f2304db9c..82aeaab122 100644 --- a/spec/jobs/dashboard_stats_spec.rb +++ b/spec/jobs/dashboard_stats_spec.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true RSpec.describe ::Jobs::DashboardStats do - let(:group_message) { GroupMessage.new(Group[:admins].name, :dashboard_problems, limit_once_per: 7.days.to_i) } + let(:group_message) do + GroupMessage.new(Group[:admins].name, :dashboard_problems, limit_once_per: 7.days.to_i) + end def clear_recently_sent! # We won't immediately create new PMs due to the limit_once_per option, reset the value for testing purposes. Discourse.redis.del(group_message.sent_recently_key) end - after do - clear_recently_sent! - end + after { clear_recently_sent! } - it 'creates group message when problems are persistent for 2 days' do + it "creates group message when problems are persistent for 2 days" do Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, Time.zone.now.to_s) expect { described_class.new.execute({}) }.not_to change { Topic.count } @@ -20,7 +20,7 @@ RSpec.describe ::Jobs::DashboardStats do expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) end - it 'replaces old message' do + it "replaces old message" do Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 3.days.ago) expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) old_topic = Topic.last @@ -32,14 +32,14 @@ RSpec.describe ::Jobs::DashboardStats do expect(new_topic.title).to eq(old_topic.title) end - it 'respects the sent_recently? check when deleting previous message' do + it "respects the sent_recently? check when deleting previous message" do Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 3.days.ago) expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) expect { described_class.new.execute({}) }.not_to change { Topic.count } end - it 'duplicates message if previous one has replies' do + it "duplicates message if previous one has replies" do Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 3.days.ago) expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) clear_recently_sent! @@ -48,7 +48,7 @@ RSpec.describe ::Jobs::DashboardStats do expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) end - it 'duplicates message if previous was 3 months ago' do + it "duplicates message if previous was 3 months ago" do freeze_time 3.months.ago do Discourse.redis.setex(AdminDashboardData.problems_started_key, 14.days.to_i, 3.days.ago) expect { described_class.new.execute({}) }.to change { Topic.count }.by(1) diff --git a/spec/jobs/delete_replies_spec.rb b/spec/jobs/delete_replies_spec.rb index 7bd9e219db..cab17ca543 100644 --- a/spec/jobs/delete_replies_spec.rb +++ b/spec/jobs/delete_replies_spec.rb @@ -5,21 +5,26 @@ RSpec.describe Jobs::DeleteReplies do fab!(:topic) { Fabricate(:topic) } fab!(:topic_timer) do - Fabricate(:topic_timer, status_type: TopicTimer.types[:delete_replies], duration_minutes: 2880, user: admin, topic: topic, execute_at: 2.days.from_now) + Fabricate( + :topic_timer, + status_type: TopicTimer.types[:delete_replies], + duration_minutes: 2880, + user: admin, + topic: topic, + execute_at: 2.days.from_now, + ) end - before do - 3.times { create_post(topic: topic) } - end + before { 3.times { create_post(topic: topic) } } it "can delete replies of a topic" do SiteSetting.skip_auto_delete_reply_likes = 0 freeze_time (2.days.from_now) - expect { - described_class.new.execute(topic_timer_id: topic_timer.id) - }.to change { topic.posts.count }.by(-2) + expect { described_class.new.execute(topic_timer_id: topic_timer.id) }.to change { + topic.posts.count + }.by(-2) topic_timer.reload expect(topic_timer.execute_at).to eq_time(2.day.from_now) @@ -32,8 +37,8 @@ RSpec.describe Jobs::DeleteReplies do topic.posts.last.update!(like_count: SiteSetting.skip_auto_delete_reply_likes + 1) - expect { - described_class.new.execute(topic_timer_id: topic_timer.id) - }.to change { topic.posts.count }.by(-1) + expect { described_class.new.execute(topic_timer_id: topic_timer.id) }.to change { + topic.posts.count + }.by(-1) end end diff --git a/spec/jobs/delete_topic_spec.rb b/spec/jobs/delete_topic_spec.rb index a65fb3052b..18ce649fdb 100644 --- a/spec/jobs/delete_topic_spec.rb +++ b/spec/jobs/delete_topic_spec.rb @@ -3,9 +3,7 @@ RSpec.describe Jobs::DeleteTopic do fab!(:admin) { Fabricate(:admin) } - fab!(:topic) do - Fabricate(:topic_timer, user: admin).topic - end + fab!(:topic) { Fabricate(:topic_timer, user: admin).topic } let(:first_post) { create_post(topic: topic) } @@ -18,7 +16,6 @@ RSpec.describe Jobs::DeleteTopic do expect(topic.reload).to be_trashed expect(first_post.reload).to be_trashed expect(topic.reload.public_topic_timer).to eq(nil) - end it "should do nothing if topic is already deleted" do @@ -42,9 +39,7 @@ RSpec.describe Jobs::DeleteTopic do end describe "user isn't authorized to delete topics" do - let(:topic) { - Fabricate(:topic_timer, user: Fabricate(:user)).topic - } + let(:topic) { Fabricate(:topic_timer, user: Fabricate(:user)).topic } it "shouldn't delete the topic" do create_post(topic: topic) @@ -55,5 +50,4 @@ RSpec.describe Jobs::DeleteTopic do expect(topic.reload).to_not be_trashed end end - end diff --git a/spec/jobs/disable_bootstrap_mode_spec.rb b/spec/jobs/disable_bootstrap_mode_spec.rb index 3d5b8d4e13..9b53e49b28 100644 --- a/spec/jobs/disable_bootstrap_mode_spec.rb +++ b/spec/jobs/disable_bootstrap_mode_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true RSpec.describe Jobs::DisableBootstrapMode do - - describe '.execute' do + describe ".execute" do fab!(:admin) { Fabricate(:admin) } before do @@ -11,30 +10,28 @@ RSpec.describe Jobs::DisableBootstrapMode do SiteSetting.default_email_digest_frequency = 1440 end - it 'does not execute if bootstrap mode is already disabled' do + it "does not execute if bootstrap mode is already disabled" do SiteSetting.bootstrap_mode_enabled = false StaffActionLogger.any_instance.expects(:log_site_setting_change).never Jobs::DisableBootstrapMode.new.execute(user_id: admin.id) end - it 'turns off bootstrap mode if bootstrap_mode_min_users is set to 0' do + it "turns off bootstrap mode if bootstrap_mode_min_users is set to 0" do SiteSetting.bootstrap_mode_min_users = 0 StaffActionLogger.any_instance.expects(:log_site_setting_change).times(3) Jobs::DisableBootstrapMode.new.execute(user_id: admin.id) end - it 'does not amend setting that is not in bootstrap state' do + it "does not amend setting that is not in bootstrap state" do SiteSetting.bootstrap_mode_min_users = 0 SiteSetting.default_trust_level = TrustLevel[3] StaffActionLogger.any_instance.expects(:log_site_setting_change).twice Jobs::DisableBootstrapMode.new.execute(user_id: admin.id) end - it 'successfully turns off bootstrap mode' do + it "successfully turns off bootstrap mode" do SiteSetting.bootstrap_mode_min_users = 5 - 6.times do - Fabricate(:user) - end + 6.times { Fabricate(:user) } StaffActionLogger.any_instance.expects(:log_site_setting_change).times(3) Jobs::DisableBootstrapMode.new.execute(user_id: admin.id) end diff --git a/spec/jobs/download_avatar_from_url_spec.rb b/spec/jobs/download_avatar_from_url_spec.rb index 36ab8d446d..a3c36d257f 100644 --- a/spec/jobs/download_avatar_from_url_spec.rb +++ b/spec/jobs/download_avatar_from_url_spec.rb @@ -3,13 +3,10 @@ RSpec.describe Jobs::DownloadAvatarFromUrl do fab!(:user) { Fabricate(:user) } - describe 'when url is invalid' do - it 'should not raise any error' do + describe "when url is invalid" do + it "should not raise any error" do expect do - described_class.new.execute( - url: '/assets/something/nice.jpg', - user_id: user.id - ) + described_class.new.execute(url: "/assets/something/nice.jpg", user_id: user.id) end.to_not raise_error end end diff --git a/spec/jobs/download_backup_email_spec.rb b/spec/jobs/download_backup_email_spec.rb index 9b3325af2f..efafdfba60 100644 --- a/spec/jobs/download_backup_email_spec.rb +++ b/spec/jobs/download_backup_email_spec.rb @@ -4,17 +4,16 @@ RSpec.describe Jobs::DownloadBackupEmail do fab!(:user) { Fabricate(:admin) } it "should work" do - described_class.new.execute( - user_id: user.id, - backup_file_path: "http://some.example.test/" - ) + described_class.new.execute(user_id: user.id, backup_file_path: "http://some.example.test/") email = ActionMailer::Base.deliveries.last - expect(email.subject).to eq(I18n.t('download_backup_mailer.subject_template', - email_prefix: SiteSetting.title - )) + expect(email.subject).to eq( + I18n.t("download_backup_mailer.subject_template", email_prefix: SiteSetting.title), + ) - expect(email.body.raw_source).to include("http://some.example.test/?token=#{EmailBackupToken.get(user.id)}") + expect(email.body.raw_source).to include( + "http://some.example.test/?token=#{EmailBackupToken.get(user.id)}", + ) end end diff --git a/spec/jobs/download_profile_background_from_url_spec.rb b/spec/jobs/download_profile_background_from_url_spec.rb index 1870aa1062..0fbf682a84 100644 --- a/spec/jobs/download_profile_background_from_url_spec.rb +++ b/spec/jobs/download_profile_background_from_url_spec.rb @@ -3,13 +3,10 @@ RSpec.describe Jobs::DownloadProfileBackgroundFromUrl do fab!(:user) { Fabricate(:user) } - describe 'when url is invalid' do - it 'should not raise any error' do + describe "when url is invalid" do + it "should not raise any error" do expect do - described_class.new.execute( - url: '/assets/something/nice.jpg', - user_id: user.id - ) + described_class.new.execute(url: "/assets/something/nice.jpg", user_id: user.id) end.to_not raise_error end end diff --git a/spec/jobs/emit_web_hook_event_spec.rb b/spec/jobs/emit_web_hook_event_spec.rb index 5abe40e996..efddb33a04 100644 --- a/spec/jobs/emit_web_hook_event_spec.rb +++ b/spec/jobs/emit_web_hook_event_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'excon' +require "excon" RSpec.describe Jobs::EmitWebHookEvent do fab!(:post_hook) { Fabricate(:web_hook) } @@ -8,22 +8,22 @@ RSpec.describe Jobs::EmitWebHookEvent do fab!(:post) { Fabricate(:post) } fab!(:user) { Fabricate(:user) } - it 'raises an error when there is no web hook record' do - expect do - subject.execute(event_type: 'post', payload: {}) - end.to raise_error(Discourse::InvalidParameters) + it "raises an error when there is no web hook record" do + expect do subject.execute(event_type: "post", payload: {}) end.to raise_error( + Discourse::InvalidParameters, + ) end - it 'raises an error when there is no event type' do - expect do - subject.execute(web_hook_id: post_hook.id, payload: {}) - end.to raise_error(Discourse::InvalidParameters) + it "raises an error when there is no event type" do + expect do subject.execute(web_hook_id: post_hook.id, payload: {}) end.to raise_error( + Discourse::InvalidParameters, + ) end - it 'raises an error when there is no payload' do - expect do - subject.execute(web_hook_id: post_hook.id, event_type: 'post') - end.to raise_error(Discourse::InvalidParameters) + it "raises an error when there is no payload" do + expect do subject.execute(web_hook_id: post_hook.id, event_type: "post") end.to raise_error( + Discourse::InvalidParameters, + ) end it "should not destroy webhook event in case of error" do @@ -32,139 +32,127 @@ RSpec.describe Jobs::EmitWebHookEvent do subject.execute( web_hook_id: post_hook.id, payload: { id: post.id }.to_json, - event_type: WebHookEventType::POST + event_type: WebHookEventType::POST, ) expect(WebHookEvent.last.web_hook_id).to eq(post_hook.id) end - context 'when the web hook is failed' do - before do - SiteSetting.retry_web_hook_events = true - end + context "when the web hook is failed" do + before { SiteSetting.retry_web_hook_events = true } - context 'when the webhook has failed for 404 or 410' do + context "when the webhook has failed for 404 or 410" do before do - stub_request(:post, post_hook.payload_url).to_return(body: 'Invalid Access', status: response_status) + stub_request(:post, post_hook.payload_url).to_return( + body: "Invalid Access", + status: response_status, + ) end let(:response_status) { 410 } - it 'disables the webhook' do + it "disables the webhook" do expect do subject.execute( web_hook_id: post_hook.id, event_type: described_class::PING_EVENT, - retry_count: described_class::MAX_RETRY_COUNT + retry_count: described_class::MAX_RETRY_COUNT, ) end.to change { post_hook.reload.active }.to(false) end - it 'logs webhook deactivation reason' do + it "logs webhook deactivation reason" do subject.execute( web_hook_id: post_hook.id, event_type: described_class::PING_EVENT, - retry_count: described_class::MAX_RETRY_COUNT + retry_count: described_class::MAX_RETRY_COUNT, ) - user_history = UserHistory.find_by(action: UserHistory.actions[:web_hook_deactivate], acting_user: Discourse.system_user) + user_history = + UserHistory.find_by( + action: UserHistory.actions[:web_hook_deactivate], + acting_user: Discourse.system_user, + ) expect(user_history).to be_present - expect(user_history.context).to eq([ - "webhook_id: #{post_hook.id}", - "webhook_response_status: #{response_status}" - ].to_s) + expect(user_history.context).to eq( + ["webhook_id: #{post_hook.id}", "webhook_response_status: #{response_status}"].to_s, + ) end end - context 'when the webhook has failed' do + context "when the webhook has failed" do before do - stub_request(:post, post_hook.payload_url).to_return(body: 'Invalid Access', status: 403) + stub_request(:post, post_hook.payload_url).to_return(body: "Invalid Access", status: 403) end - it 'retry if site setting is enabled' do + it "retry if site setting is enabled" do expect do - subject.execute( - web_hook_id: post_hook.id, - event_type: described_class::PING_EVENT - ) + subject.execute(web_hook_id: post_hook.id, event_type: described_class::PING_EVENT) end.to change { Jobs::EmitWebHookEvent.jobs.size }.by(1) end - it 'retries at most 5 times' do + it "retries at most 5 times" do Jobs.run_immediately! expect(Jobs::EmitWebHookEvent::MAX_RETRY_COUNT + 1).to eq(5) expect do - subject.execute( - web_hook_id: post_hook.id, - event_type: described_class::PING_EVENT - ) + subject.execute(web_hook_id: post_hook.id, event_type: described_class::PING_EVENT) end.to change { WebHookEvent.count }.by(Jobs::EmitWebHookEvent::MAX_RETRY_COUNT + 1) end - it 'does not retry for more than maximum allowed times' do + it "does not retry for more than maximum allowed times" do expect do subject.execute( web_hook_id: post_hook.id, event_type: described_class::PING_EVENT, - retry_count: described_class::MAX_RETRY_COUNT + retry_count: described_class::MAX_RETRY_COUNT, ) end.to_not change { Jobs::EmitWebHookEvent.jobs.size } end - it 'does not retry if site setting is disabled' do + it "does not retry if site setting is disabled" do SiteSetting.retry_web_hook_events = false expect do - subject.execute( - web_hook_id: post_hook.id, - event_type: described_class::PING_EVENT - ) + subject.execute(web_hook_id: post_hook.id, event_type: described_class::PING_EVENT) end.not_to change { Jobs::EmitWebHookEvent.jobs.size } end - it 'properly logs error on rescue' do + it "properly logs error on rescue" do stub_request(:post, post_hook.payload_url).to_raise("connection error") - subject.execute( - web_hook_id: post_hook.id, - event_type: described_class::PING_EVENT - ) + subject.execute(web_hook_id: post_hook.id, event_type: described_class::PING_EVENT) event = WebHookEvent.last - expect(event.payload).to eq(MultiJson.dump(ping: 'OK')) + expect(event.payload).to eq(MultiJson.dump(ping: "OK")) expect(event.status).to eq(-1) - expect(MultiJson.load(event.response_headers)['error']).to eq('connection error') + expect(MultiJson.load(event.response_headers)["error"]).to eq("connection error") end end end - it 'does not raise an error for a ping event without payload' do - stub_request(:post, post_hook.payload_url) - .to_return(body: 'OK', status: 200) + it "does not raise an error for a ping event without payload" do + stub_request(:post, post_hook.payload_url).to_return(body: "OK", status: 200) - subject.execute( - web_hook_id: post_hook.id, - event_type: described_class::PING_EVENT - ) + subject.execute(web_hook_id: post_hook.id, event_type: described_class::PING_EVENT) end it "doesn't emit when the hook is inactive" do subject.execute( web_hook_id: inactive_hook.id, - event_type: 'post', - payload: { test: "some payload" }.to_json + event_type: "post", + payload: { test: "some payload" }.to_json, ) end - it 'emits normally with sufficient arguments' do - stub_request(:post, post_hook.payload_url) - .with(body: "{\"post\":{\"test\":\"some payload\"}}") - .to_return(body: 'OK', status: 200) + it "emits normally with sufficient arguments" do + stub_request(:post, post_hook.payload_url).with( + body: "{\"post\":{\"test\":\"some payload\"}}", + ).to_return(body: "OK", status: 200) subject.execute( web_hook_id: post_hook.id, - event_type: 'post', - payload: { test: "some payload" }.to_json + event_type: "post", + payload: { test: "some payload" }.to_json, ) end @@ -172,17 +160,19 @@ RSpec.describe Jobs::EmitWebHookEvent do FinalDestination::TestHelper.stub_to_fail do subject.execute( web_hook_id: post_hook.id, - event_type: 'post', - payload: { test: "some payload" }.to_json + event_type: "post", + payload: { test: "some payload" }.to_json, ) end event = post_hook.web_hook_events.last - expect(event.response_headers).to eq({ error: I18n.t("webhooks.payload_url.blocked_or_internal") }.to_json) + expect(event.response_headers).to eq( + { error: I18n.t("webhooks.payload_url.blocked_or_internal") }.to_json, + ) expect(event.response_body).to eq(nil) expect(event.status).to eq(-1) end - context 'with category filters' do + context "with category filters" do fab!(:category) { Fabricate(:category) } fab!(:topic) { Fabricate(:topic) } fab!(:topic_with_category) { Fabricate(:topic, category_id: category.id) } @@ -191,27 +181,27 @@ RSpec.describe Jobs::EmitWebHookEvent do it "doesn't emit when event is not related with defined categories" do subject.execute( web_hook_id: topic_hook.id, - event_type: 'topic', + event_type: "topic", category_id: topic.category.id, - payload: { test: "some payload" }.to_json + payload: { test: "some payload" }.to_json, ) end - it 'emit when event is related with defined categories' do - stub_request(:post, post_hook.payload_url) - .with(body: "{\"topic\":{\"test\":\"some payload\"}}") - .to_return(body: 'OK', status: 200) + it "emit when event is related with defined categories" do + stub_request(:post, post_hook.payload_url).with( + body: "{\"topic\":{\"test\":\"some payload\"}}", + ).to_return(body: "OK", status: 200) subject.execute( web_hook_id: topic_hook.id, - event_type: 'topic', + event_type: "topic", category_id: topic_with_category.category.id, - payload: { test: "some payload" }.to_json + payload: { test: "some payload" }.to_json, ) end end - context 'with tag filters' do + context "with tag filters" do fab!(:tag) { Fabricate(:tag) } fab!(:topic) { Fabricate(:topic, tags: [tag]) } fab!(:topic_hook) { Fabricate(:topic_web_hook, tags: [tag]) } @@ -219,35 +209,35 @@ RSpec.describe Jobs::EmitWebHookEvent do it "doesn't emit when event is not included any tags" do subject.execute( web_hook_id: topic_hook.id, - event_type: 'topic', - payload: { test: "some payload" }.to_json + event_type: "topic", + payload: { test: "some payload" }.to_json, ) end it "doesn't emit when event is not related with defined tags" do subject.execute( web_hook_id: topic_hook.id, - event_type: 'topic', + event_type: "topic", tag_ids: [Fabricate(:tag).id], - payload: { test: "some payload" }.to_json + payload: { test: "some payload" }.to_json, ) end - it 'emit when event is related with defined tags' do - stub_request(:post, post_hook.payload_url) - .with(body: "{\"topic\":{\"test\":\"some payload\"}}") - .to_return(body: 'OK', status: 200) + it "emit when event is related with defined tags" do + stub_request(:post, post_hook.payload_url).with( + body: "{\"topic\":{\"test\":\"some payload\"}}", + ).to_return(body: "OK", status: 200) subject.execute( web_hook_id: topic_hook.id, - event_type: 'topic', + event_type: "topic", tag_ids: topic.tags.pluck(:id), - payload: { test: "some payload" }.to_json + payload: { test: "some payload" }.to_json, ) end end - context 'with group filters' do + context "with group filters" do fab!(:group) { Fabricate(:group) } fab!(:user) { Fabricate(:user, groups: [group]) } fab!(:like_hook) { Fabricate(:like_web_hook, groups: [group]) } @@ -255,38 +245,37 @@ RSpec.describe Jobs::EmitWebHookEvent do it "doesn't emit when event is not included any groups" do subject.execute( web_hook_id: like_hook.id, - event_type: 'like', - payload: { test: "some payload" }.to_json + event_type: "like", + payload: { test: "some payload" }.to_json, ) end it "doesn't emit when event is not related with defined groups" do subject.execute( web_hook_id: like_hook.id, - event_type: 'like', + event_type: "like", group_ids: [Fabricate(:group).id], - payload: { test: "some payload" }.to_json + payload: { test: "some payload" }.to_json, ) end - it 'emit when event is related with defined groups' do - stub_request(:post, like_hook.payload_url) - .with(body: "{\"like\":{\"test\":\"some payload\"}}") - .to_return(body: 'OK', status: 200) + it "emit when event is related with defined groups" do + stub_request(:post, like_hook.payload_url).with( + body: "{\"like\":{\"test\":\"some payload\"}}", + ).to_return(body: "OK", status: 200) subject.execute( web_hook_id: like_hook.id, - event_type: 'like', + event_type: "like", group_ids: user.groups.pluck(:id), - payload: { test: "some payload" }.to_json + payload: { test: "some payload" }.to_json, ) end end - describe '#send_webhook!' do - it 'creates delivery event record' do - stub_request(:post, post_hook.payload_url) - .to_return(body: 'OK', status: 200) + describe "#send_webhook!" do + it "creates delivery event record" do + stub_request(:post, post_hook.payload_url).to_return(body: "OK", status: 200) topic_event_type = WebHookEventType.all.first web_hook_id = Fabricate("#{topic_event_type.name}_web_hook").id @@ -295,55 +284,64 @@ RSpec.describe Jobs::EmitWebHookEvent do subject.execute( web_hook_id: web_hook_id, event_type: topic_event_type.name, - payload: { test: "some payload" }.to_json + payload: { test: "some payload" }.to_json, ) end.to change(WebHookEvent, :count).by(1) end - it 'sets up proper request headers' do - stub_request(:post, post_hook.payload_url) - .to_return(headers: { test: 'string' }, body: 'OK', status: 200) + it "sets up proper request headers" do + stub_request(:post, post_hook.payload_url).to_return( + headers: { + test: "string", + }, + body: "OK", + status: 200, + ) subject.execute( web_hook_id: post_hook.id, event_type: described_class::PING_EVENT, event_name: described_class::PING_EVENT, - payload: { test: "this payload shouldn't appear" }.to_json + payload: { test: "this payload shouldn't appear" }.to_json, ) event = WebHookEvent.last headers = MultiJson.load(event.headers) - expect(headers['Content-Length']).to eq("13") - expect(headers['Host']).to eq("meta.discourse.org") - expect(headers['X-Discourse-Event-Id']).to eq(event.id.to_s) - expect(headers['X-Discourse-Event-Type']).to eq(described_class::PING_EVENT) - expect(headers['X-Discourse-Event']).to eq(described_class::PING_EVENT) - expect(headers['X-Discourse-Event-Signature']).to eq('sha256=162f107f6b5022353274eb1a7197885cfd35744d8d08e5bcea025d309386b7d6') - expect(event.payload).to eq(MultiJson.dump(ping: 'OK')) + expect(headers["Content-Length"]).to eq("13") + expect(headers["Host"]).to eq("meta.discourse.org") + expect(headers["X-Discourse-Event-Id"]).to eq(event.id.to_s) + expect(headers["X-Discourse-Event-Type"]).to eq(described_class::PING_EVENT) + expect(headers["X-Discourse-Event"]).to eq(described_class::PING_EVENT) + expect(headers["X-Discourse-Event-Signature"]).to eq( + "sha256=162f107f6b5022353274eb1a7197885cfd35744d8d08e5bcea025d309386b7d6", + ) + expect(event.payload).to eq(MultiJson.dump(ping: "OK")) expect(event.status).to eq(200) - expect(MultiJson.load(event.response_headers)['test']).to eq('string') - expect(event.response_body).to eq('OK') + expect(MultiJson.load(event.response_headers)["test"]).to eq("string") + expect(event.response_body).to eq("OK") end - it 'sets up proper request headers when an error raised' do + it "sets up proper request headers when an error raised" do stub_request(:post, post_hook.payload_url).to_raise("error") subject.execute( web_hook_id: post_hook.id, event_type: described_class::PING_EVENT, event_name: described_class::PING_EVENT, - payload: { test: "this payload shouldn't appear" }.to_json + payload: { test: "this payload shouldn't appear" }.to_json, ) event = WebHookEvent.last headers = MultiJson.load(event.headers) - expect(headers['Content-Length']).to eq("13") - expect(headers['Host']).to eq("meta.discourse.org") - expect(headers['X-Discourse-Event-Id']).to eq(event.id.to_s) - expect(headers['X-Discourse-Event-Type']).to eq(described_class::PING_EVENT) - expect(headers['X-Discourse-Event']).to eq(described_class::PING_EVENT) - expect(headers['X-Discourse-Event-Signature']).to eq('sha256=162f107f6b5022353274eb1a7197885cfd35744d8d08e5bcea025d309386b7d6') - expect(event.payload).to eq(MultiJson.dump(ping: 'OK')) + expect(headers["Content-Length"]).to eq("13") + expect(headers["Host"]).to eq("meta.discourse.org") + expect(headers["X-Discourse-Event-Id"]).to eq(event.id.to_s) + expect(headers["X-Discourse-Event-Type"]).to eq(described_class::PING_EVENT) + expect(headers["X-Discourse-Event"]).to eq(described_class::PING_EVENT) + expect(headers["X-Discourse-Event-Signature"]).to eq( + "sha256=162f107f6b5022353274eb1a7197885cfd35744d8d08e5bcea025d309386b7d6", + ) + expect(event.payload).to eq(MultiJson.dump(ping: "OK")) end end end diff --git a/spec/jobs/enable_bootstrap_mode_spec.rb b/spec/jobs/enable_bootstrap_mode_spec.rb index 583caa041e..faaa5f8215 100644 --- a/spec/jobs/enable_bootstrap_mode_spec.rb +++ b/spec/jobs/enable_bootstrap_mode_spec.rb @@ -1,37 +1,36 @@ # frozen_string_literal: true RSpec.describe Jobs::EnableBootstrapMode do - - describe '.execute' do + describe ".execute" do fab!(:admin) { Fabricate(:admin) } - before do - SiteSetting.bootstrap_mode_enabled = false + before { SiteSetting.bootstrap_mode_enabled = false } + + it "raises an error when user_id is missing" do + expect { Jobs::EnableBootstrapMode.new.execute({}) }.to raise_error( + Discourse::InvalidParameters, + ) end - it 'raises an error when user_id is missing' do - expect { Jobs::EnableBootstrapMode.new.execute({}) }.to raise_error(Discourse::InvalidParameters) - end - - it 'does not execute if bootstrap mode is already enabled' do + it "does not execute if bootstrap mode is already enabled" do SiteSetting.bootstrap_mode_enabled = true StaffActionLogger.any_instance.expects(:log_site_setting_change).never Jobs::EnableBootstrapMode.new.execute(user_id: admin.id) end - it 'does not turn on bootstrap mode if first admin already exists' do + it "does not turn on bootstrap mode if first admin already exists" do first_admin = Fabricate(:admin) StaffActionLogger.any_instance.expects(:log_site_setting_change).never Jobs::EnableBootstrapMode.new.execute(user_id: admin.id) end - it 'does not amend setting that is not in default state' do + it "does not amend setting that is not in default state" do SiteSetting.default_trust_level = TrustLevel[3] StaffActionLogger.any_instance.expects(:log_site_setting_change).twice Jobs::EnableBootstrapMode.new.execute(user_id: admin.id) end - it 'successfully turns on bootstrap mode' do + it "successfully turns on bootstrap mode" do StaffActionLogger.any_instance.expects(:log_site_setting_change).times(3) Jobs::EnableBootstrapMode.new.execute(user_id: admin.id) end diff --git a/spec/jobs/enqueue_digest_emails_spec.rb b/spec/jobs/enqueue_digest_emails_spec.rb index 82b18c8c64..681060b9a6 100644 --- a/spec/jobs/enqueue_digest_emails_spec.rb +++ b/spec/jobs/enqueue_digest_emails_spec.rb @@ -1,54 +1,74 @@ # frozen_string_literal: true RSpec.describe Jobs::EnqueueDigestEmails do - - describe '#target_users' do - context 'with disabled digests' do + describe "#target_users" do + context "with disabled digests" do before { SiteSetting.default_email_digest_frequency = 0 } - let!(:user_no_digests) { Fabricate(:active_user, last_emailed_at: 8.days.ago, last_seen_at: 10.days.ago) } + let!(:user_no_digests) do + Fabricate(:active_user, last_emailed_at: 8.days.ago, last_seen_at: 10.days.ago) + end it "doesn't return users with email disabled" do - expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(user_no_digests.id)).to eq(false) + expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(user_no_digests.id)).to eq( + false, + ) end end - context 'with unapproved users' do - before do - SiteSetting.must_approve_users = true + context "with unapproved users" do + before { SiteSetting.must_approve_users = true } + + let!(:unapproved_user) do + Fabricate( + :active_user, + approved: false, + last_emailed_at: 8.days.ago, + last_seen_at: 10.days.ago, + ) end - let!(:unapproved_user) { Fabricate(:active_user, approved: false, last_emailed_at: 8.days.ago, last_seen_at: 10.days.ago) } - - it 'should enqueue the right digest emails' do - expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(unapproved_user.id)).to eq(false) + it "should enqueue the right digest emails" do + expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(unapproved_user.id)).to eq( + false, + ) # As a moderator unapproved_user.update_column(:moderator, true) - expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(unapproved_user.id)).to eq(true) + expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(unapproved_user.id)).to eq( + true, + ) # As an admin unapproved_user.update(admin: true, moderator: false) - expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(unapproved_user.id)).to eq(true) + expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(unapproved_user.id)).to eq( + true, + ) # As an approved user unapproved_user.update(admin: false, moderator: false, approved: true) - expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(unapproved_user.id)).to eq(true) + expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(unapproved_user.id)).to eq( + true, + ) end end - context 'with staged users' do - let!(:staged_user) { Fabricate(:active_user, staged: true, last_emailed_at: 1.year.ago, last_seen_at: 1.year.ago) } + context "with staged users" do + let!(:staged_user) do + Fabricate(:active_user, staged: true, last_emailed_at: 1.year.ago, last_seen_at: 1.year.ago) + end it "doesn't return staged users" do expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(staged_user.id)).to eq(false) end end - context 'when recently emailed' do + context "when recently emailed" do let!(:user_emailed_recently) { Fabricate(:active_user, last_emailed_at: 6.days.ago) } it "doesn't return users who have been emailed recently" do - expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(user_emailed_recently.id)).to eq(false) + expect( + Jobs::EnqueueDigestEmails.new.target_user_ids.include?(user_emailed_recently.id), + ).to eq(false) end end @@ -56,19 +76,25 @@ RSpec.describe Jobs::EnqueueDigestEmails do let!(:inactive_user) { Fabricate(:user, active: false) } it "doesn't return users who have been emailed recently" do - expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(inactive_user.id)).to eq(false) + expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(inactive_user.id)).to eq( + false, + ) end end context "with suspended user" do - let!(:suspended_user) { Fabricate(:user, suspended_till: 1.week.from_now, suspended_at: 1.day.ago) } + let!(:suspended_user) do + Fabricate(:user, suspended_till: 1.week.from_now, suspended_at: 1.day.ago) + end it "doesn't return users who are suspended" do - expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(suspended_user.id)).to eq(false) + expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(suspended_user.id)).to eq( + false, + ) end end - context 'when visited the site this week' do + context "when visited the site this week" do let(:user_visited_this_week) { Fabricate(:active_user, last_seen_at: 6.days.ago) } it "doesn't return users who have been emailed recently" do @@ -77,23 +103,30 @@ RSpec.describe Jobs::EnqueueDigestEmails do end end - context 'when visited the site a year ago' do + context "when visited the site a year ago" do let!(:user_visited_a_year_ago) { Fabricate(:active_user, last_seen_at: 370.days.ago) } it "doesn't return the user who have not visited the site for more than 365 days" do - expect(Jobs::EnqueueDigestEmails.new.target_user_ids.include?(user_visited_a_year_ago.id)).to eq(false) + expect( + Jobs::EnqueueDigestEmails.new.target_user_ids.include?(user_visited_a_year_ago.id), + ).to eq(false) end end - context 'with regular users' do - let!(:user) { Fabricate(:active_user, last_seen_at: (SiteSetting.suppress_digest_email_after_days - 1).days.ago) } + context "with regular users" do + let!(:user) do + Fabricate( + :active_user, + last_seen_at: (SiteSetting.suppress_digest_email_after_days - 1).days.ago, + ) + end it "returns the user" do expect(Jobs::EnqueueDigestEmails.new.target_user_ids).to eq([user.id]) end end - context 'with too many bounces' do + context "with too many bounces" do let!(:bounce_user) { Fabricate(:active_user, last_seen_at: 6.month.ago) } it "doesn't return users with too many bounces" do @@ -112,7 +145,7 @@ RSpec.describe Jobs::EnqueueDigestEmails do end end - describe '#execute' do + describe "#execute" do let(:user) { Fabricate(:user) } it "limits jobs enqueued per max_digests_enqueued_per_30_mins_per_site" do @@ -125,27 +158,29 @@ RSpec.describe Jobs::EnqueueDigestEmails do global_setting :max_digests_enqueued_per_30_mins_per_site, 1 expect_enqueued_with(job: :user_email, args: { type: :digest, user_id: user2.id }) do - expect { Jobs::EnqueueDigestEmails.new.execute(nil) }.to change(Jobs::UserEmail.jobs, :size).by (1) + expect { Jobs::EnqueueDigestEmails.new.execute(nil) }.to change( + Jobs::UserEmail.jobs, + :size, + ).by (1) end # The job didn't actually run, so fake the user_stat update user2.user_stat.update(digest_attempted_at: Time.zone.now) expect_enqueued_with(job: :user_email, args: { type: :digest, user_id: user1.id }) do - expect { Jobs::EnqueueDigestEmails.new.execute(nil) }.to change(Jobs::UserEmail.jobs, :size).by (1) + expect { Jobs::EnqueueDigestEmails.new.execute(nil) }.to change( + Jobs::UserEmail.jobs, + :size, + ).by (1) end user1.user_stat.update(digest_attempted_at: Time.zone.now) - expect_not_enqueued_with(job: :user_email) do - Jobs::EnqueueDigestEmails.new.execute(nil) - end + expect_not_enqueued_with(job: :user_email) { Jobs::EnqueueDigestEmails.new.execute(nil) } end context "when digest emails are enabled" do - before do - Jobs::EnqueueDigestEmails.any_instance.expects(:target_user_ids).returns([user.id]) - end + before { Jobs::EnqueueDigestEmails.any_instance.expects(:target_user_ids).returns([user.id]) } it "enqueues the digest email job" do SiteSetting.disable_digest_emails = false diff --git a/spec/jobs/enqueue_suspect_users_spec.rb b/spec/jobs/enqueue_suspect_users_spec.rb index d1c52285ba..125e2e0de4 100644 --- a/spec/jobs/enqueue_suspect_users_spec.rb +++ b/spec/jobs/enqueue_suspect_users_spec.rb @@ -3,27 +3,28 @@ RSpec.describe Jobs::EnqueueSuspectUsers do before { SiteSetting.approve_suspect_users = true } - it 'does nothing when there are no suspect users' do + it "does nothing when there are no suspect users" do subject.execute({}) expect(ReviewableUser.count).to be_zero end - context 'with suspect users' do + context "with suspect users" do let!(:suspect_user) { Fabricate(:active_user, created_at: 1.day.ago) } - it 'creates a reviewable when there is a suspect user' do + it "creates a reviewable when there is a suspect user" do subject.execute({}) expect(ReviewableUser.count).to eq(1) end - it 'only creates one reviewable per user' do - review_user = ReviewableUser.needs_review!( - target: suspect_user, - created_by: Discourse.system_user, - reviewable_by_moderator: true - ) + it "only creates one reviewable per user" do + review_user = + ReviewableUser.needs_review!( + target: suspect_user, + created_by: Discourse.system_user, + reviewable_by_moderator: true, + ) subject.execute({}) @@ -31,14 +32,14 @@ RSpec.describe Jobs::EnqueueSuspectUsers do expect(ReviewableUser.last).to eq(review_user) end - it 'adds a score' do + it "adds a score" do subject.execute({}) score = ReviewableScore.last - expect(score.reason).to eq('suspect_user') + expect(score.reason).to eq("suspect_user") end - it 'only enqueues non-approved users' do + it "only enqueues non-approved users" do suspect_user.update!(approved: true) subject.execute({}) @@ -46,7 +47,7 @@ RSpec.describe Jobs::EnqueueSuspectUsers do expect(ReviewableUser.where(target: suspect_user).exists?).to eq(false) end - it 'does nothing if must_approve_users is set to true' do + it "does nothing if must_approve_users is set to true" do SiteSetting.must_approve_users = true suspect_user.update!(approved: false) @@ -55,7 +56,7 @@ RSpec.describe Jobs::EnqueueSuspectUsers do expect(ReviewableUser.where(target: suspect_user).exists?).to eq(false) end - it 'ignores users created more than six months ago' do + it "ignores users created more than six months ago" do suspect_user.update!(created_at: 1.year.ago) subject.execute({}) @@ -63,40 +64,50 @@ RSpec.describe Jobs::EnqueueSuspectUsers do expect(ReviewableUser.where(target: suspect_user).exists?).to eq(false) end - it 'ignores users that were imported from another site' do - suspect_user.upsert_custom_fields({ import_id: 'fake_id' }) + it "ignores users that were imported from another site" do + suspect_user.upsert_custom_fields({ import_id: "fake_id" }) subject.execute({}) expect(ReviewableUser.where(target: suspect_user).exists?).to eq(false) end - it 'enqueues a suspect users with custom fields' do - suspect_user.upsert_custom_fields({ field_a: 'value', field_b: 'value' }) + it "enqueues a suspect users with custom fields" do + suspect_user.upsert_custom_fields({ field_a: "value", field_b: "value" }) subject.execute({}) expect(ReviewableUser.where(target: suspect_user).exists?).to eq(true) end - it 'ignores imported users even if they have multiple custom fields' do - suspect_user.upsert_custom_fields({ field_a: 'value', field_b: 'value', import_id: 'fake_id' }) + it "ignores imported users even if they have multiple custom fields" do + suspect_user.upsert_custom_fields( + { field_a: "value", field_b: "value", import_id: "fake_id" }, + ) subject.execute({}) expect(ReviewableUser.where(target: suspect_user).exists?).to eq(false) end - it 'enqueues a suspect user with not enough time read' do - suspect_user.user_stat.update!(posts_read_count: 2, topics_entered: 2, time_read: 30.seconds.to_i) + it "enqueues a suspect user with not enough time read" do + suspect_user.user_stat.update!( + posts_read_count: 2, + topics_entered: 2, + time_read: 30.seconds.to_i, + ) subject.execute({}) expect(ReviewableUser.count).to eq(1) end - it 'ignores users if their time read is higher than one minute' do - suspect_user.user_stat.update!(posts_read_count: 2, topics_entered: 2, time_read: 2.minutes.to_i) + it "ignores users if their time read is higher than one minute" do + suspect_user.user_stat.update!( + posts_read_count: 2, + topics_entered: 2, + time_read: 2.minutes.to_i, + ) subject.execute({}) diff --git a/spec/jobs/export_csv_file_spec.rb b/spec/jobs/export_csv_file_spec.rb index c126cbc9a8..e395bddec5 100644 --- a/spec/jobs/export_csv_file_spec.rb +++ b/spec/jobs/export_csv_file_spec.rb @@ -1,40 +1,43 @@ # frozen_string_literal: true RSpec.describe Jobs::ExportCsvFile do - - describe '#execute' do + describe "#execute" do let(:other_user) { Fabricate(:user) } let(:admin) { Fabricate(:admin) } let(:action_log) { StaffActionLogger.new(admin).log_revoke_moderation(other_user) } - it 'raises an error when the entity is missing' do - expect { Jobs::ExportCsvFile.new.execute(user_id: admin.id) }.to raise_error(Discourse::InvalidParameters) + it "raises an error when the entity is missing" do + expect { Jobs::ExportCsvFile.new.execute(user_id: admin.id) }.to raise_error( + Discourse::InvalidParameters, + ) end - it 'works' do + it "works" do action_log begin expect do - Jobs::ExportCsvFile.new.execute( - user_id: admin.id, - entity: "staff_action" - ) + Jobs::ExportCsvFile.new.execute(user_id: admin.id, entity: "staff_action") end.to change { Upload.count }.by(1) system_message = admin.topics_allowed.last - expect(system_message.title).to eq(I18n.t( - "system_messages.csv_export_succeeded.subject_template", - export_title: "Staff Action" - )) + expect(system_message.title).to eq( + I18n.t( + "system_messages.csv_export_succeeded.subject_template", + export_title: "Staff Action", + ), + ) upload = system_message.first_post.uploads.first - expect(system_message.first_post.raw).to eq(I18n.t( - "system_messages.csv_export_succeeded.text_body_template", - download_link: "[#{upload.original_filename}|attachment](#{upload.short_url}) (#{upload.filesize} Bytes)" - ).chomp) + expect(system_message.first_post.raw).to eq( + I18n.t( + "system_messages.csv_export_succeeded.text_body_template", + download_link: + "[#{upload.original_filename}|attachment](#{upload.short_url}) (#{upload.filesize} Bytes)", + ).chomp, + ) expect(system_message.id).to eq(UserExport.last.topic_id) expect(system_message.closed).to eq(true) @@ -51,31 +54,35 @@ RSpec.describe Jobs::ExportCsvFile do end end - describe '.report_export' do - + describe ".report_export" do let(:user) { Fabricate(:admin) } let(:exporter) do exporter = Jobs::ExportCsvFile.new - exporter.entity = 'report' - exporter.extra = HashWithIndifferentAccess.new(start_date: '2010-01-01', end_date: '2011-01-01') + exporter.entity = "report" + exporter.extra = + HashWithIndifferentAccess.new(start_date: "2010-01-01", end_date: "2011-01-01") exporter.current_user = User.find_by(id: user.id) exporter end it "does not throw an error when the dates are invalid" do Jobs::ExportCsvFile.new.execute( - entity: 'report', + entity: "report", user_id: user.id, - args: { start_date: 'asdfasdf', end_date: 'not-a-date', name: 'dau_by_mau' } + args: { + start_date: "asdfasdf", + end_date: "not-a-date", + name: "dau_by_mau", + }, ) end - it 'works with single-column reports' do - user.user_visits.create!(visited_at: '2010-01-01', posts_read: 42) - Fabricate(:user).user_visits.create!(visited_at: '2010-01-03', posts_read: 420) + it "works with single-column reports" do + user.user_visits.create!(visited_at: "2010-01-01", posts_read: 42) + Fabricate(:user).user_visits.create!(visited_at: "2010-01-03", posts_read: 420) - exporter.extra['name'] = 'dau_by_mau' + exporter.extra["name"] = "dau_by_mau" report = exporter.report_export.to_a expect(report.first).to contain_exactly("Day", "Percent") @@ -83,16 +90,16 @@ RSpec.describe Jobs::ExportCsvFile do expect(report.third).to contain_exactly("2010-01-03", "50.0") end - it 'works with filters' do - user.user_visits.create!(visited_at: '2010-01-01', posts_read: 42) + it "works with filters" do + user.user_visits.create!(visited_at: "2010-01-01", posts_read: 42) group = Fabricate(:group) user1 = Fabricate(:user) group_user = Fabricate(:group_user, group: group, user: user1) - user1.user_visits.create!(visited_at: '2010-01-03', posts_read: 420) + user1.user_visits.create!(visited_at: "2010-01-03", posts_read: 420) - exporter.extra['name'] = 'visits' - exporter.extra['group'] = group.id + exporter.extra["name"] = "visits" + exporter.extra["group"] = group.id report = exporter.report_export.to_a expect(report.length).to eq(2) @@ -100,11 +107,11 @@ RSpec.describe Jobs::ExportCsvFile do expect(report.second).to contain_exactly("2010-01-03", "1") end - it 'works with single-column reports with default label' do - user.user_visits.create!(visited_at: '2010-01-01') - Fabricate(:user).user_visits.create!(visited_at: '2010-01-03') + it "works with single-column reports with default label" do + user.user_visits.create!(visited_at: "2010-01-01") + Fabricate(:user).user_visits.create!(visited_at: "2010-01-03") - exporter.extra['name'] = 'visits' + exporter.extra["name"] = "visits" report = exporter.report_export.to_a expect(report.first).to contain_exactly("Day", "Count") @@ -112,24 +119,33 @@ RSpec.describe Jobs::ExportCsvFile do expect(report.third).to contain_exactly("2010-01-03", "1") end - it 'works with multi-columns reports' do + it "works with multi-columns reports" do DiscourseIpInfo.stubs(:get).with("1.1.1.1").returns(location: "Earth") - user.user_auth_token_logs.create!(action: "login", client_ip: "1.1.1.1", created_at: '2010-01-01') + user.user_auth_token_logs.create!( + action: "login", + client_ip: "1.1.1.1", + created_at: "2010-01-01", + ) - exporter.extra['name'] = 'staff_logins' + exporter.extra["name"] = "staff_logins" report = exporter.report_export.to_a expect(report.first).to contain_exactly("User", "Location", "Login at") expect(report.second).to contain_exactly(user.username, "Earth", "2010-01-01 00:00:00 UTC") end - it 'works with topic reports' do - freeze_time DateTime.parse('2010-01-01 6:00') + it "works with topic reports" do + freeze_time DateTime.parse("2010-01-01 6:00") - exporter.extra['name'] = 'top_referred_topics' + exporter.extra["name"] = "top_referred_topics" post1 = Fabricate(:post) post2 = Fabricate(:post) - IncomingLink.add(host: "a.com", referer: "http://twitter.com", post_id: post1.id, ip_address: '1.1.1.1') + IncomingLink.add( + host: "a.com", + referer: "http://twitter.com", + post_id: post1.id, + ip_address: "1.1.1.1", + ) report = exporter.report_export.to_a @@ -137,20 +153,20 @@ RSpec.describe Jobs::ExportCsvFile do expect(report.second).to contain_exactly(post1.topic.id.to_s, "1") end - it 'works with stacked_chart reports' do - ApplicationRequest.create!(date: '2010-01-01', req_type: 'page_view_logged_in', count: 1) - ApplicationRequest.create!(date: '2010-01-02', req_type: 'page_view_logged_in', count: 2) - ApplicationRequest.create!(date: '2010-01-03', req_type: 'page_view_logged_in', count: 3) + it "works with stacked_chart reports" do + ApplicationRequest.create!(date: "2010-01-01", req_type: "page_view_logged_in", count: 1) + ApplicationRequest.create!(date: "2010-01-02", req_type: "page_view_logged_in", count: 2) + ApplicationRequest.create!(date: "2010-01-03", req_type: "page_view_logged_in", count: 3) - ApplicationRequest.create!(date: '2010-01-01', req_type: 'page_view_anon', count: 4) - ApplicationRequest.create!(date: '2010-01-02', req_type: 'page_view_anon', count: 5) - ApplicationRequest.create!(date: '2010-01-03', req_type: 'page_view_anon', count: 6) + ApplicationRequest.create!(date: "2010-01-01", req_type: "page_view_anon", count: 4) + ApplicationRequest.create!(date: "2010-01-02", req_type: "page_view_anon", count: 5) + ApplicationRequest.create!(date: "2010-01-03", req_type: "page_view_anon", count: 6) - ApplicationRequest.create!(date: '2010-01-01', req_type: 'page_view_crawler', count: 7) - ApplicationRequest.create!(date: '2010-01-02', req_type: 'page_view_crawler', count: 8) - ApplicationRequest.create!(date: '2010-01-03', req_type: 'page_view_crawler', count: 9) + ApplicationRequest.create!(date: "2010-01-01", req_type: "page_view_crawler", count: 7) + ApplicationRequest.create!(date: "2010-01-02", req_type: "page_view_crawler", count: 8) + ApplicationRequest.create!(date: "2010-01-03", req_type: "page_view_crawler", count: 9) - exporter.extra['name'] = 'consolidated_page_views' + exporter.extra["name"] = "consolidated_page_views" report = exporter.report_export.to_a expect(report[0]).to contain_exactly("Day", "Logged in users", "Anonymous users", "Crawlers") @@ -159,37 +175,74 @@ RSpec.describe Jobs::ExportCsvFile do expect(report[3]).to contain_exactly("2010-01-03", "3", "6", "9") end - it 'works with posts reports and filters' do + it "works with posts reports and filters" do category = Fabricate(:category) subcategory = Fabricate(:category, parent_category: category) - Fabricate(:post, topic: Fabricate(:topic, category: category), created_at: '2010-01-01 12:00:00 UTC') - Fabricate(:post, topic: Fabricate(:topic, category: subcategory), created_at: '2010-01-01 12:00:00 UTC') + Fabricate( + :post, + topic: Fabricate(:topic, category: category), + created_at: "2010-01-01 12:00:00 UTC", + ) + Fabricate( + :post, + topic: Fabricate(:topic, category: subcategory), + created_at: "2010-01-01 12:00:00 UTC", + ) - exporter.extra['name'] = 'posts' + exporter.extra["name"] = "posts" - exporter.extra['category'] = category.id + exporter.extra["category"] = category.id report = exporter.report_export.to_a expect(report[0]).to contain_exactly("Count", "Day") expect(report[1]).to contain_exactly("1", "2010-01-01") - exporter.extra['include_subcategories'] = true + exporter.extra["include_subcategories"] = true report = exporter.report_export.to_a expect(report[0]).to contain_exactly("Count", "Day") expect(report[1]).to contain_exactly("2", "2010-01-01") end end - let(:user_list_header) { - %w{ - id name username email title created_at last_seen_at last_posted_at - last_emailed_at trust_level approved suspended_at suspended_till blocked - active admin moderator ip_address staged secondary_emails topics_entered - posts_read_count time_read topic_count post_count likes_given - likes_received location website views external_id external_email - external_username external_name external_avatar_url - } - } + let(:user_list_header) do + %w[ + id + name + username + email + title + created_at + last_seen_at + last_posted_at + last_emailed_at + trust_level + approved + suspended_at + suspended_till + blocked + active + admin + moderator + ip_address + staged + secondary_emails + topics_entered + posts_read_count + time_read + topic_count + post_count + likes_given + likes_received + location + website + views + external_id + external_email + external_username + external_name + external_avatar_url + ] + end let(:user_list_export) { Jobs::ExportCsvFile.new.user_list_export } @@ -207,12 +260,16 @@ RSpec.describe Jobs::ExportCsvFile do expect(user["secondary_emails"].split(";")).to match_array(secondary_emails) end - it 'exports sso data' do + it "exports sso data" do SiteSetting.discourse_connect_url = "https://www.example.com/sso" SiteSetting.enable_discourse_connect = true user = Fabricate(:user) user.user_profile.update_column(:location, "La,La Land") - user.create_single_sign_on_record(external_id: "123", last_payload: "xxx", external_email: 'test@test.com') + user.create_single_sign_on_record( + external_id: "123", + last_payload: "xxx", + external_email: "test@test.com", + ) user = to_hash(user_list_export.find { |u| u[0].to_i == user.id }) diff --git a/spec/jobs/export_user_archive_spec.rb b/spec/jobs/export_user_archive_spec.rb index 2c047791b2..ea6f91cf1f 100644 --- a/spec/jobs/export_user_archive_spec.rb +++ b/spec/jobs/export_user_archive_spec.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true -require 'csv' +require "csv" RSpec.describe Jobs::ExportUserArchive do fab!(:user) { Fabricate(:user, username: "john_doe") } fab!(:user2) { Fabricate(:user) } let(:extra) { {} } - let(:job) { + let(:job) do j = Jobs::ExportUserArchive.new j.current_user = user j.extra = extra j - } - let(:component) { raise 'component not set' } + end + let(:component) { raise "component not set" } fab!(:admin) { Fabricate(:admin) } fab!(:category) { Fabricate(:category_with_definition, name: "User Archive Category") } @@ -22,13 +22,17 @@ RSpec.describe Jobs::ExportUserArchive do def make_component_csv data_rows = [] - csv_out = CSV.generate do |csv| - csv << job.get_header(component) - job.public_send(:"#{component}_export") do |row| - csv << row - data_rows << Jobs::ExportUserArchive::HEADER_ATTRS_FOR[component].zip(row.map(&:to_s)).to_h.with_indifferent_access + csv_out = + CSV.generate do |csv| + csv << job.get_header(component) + job.public_send(:"#{component}_export") do |row| + csv << row + data_rows << Jobs::ExportUserArchive::HEADER_ATTRS_FOR[component] + .zip(row.map(&:to_s)) + .to_h + .with_indifferent_access + end end - end [data_rows, csv_out] end @@ -36,16 +40,17 @@ RSpec.describe Jobs::ExportUserArchive do JSON.parse(MultiJson.dump(job.public_send(:"#{component}_export"))) end - describe '#execute' do + describe "#execute" do before do _ = post - user.user_profile.website = 'https://doe.example.com/john' + user.user_profile.website = "https://doe.example.com/john" user.user_profile.save # force a UserAuthTokenLog entry - env = create_request_env.merge( - 'HTTP_USER_AGENT' => 'MyWebBrowser', - 'REQUEST_PATH' => '/some_path/456852', - ) + env = + create_request_env.merge( + "HTTP_USER_AGENT" => "MyWebBrowser", + "REQUEST_PATH" => "/some_path/456852", + ) cookie_jar = ActionDispatch::Request.new(env).cookie_jar Discourse.current_user_provider.new(env).log_on_user(user, {}, cookie_jar) @@ -53,34 +58,37 @@ RSpec.describe Jobs::ExportUserArchive do PostAction.new(user: user, post: post, post_action_type_id: 5).save end - after do - user.uploads.each(&:destroy!) + after { user.uploads.each(&:destroy!) } + + it "raises an error when the user is missing" do + expect { Jobs::ExportCsvFile.new.execute(user_id: user.id + (1 << 20)) }.to raise_error( + Discourse::InvalidParameters, + ) end - it 'raises an error when the user is missing' do - expect { Jobs::ExportCsvFile.new.execute(user_id: user.id + (1 << 20)) }.to raise_error(Discourse::InvalidParameters) - end - - it 'works' do - expect do - Jobs::ExportUserArchive.new.execute( - user_id: user.id, - ) - end.to change { Upload.count }.by(1) + it "works" do + expect do Jobs::ExportUserArchive.new.execute(user_id: user.id) end.to change { + Upload.count + }.by(1) system_message = user.topics_allowed.last - expect(system_message.title).to eq(I18n.t( - "system_messages.csv_export_succeeded.subject_template", - export_title: "User Archive" - )) + expect(system_message.title).to eq( + I18n.t( + "system_messages.csv_export_succeeded.subject_template", + export_title: "User Archive", + ), + ) upload = system_message.first_post.uploads.first - expect(system_message.first_post.raw).to eq(I18n.t( - "system_messages.csv_export_succeeded.text_body_template", - download_link: "[#{upload.original_filename}|attachment](#{upload.short_url}) (#{upload.human_filesize})" - ).chomp) + expect(system_message.first_post.raw).to eq( + I18n.t( + "system_messages.csv_export_succeeded.text_body_template", + download_link: + "[#{upload.original_filename}|attachment](#{upload.short_url}) (#{upload.human_filesize})", + ).chomp, + ) expect(system_message.id).to eq(UserExport.last.topic_id) expect(system_message.closed).to eq(true) @@ -91,37 +99,46 @@ RSpec.describe Jobs::ExportUserArchive do end expect(files.size).to eq(Jobs::ExportUserArchive::COMPONENTS.length) - expect(files.find { |f| f == 'user_archive.csv' }).to_not be_nil - expect(files.find { |f| f == 'category_preferences.csv' }).to_not be_nil + expect(files.find { |f| f == "user_archive.csv" }).to_not be_nil + expect(files.find { |f| f == "category_preferences.csv" }).to_not be_nil end - it 'sends a message if it fails' do + it "sends a message if it fails" do SiteSetting.max_export_file_size_kb = 1 - expect do - Jobs::ExportUserArchive.new.execute( - user_id: user.id, - ) - end.not_to change { Upload.count } + expect do Jobs::ExportUserArchive.new.execute(user_id: user.id) end.not_to change { + Upload.count + } system_message = user.topics_allowed.last - expect(system_message.title).to eq(I18n.t("system_messages.csv_export_failed.subject_template")) + expect(system_message.title).to eq( + I18n.t("system_messages.csv_export_failed.subject_template"), + ) end end - describe 'user_archive posts' do - let(:component) { 'user_archive' } - let(:subsubcategory) { Fabricate(:category_with_definition, parent_category_id: subcategory.id) } + describe "user_archive posts" do + let(:component) { "user_archive" } + let(:subsubcategory) do + Fabricate(:category_with_definition, parent_category_id: subcategory.id) + end let(:subsubtopic) { Fabricate(:topic, category: subsubcategory) } let(:subsubpost) { Fabricate(:post, user: user, topic: subsubtopic) } let(:normal_post) { Fabricate(:post, user: user, topic: topic) } - let(:reply) { PostCreator.new(user2, raw: 'asdf1234qwert7896', topic_id: topic.id, reply_to_post_number: normal_post.post_number).create } + let(:reply) do + PostCreator.new( + user2, + raw: "asdf1234qwert7896", + topic_id: topic.id, + reply_to_post_number: normal_post.post_number, + ).create + end let(:message) { Fabricate(:private_message_topic) } let(:message_post) { Fabricate(:post, user: user, topic: message) } - it 'properly exports posts' do + it "properly exports posts" do SiteSetting.max_category_nesting = 3 [reply, subsubpost, message_post] @@ -129,17 +146,19 @@ RSpec.describe Jobs::ExportUserArchive do rows = [] job.user_archive_export do |row| - rows << Jobs::ExportUserArchive::HEADER_ATTRS_FOR['user_archive'].zip(row).to_h + rows << Jobs::ExportUserArchive::HEADER_ATTRS_FOR["user_archive"].zip(row).to_h end expect(rows.length).to eq(3) - post1 = rows.find { |r| r['topic_title'] == topic.title } - post2 = rows.find { |r| r['topic_title'] == subsubtopic.title } - post3 = rows.find { |r| r['topic_title'] == message.title } + post1 = rows.find { |r| r["topic_title"] == topic.title } + post2 = rows.find { |r| r["topic_title"] == subsubtopic.title } + post3 = rows.find { |r| r["topic_title"] == message.title } expect(post1["categories"]).to eq("#{category.name}") - expect(post2["categories"]).to eq("#{category.name}|#{subcategory.name}|#{subsubcategory.name}") + expect(post2["categories"]).to eq( + "#{category.name}|#{subcategory.name}|#{subsubcategory.name}", + ) expect(post3["categories"]).to eq("-") expect(post1["is_pm"]).to eq(I18n.t("csv_export.boolean_no")) @@ -154,14 +173,14 @@ RSpec.describe Jobs::ExportUserArchive do expect(post2["post_cooked"]).to eq(subsubpost.cooked) expect(post3["post_cooked"]).to eq(message_post.cooked) - expect(post1['like_count']).to eq(1) - expect(post2['like_count']).to eq(0) + expect(post1["like_count"]).to eq(1) + expect(post2["like_count"]).to eq(0) - expect(post1['reply_count']).to eq(1) - expect(post2['reply_count']).to eq(0) + expect(post1["reply_count"]).to eq(1) + expect(post2["reply_count"]).to eq(0) end - it 'can export a post from a deleted category' do + it "can export a post from a deleted category" do cat2 = Fabricate(:category) topic2 = Fabricate(:topic, category: cat2, user: user) _post2 = Fabricate(:post, topic: topic2, user: user) @@ -185,11 +204,11 @@ RSpec.describe Jobs::ExportUserArchive do end end - describe 'preferences' do - let(:component) { 'preferences' } + describe "preferences" do + let(:component) { "preferences" } before do - user.user_profile.website = 'https://doe.example.com/john' + user.user_profile.website = "https://doe.example.com/john" user.user_profile.bio_raw = "I am John Doe\n\nHere I am" user.user_profile.save user.user_option.text_size = :smaller @@ -197,59 +216,60 @@ RSpec.describe Jobs::ExportUserArchive do user.user_option.save end - it 'properly includes the profile fields' do + it "properly includes the profile fields" do _serializer = job.preferences_export # puts MultiJson.dump(serializer, indent: 4) output = make_component_json - payload = output['user'] + payload = output["user"] - expect(payload['website']).to match('doe.example.com') - expect(payload['bio_raw']).to match("Doe\n\nHere") - expect(payload['user_option']['automatically_unpin_topics']).to eq(false) - expect(payload['user_option']['text_size']).to eq('smaller') + expect(payload["website"]).to match("doe.example.com") + expect(payload["bio_raw"]).to match("Doe\n\nHere") + expect(payload["user_option"]["automatically_unpin_topics"]).to eq(false) + expect(payload["user_option"]["text_size"]).to eq("smaller") end end - describe 'auth tokens' do - let(:component) { 'auth_tokens' } + describe "auth tokens" do + let(:component) { "auth_tokens" } before do - env = create_request_env.merge( - 'HTTP_USER_AGENT' => 'MyWebBrowser', - 'REQUEST_PATH' => '/some_path/456852', - ) + env = + create_request_env.merge( + "HTTP_USER_AGENT" => "MyWebBrowser", + "REQUEST_PATH" => "/some_path/456852", + ) cookie_jar = ActionDispatch::Request.new(env).cookie_jar Discourse.current_user_provider.new(env).log_on_user(user, {}, cookie_jar) end - it 'properly includes session records' do + it "properly includes session records" do data, _csv_out = make_component_csv expect(data.length).to eq(1) - expect(data[0]['user_agent']).to eq('MyWebBrowser') + expect(data[0]["user_agent"]).to eq("MyWebBrowser") end - context 'with auth token logs' do - let(:component) { 'auth_token_logs' } - it 'includes details such as the path' do + context "with auth token logs" do + let(:component) { "auth_token_logs" } + it "includes details such as the path" do data, _csv_out = make_component_csv expect(data.length).to eq(1) - expect(data[0]['action']).to eq('generate') - expect(data[0]['path']).to eq('/some_path/456852') + expect(data[0]["action"]).to eq("generate") + expect(data[0]["path"]).to eq("/some_path/456852") end end end - describe 'badges' do - let(:component) { 'badges' } + describe "badges" do + let(:component) { "badges" } let(:badge1) { Fabricate(:badge) } let(:badge2) { Fabricate(:badge, multiple_grant: true) } let(:badge3) { Fabricate(:badge, multiple_grant: true) } let(:day_ago) { 1.day.ago } - it 'properly includes badge records' do + it "properly includes badge records" do grant_start = Time.now.utc BadgeGranter.grant(badge1, user) BadgeGranter.grant(badge2, user) @@ -261,19 +281,19 @@ RSpec.describe Jobs::ExportUserArchive do data, _csv_out = make_component_csv expect(data.length).to eq(6) - expect(data[0]['badge_id']).to eq(badge1.id.to_s) - expect(data[0]['badge_name']).to eq(badge1.display_name) - expect(data[0]['featured_rank']).to_not eq('') - expect(DateTime.parse(data[0]['granted_at'])).to be >= DateTime.parse(grant_start.to_s) - expect(data[2]['granted_manually']).to eq('true') - expect(Post.find(data[3]['post_id'])).to_not be_nil + expect(data[0]["badge_id"]).to eq(badge1.id.to_s) + expect(data[0]["badge_name"]).to eq(badge1.display_name) + expect(data[0]["featured_rank"]).to_not eq("") + expect(DateTime.parse(data[0]["granted_at"])).to be >= DateTime.parse(grant_start.to_s) + expect(data[2]["granted_manually"]).to eq("true") + expect(Post.find(data[3]["post_id"])).to_not be_nil end end - describe 'bookmarks' do - let(:component) { 'bookmarks' } + describe "bookmarks" do + let(:component) { "bookmarks" } - let(:name) { 'Collect my thoughts on this' } + let(:name) { "Collect my thoughts on this" } let(:manager) { BookmarkManager.new(user) } let(:topic1) { Fabricate(:topic) } let(:post1) { Fabricate(:post, topic: topic1, post_number: 5) } @@ -284,15 +304,30 @@ RSpec.describe Jobs::ExportUserArchive do let(:reminder_at) { 1.day.from_now } it "properly includes bookmark records" do - now = freeze_time '2017-03-01 12:00' + now = freeze_time "2017-03-01 12:00" - bookmark1 = manager.create_for(bookmarkable_id: post1.id, bookmarkable_type: "Post", name: name) + bookmark1 = + manager.create_for(bookmarkable_id: post1.id, bookmarkable_type: "Post", name: name) update1_at = now + 1.hours - bookmark1.update(name: 'great food recipe', updated_at: update1_at) + bookmark1.update(name: "great food recipe", updated_at: update1_at) - manager.create_for(bookmarkable_id: post2.id, bookmarkable_type: "Post", name: name, reminder_at: reminder_at, options: { auto_delete_preference: Bookmark.auto_delete_preferences[:when_reminder_sent] }) + manager.create_for( + bookmarkable_id: post2.id, + bookmarkable_type: "Post", + name: name, + reminder_at: reminder_at, + options: { + auto_delete_preference: Bookmark.auto_delete_preferences[:when_reminder_sent], + }, + ) twelve_hr_ago = freeze_time now - 12.hours - pending_reminder = manager.create_for(bookmarkable_id: post3.id, bookmarkable_type: "Post", name: name, reminder_at: now - 8.hours) + pending_reminder = + manager.create_for( + bookmarkable_id: post3.id, + bookmarkable_type: "Post", + name: name, + reminder_at: now - 8.hours, + ) freeze_time now tau_record = private_message_topic.topic_allowed_users.create!(user_id: user.id) @@ -305,35 +340,43 @@ RSpec.describe Jobs::ExportUserArchive do expect(data.length).to eq(4) - expect(data[0]['bookmarkable_id']).to eq(post1.id.to_s) - expect(data[0]['bookmarkable_type']).to eq("Post") - expect(data[0]['link']).to eq(post1.full_url) - expect(DateTime.parse(data[0]['updated_at'])).to eq(DateTime.parse(update1_at.to_s)) + expect(data[0]["bookmarkable_id"]).to eq(post1.id.to_s) + expect(data[0]["bookmarkable_type"]).to eq("Post") + expect(data[0]["link"]).to eq(post1.full_url) + expect(DateTime.parse(data[0]["updated_at"])).to eq(DateTime.parse(update1_at.to_s)) - expect(data[1]['name']).to eq(name) - expect(DateTime.parse(data[1]['reminder_at'])).to eq(DateTime.parse(reminder_at.to_s)) - expect(data[1]['auto_delete_preference']).to eq('when_reminder_sent') + expect(data[1]["name"]).to eq(name) + expect(DateTime.parse(data[1]["reminder_at"])).to eq(DateTime.parse(reminder_at.to_s)) + expect(data[1]["auto_delete_preference"]).to eq("when_reminder_sent") - expect(DateTime.parse(data[2]['created_at'])).to eq(DateTime.parse(twelve_hr_ago.to_s)) - expect(DateTime.parse(data[2]['reminder_last_sent_at'])).to eq(DateTime.parse(now.to_s)) - expect(data[2]['reminder_set_at']).to eq('') + expect(DateTime.parse(data[2]["created_at"])).to eq(DateTime.parse(twelve_hr_ago.to_s)) + expect(DateTime.parse(data[2]["reminder_last_sent_at"])).to eq(DateTime.parse(now.to_s)) + expect(data[2]["reminder_set_at"]).to eq("") - expect(data[3]['bookmarkable_id']).to eq(post4.id.to_s) - expect(data[3]['bookmarkable_type']).to eq("Post") - expect(data[3]['link']).to eq('') + expect(data[3]["bookmarkable_id"]).to eq(post4.id.to_s) + expect(data[3]["bookmarkable_type"]).to eq("Post") + expect(data[3]["link"]).to eq("") end end - describe 'category_preferences' do - let(:component) { 'category_preferences' } + describe "category_preferences" do + let(:component) { "category_preferences" } - let(:subsubcategory) { Fabricate(:category_with_definition, parent_category_id: subcategory.id, name: "User Archive Subcategory") } + let(:subsubcategory) do + Fabricate( + :category_with_definition, + parent_category_id: subcategory.id, + name: "User Archive Subcategory", + ) + end let(:announcements) { Fabricate(:category_with_definition, name: "Announcements") } let(:deleted_category) { Fabricate(:category, name: "Deleted Category") } let(:secure_category_group) { Fabricate(:group) } - let(:secure_category) { Fabricate(:private_category, group: secure_category_group, name: "Super Secret Category") } + let(:secure_category) do + Fabricate(:private_category, group: secure_category_group, name: "Super Secret Category") + end - let(:reset_at) { DateTime.parse('2017-03-01 12:00') } + let(:reset_at) { DateTime.parse("2017-03-01 12:00") } before do SiteSetting.max_category_nesting = 3 @@ -349,61 +392,83 @@ RSpec.describe Jobs::ExportUserArchive do end # Set Watching First Post on announcements, Tracking on subcategory, Muted on deleted, nothing on subsubcategory - CategoryUser.set_notification_level_for_category(user, NotificationLevels.all[:watching_first_post], announcements.id) - CategoryUser.set_notification_level_for_category(user, NotificationLevels.all[:tracking], subcategory.id) - CategoryUser.set_notification_level_for_category(user, NotificationLevels.all[:muted], deleted_category.id) + CategoryUser.set_notification_level_for_category( + user, + NotificationLevels.all[:watching_first_post], + announcements.id, + ) + CategoryUser.set_notification_level_for_category( + user, + NotificationLevels.all[:tracking], + subcategory.id, + ) + CategoryUser.set_notification_level_for_category( + user, + NotificationLevels.all[:muted], + deleted_category.id, + ) deleted_category.destroy! end - it 'correctly exports the CategoryUser table, excluding deleted categories' do + it "correctly exports the CategoryUser table, excluding deleted categories" do data, _csv_out = make_component_csv - expect(data.find { |r| r['category_id'] == category.id.to_s }).to be_nil - expect(data.find { |r| r['category_id'] == deleted_category.id.to_s }).to be_nil + expect(data.find { |r| r["category_id"] == category.id.to_s }).to be_nil + expect(data.find { |r| r["category_id"] == deleted_category.id.to_s }).to be_nil expect(data.length).to eq(3) - data.sort! { |a, b| a['category_id'].to_i <=> b['category_id'].to_i } + data.sort! { |a, b| a["category_id"].to_i <=> b["category_id"].to_i } expect(data[0][:category_id]).to eq(subcategory.id.to_s) - expect(data[0][:notification_level].to_s).to eq('tracking') + expect(data[0][:notification_level].to_s).to eq("tracking") expect(DateTime.parse(data[0][:dismiss_new_timestamp])).to eq(reset_at) expect(data[1][:category_id]).to eq(subsubcategory.id.to_s) - expect(data[1][:category_names]).to eq("#{category.name}|#{subcategory.name}|#{subsubcategory.name}") - expect(data[1][:notification_level]).to eq('regular') + expect(data[1][:category_names]).to eq( + "#{category.name}|#{subcategory.name}|#{subsubcategory.name}", + ) + expect(data[1][:notification_level]).to eq("regular") expect(DateTime.parse(data[1][:dismiss_new_timestamp])).to eq(reset_at) expect(data[2][:category_id]).to eq(announcements.id.to_s) expect(data[2][:category_names]).to eq(announcements.name) - expect(data[2][:notification_level]).to eq('watching_first_post') - expect(data[2][:dismiss_new_timestamp]).to eq('') + expect(data[2][:notification_level]).to eq("watching_first_post") + expect(data[2][:dismiss_new_timestamp]).to eq("") end it "does not include any secure categories the user does not have access to, even if the user has a CategoryUser record" do - CategoryUser.set_notification_level_for_category(user, NotificationLevels.all[:muted], secure_category.id) + CategoryUser.set_notification_level_for_category( + user, + NotificationLevels.all[:muted], + secure_category.id, + ) data, _csv_out = make_component_csv - expect(data.any? { |r| r['category_id'] == secure_category.id.to_s }).to eq(false) + expect(data.any? { |r| r["category_id"] == secure_category.id.to_s }).to eq(false) expect(data.length).to eq(3) end it "does include secure categories that the user has access to" do - CategoryUser.set_notification_level_for_category(user, NotificationLevels.all[:muted], secure_category.id) + CategoryUser.set_notification_level_for_category( + user, + NotificationLevels.all[:muted], + secure_category.id, + ) GroupUser.create!(user: user, group: secure_category_group) data, _csv_out = make_component_csv - expect(data.any? { |r| r['category_id'] == secure_category.id.to_s }).to eq(true) + expect(data.any? { |r| r["category_id"] == secure_category.id.to_s }).to eq(true) expect(data.length).to eq(4) end end - describe 'flags' do - let(:component) { 'flags' } + describe "flags" do + let(:component) { "flags" } let(:other_post) { Fabricate(:post, user: admin) } let(:post3) { Fabricate(:post) } let(:post4) { Fabricate(:post) } - it 'correctly exports flags' do + it "correctly exports flags" do result0 = PostActionCreator.notify_moderators(user, other_post, "helping out the admins") PostActionCreator.spam(user, post3) PostActionDestroyer.destroy(user, post3, :spam) @@ -414,30 +479,30 @@ RSpec.describe Jobs::ExportUserArchive do data, _csv_out = make_component_csv expect(data.length).to eq(4) - data.sort_by! { |row| row['post_id'].to_i } + data.sort_by! { |row| row["post_id"].to_i } - expect(data[0]['post_id']).to eq(other_post.id.to_s) - expect(data[0]['flag_type']).to eq('notify_moderators') - expect(data[0]['related_post_id']).to eq(result0.post_action.related_post_id.to_s) + expect(data[0]["post_id"]).to eq(other_post.id.to_s) + expect(data[0]["flag_type"]).to eq("notify_moderators") + expect(data[0]["related_post_id"]).to eq(result0.post_action.related_post_id.to_s) - expect(data[1]['flag_type']).to eq('spam') - expect(data[2]['flag_type']).to eq('inappropriate') - expect(data[1]['deleted_at']).to_not be_empty - expect(data[1]['deleted_by']).to eq('self') - expect(data[2]['deleted_at']).to be_empty + expect(data[1]["flag_type"]).to eq("spam") + expect(data[2]["flag_type"]).to eq("inappropriate") + expect(data[1]["deleted_at"]).to_not be_empty + expect(data[1]["deleted_by"]).to eq("self") + expect(data[2]["deleted_at"]).to be_empty - expect(data[3]['post_id']).to eq(post4.id.to_s) - expect(data[3]['flag_type']).to eq('off_topic') - expect(data[3]['deleted_at']).to be_empty + expect(data[3]["post_id"]).to eq(post4.id.to_s) + expect(data[3]["flag_type"]).to eq("off_topic") + expect(data[3]["deleted_at"]).to be_empty end end - describe 'likes' do - let(:component) { 'likes' } + describe "likes" do + let(:component) { "likes" } let(:other_post) { Fabricate(:post, user: admin) } let(:post3) { Fabricate(:post) } - it 'correctly exports likes' do + it "correctly exports likes" do PostActionCreator.like(user, other_post) PostActionCreator.like(user, post3) PostActionCreator.like(admin, post3) @@ -446,25 +511,27 @@ RSpec.describe Jobs::ExportUserArchive do data, _csv_out = make_component_csv expect(data.length).to eq(2) - data.sort_by! { |row| row['post_id'].to_i } + data.sort_by! { |row| row["post_id"].to_i } - expect(data[0]['post_id']).to eq(other_post.id.to_s) - expect(data[1]['post_id']).to eq(post3.id.to_s) - expect(data[1]['deleted_at']).to_not be_empty - expect(data[1]['deleted_by']).to eq('self') + expect(data[0]["post_id"]).to eq(other_post.id.to_s) + expect(data[1]["post_id"]).to eq(post3.id.to_s) + expect(data[1]["deleted_at"]).to_not be_empty + expect(data[1]["deleted_by"]).to eq("self") end end - describe 'queued posts' do - let(:component) { 'queued_posts' } + describe "queued posts" do + let(:component) { "queued_posts" } let(:reviewable_post) { Fabricate(:reviewable_queued_post, topic: topic, created_by: user) } - let(:reviewable_topic) { Fabricate(:reviewable_queued_post_topic, category: category, created_by: user) } + let(:reviewable_topic) do + Fabricate(:reviewable_queued_post_topic, category: category, created_by: user) + end - it 'correctly exports queued posts' do + it "correctly exports queued posts" do SiteSetting.tagging_enabled = true reviewable_post.perform(admin, :reject_post) - reviewable_topic.payload['tags'] = ['example_tag'] + reviewable_topic.payload["tags"] = ["example_tag"] result = reviewable_topic.perform(admin, :approve_post) expect(result.success?).to eq(true) @@ -475,35 +542,65 @@ RSpec.describe Jobs::ExportUserArchive do approved = data.find { |el| el["verdict"] === "approved" } rejected = data.find { |el| el["verdict"] === "rejected" } - expect(approved['other_json']).to match('example_tag') - expect(rejected['post_raw']).to eq('hello world post contents.') - expect(rejected['other_json']).to match('reply_to_post_number') + expect(approved["other_json"]).to match("example_tag") + expect(rejected["post_raw"]).to eq("hello world post contents.") + expect(rejected["other_json"]).to match("reply_to_post_number") end end - describe 'visits' do - let(:component) { 'visits' } + describe "visits" do + let(:component) { "visits" } - it 'correctly exports the UserVisit table' do - freeze_time '2017-03-01 12:00' + it "correctly exports the UserVisit table" do + freeze_time "2017-03-01 12:00" - UserVisit.create(user_id: user.id, visited_at: 1.minute.ago, posts_read: 1, mobile: false, time_read: 10) - UserVisit.create(user_id: user.id, visited_at: 2.days.ago, posts_read: 2, mobile: false, time_read: 20) - UserVisit.create(user_id: user.id, visited_at: 1.week.ago, posts_read: 3, mobile: true, time_read: 30) - UserVisit.create(user_id: user.id, visited_at: 1.year.ago, posts_read: 4, mobile: false, time_read: 40) - UserVisit.create(user_id: user2.id, visited_at: 1.minute.ago, posts_read: 1, mobile: false, time_read: 50) + UserVisit.create( + user_id: user.id, + visited_at: 1.minute.ago, + posts_read: 1, + mobile: false, + time_read: 10, + ) + UserVisit.create( + user_id: user.id, + visited_at: 2.days.ago, + posts_read: 2, + mobile: false, + time_read: 20, + ) + UserVisit.create( + user_id: user.id, + visited_at: 1.week.ago, + posts_read: 3, + mobile: true, + time_read: 30, + ) + UserVisit.create( + user_id: user.id, + visited_at: 1.year.ago, + posts_read: 4, + mobile: false, + time_read: 40, + ) + UserVisit.create( + user_id: user2.id, + visited_at: 1.minute.ago, + posts_read: 1, + mobile: false, + time_read: 50, + ) data, _csv_out = make_component_csv # user2's data is not mixed in expect(data.length).to eq(4) - expect(data.find { |r| r['time_read'] == 50 }).to be_nil + expect(data.find { |r| r["time_read"] == 50 }).to be_nil - expect(data[0]['visited_at']).to eq('2016-03-01') - expect(data[0]['posts_read']).to eq('4') - expect(data[0]['time_read']).to eq('40') - expect(data[1]['mobile']).to eq('true') - expect(data[3]['visited_at']).to eq('2017-03-01') + expect(data[0]["visited_at"]).to eq("2016-03-01") + expect(data[0]["posts_read"]).to eq("4") + expect(data[0]["time_read"]).to eq("40") + expect(data[1]["mobile"]).to eq("true") + expect(data[3]["visited_at"]).to eq("2017-03-01") end end end diff --git a/spec/jobs/feature_topic_users_spec.rb b/spec/jobs/feature_topic_users_spec.rb index 3fa7eba092..4eecff5e54 100644 --- a/spec/jobs/feature_topic_users_spec.rb +++ b/spec/jobs/feature_topic_users_spec.rb @@ -9,7 +9,7 @@ RSpec.describe Jobs::FeatureTopicUsers do Jobs::FeatureTopicUsers.new.execute(topic_id: 123) end - context 'with a topic' do + context "with a topic" do let!(:post) { create_post } let(:topic) { post.topic } fab!(:coding_horror) { Fabricate(:coding_horror) } diff --git a/spec/jobs/fix_out_of_sync_user_uploaded_avatar_spec.rb b/spec/jobs/fix_out_of_sync_user_uploaded_avatar_spec.rb index b6da24ac57..bd5a32a745 100644 --- a/spec/jobs/fix_out_of_sync_user_uploaded_avatar_spec.rb +++ b/spec/jobs/fix_out_of_sync_user_uploaded_avatar_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe Jobs::FixOutOfSyncUserUploadedAvatar do - it 'should fix out of sync user uploaded avatars' do + it "should fix out of sync user uploaded avatars" do user_with_custom_upload = Fabricate(:user) custom_upload1 = Fabricate(:upload, user: user_with_custom_upload) gravatar_upload1 = Fabricate(:upload, user: user_with_custom_upload) @@ -9,7 +9,7 @@ RSpec.describe Jobs::FixOutOfSyncUserUploadedAvatar do user_with_custom_upload.user_avatar.update!( custom_upload: custom_upload1, - gravatar_upload: gravatar_upload1 + gravatar_upload: gravatar_upload1, ) user_out_of_sync = Fabricate(:user) @@ -22,23 +22,19 @@ RSpec.describe Jobs::FixOutOfSyncUserUploadedAvatar do user_out_of_sync.user_avatar.update!( custom_upload: custom_upload2, - gravatar_upload: gravatar_upload2 + gravatar_upload: gravatar_upload2, ) user_without_uploaded_avatar = Fabricate(:user) gravatar_upload3 = Fabricate(:upload, user: user_without_uploaded_avatar) - user_without_uploaded_avatar.user_avatar.update!( - gravatar_upload: gravatar_upload3 - ) + user_without_uploaded_avatar.user_avatar.update!(gravatar_upload: gravatar_upload3) described_class.new.execute_onceoff({}) expect(user_with_custom_upload.reload.uploaded_avatar).to eq(custom_upload1) expect(user_out_of_sync.reload.uploaded_avatar).to eq(gravatar_upload2) - expect(user_without_uploaded_avatar.reload.uploaded_avatar) - .to eq(nil) - + expect(user_without_uploaded_avatar.reload.uploaded_avatar).to eq(nil) end end diff --git a/spec/jobs/fix_primary_emails_for_staged_users_spec.rb b/spec/jobs/fix_primary_emails_for_staged_users_spec.rb index 54a997c477..fbf4af9624 100644 --- a/spec/jobs/fix_primary_emails_for_staged_users_spec.rb +++ b/spec/jobs/fix_primary_emails_for_staged_users_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true RSpec.describe Jobs::FixPrimaryEmailsForStagedUsers do - it 'should clean up duplicated staged users' do - common_email = 'test@reply' + it "should clean up duplicated staged users" do + common_email = "test@reply" staged_user = Fabricate(:user, staged: true, active: false) staged_user2 = Fabricate(:user, staged: true, active: false) @@ -24,11 +24,15 @@ RSpec.describe Jobs::FixPrimaryEmailsForStagedUsers do # it will raise error in https://github.com/discourse/discourse/blob/d0b027d88deeabf8bc105419f7d3fae0087091cd/app/models/user.rb#L942 WebHook.stubs(:generate_payload).returns(nil) - expect { described_class.new.execute_onceoff({}) } - .to change { User.count }.by(-2) - .and change { staged_user.posts.count }.by(3) + expect { described_class.new.execute_onceoff({}) }.to change { User.count }.by(-2).and change { + staged_user.posts.count + }.by(3) - expect(User.where('id > -2')).to contain_exactly(Discourse.system_user, staged_user, active_user) + expect(User.where("id > -2")).to contain_exactly( + Discourse.system_user, + staged_user, + active_user, + ) expect(staged_user.posts.all).to contain_exactly(post1, post2, post3) expect(staged_user.reload.email).to eq(common_email) end diff --git a/spec/jobs/fix_s3_etags_spec.rb b/spec/jobs/fix_s3_etags_spec.rb index 225837fed1..2f72829f08 100644 --- a/spec/jobs/fix_s3_etags_spec.rb +++ b/spec/jobs/fix_s3_etags_spec.rb @@ -2,9 +2,9 @@ RSpec.describe Jobs::FixS3Etags do let(:etag_with_quotes) { '"ETag"' } - let(:etag_without_quotes) { 'ETag' } + let(:etag_without_quotes) { "ETag" } - it 'should remove double quotes from etags' do + it "should remove double quotes from etags" do upload1 = Fabricate(:upload, etag: etag_with_quotes) upload2 = Fabricate(:upload, etag: etag_without_quotes) optimized = Fabricate(:optimized_image, etag: etag_with_quotes) diff --git a/spec/jobs/fix_user_usernames_and_groups_names_clash_spec.rb b/spec/jobs/fix_user_usernames_and_groups_names_clash_spec.rb index e237f9c6be..05ba09781d 100644 --- a/spec/jobs/fix_user_usernames_and_groups_names_clash_spec.rb +++ b/spec/jobs/fix_user_usernames_and_groups_names_clash_spec.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true RSpec.describe Jobs::FixUserUsernamesAndGroupsNamesClash do - it 'update usernames of users that clashes with a group name' do + it "update usernames of users that clashes with a group name" do user = Fabricate(:user) - Fabricate(:user, username: 'test1') - group = Fabricate(:group, name: 'test') - user.update_columns(username: 'test', username_lower: 'test') + Fabricate(:user, username: "test1") + group = Fabricate(:group, name: "test") + user.update_columns(username: "test", username_lower: "test") Jobs::FixUserUsernamesAndGroupsNamesClash.new.execute({}) - expect(user.reload.username).to eq('test2') - expect(group.reload.name).to eq('test') + expect(user.reload.username).to eq("test2") + expect(group.reload.name).to eq("test") end end diff --git a/spec/jobs/grant_anniversary_badges_spec.rb b/spec/jobs/grant_anniversary_badges_spec.rb index ae5cad056c..5cee313087 100644 --- a/spec/jobs/grant_anniversary_badges_spec.rb +++ b/spec/jobs/grant_anniversary_badges_spec.rb @@ -100,9 +100,7 @@ RSpec.describe Jobs::GrantAnniversaryBadges do user = Fabricate(:user, created_at: 800.days.ago) Fabricate(:post, user: user, created_at: 450.days.ago) - freeze_time(400.days.ago) do - granter.execute({}) - end + freeze_time(400.days.ago) { granter.execute({}) } badge = user.user_badges.where(badge_id: Badge::Anniversary) expect(badge.count).to eq(1) diff --git a/spec/jobs/grant_new_user_of_the_month_badges_spec.rb b/spec/jobs/grant_new_user_of_the_month_badges_spec.rb index 419f709cad..9ac859a514 100644 --- a/spec/jobs/grant_new_user_of_the_month_badges_spec.rb +++ b/spec/jobs/grant_new_user_of_the_month_badges_spec.rb @@ -1,16 +1,15 @@ # frozen_string_literal: true RSpec.describe Jobs::GrantNewUserOfTheMonthBadges do - let(:granter) { described_class.new } it "runs correctly" do - freeze_time(DateTime.parse('2019-11-30 23:59 UTC')) + freeze_time(DateTime.parse("2019-11-30 23:59 UTC")) u0 = Fabricate(:user, created_at: 2.weeks.ago) BadgeGranter.grant(Badge.find(Badge::NewUserOfTheMonth), u0, created_at: Time.now) - freeze_time(DateTime.parse('2020-01-01 00:00 UTC')) + freeze_time(DateTime.parse("2020-01-01 00:00 UTC")) user = Fabricate(:user, created_at: 1.week.ago) p = Fabricate(:post, user: user) @@ -25,11 +24,11 @@ RSpec.describe Jobs::GrantNewUserOfTheMonthBadges do badges = user.user_badges.where(badge_id: Badge::NewUserOfTheMonth) expect(badges).to be_present - expect(badges.first.granted_at.to_s).to eq('2019-12-31 23:59:59 UTC') + expect(badges.first.granted_at.to_s).to eq("2019-12-31 23:59:59 UTC") end it "does not include people created after the previous month" do - freeze_time(DateTime.parse('2020-01-15 00:00 UTC')) + freeze_time(DateTime.parse("2020-01-15 00:00 UTC")) user = Fabricate(:user, created_at: 1.week.ago) p = Fabricate(:post, user: user) @@ -85,12 +84,12 @@ RSpec.describe Jobs::GrantNewUserOfTheMonthBadges do end it "does nothing if it's already been awarded in previous month" do - freeze_time(DateTime.parse('2019-11-30 23:59 UTC')) + freeze_time(DateTime.parse("2019-11-30 23:59 UTC")) u0 = Fabricate(:user, created_at: 2.weeks.ago) BadgeGranter.grant(Badge.find(Badge::NewUserOfTheMonth), u0, created_at: Time.now) - freeze_time(DateTime.parse('2019-12-01 00:00 UTC')) + freeze_time(DateTime.parse("2019-12-01 00:00 UTC")) user = Fabricate(:user, created_at: 1.week.ago) p = Fabricate(:post, user: user) @@ -107,7 +106,7 @@ RSpec.describe Jobs::GrantNewUserOfTheMonthBadges do expect(badge).to be_blank end - describe '.scores' do + describe ".scores" do def scores granter.scores(1.month.ago, Time.now) end @@ -223,7 +222,5 @@ RSpec.describe Jobs::GrantNewUserOfTheMonthBadges do expect(scores.keys.size).to eq(2) end - end - end diff --git a/spec/jobs/heartbeat_spec.rb b/spec/jobs/heartbeat_spec.rb index f62043e287..983b8832c5 100644 --- a/spec/jobs/heartbeat_spec.rb +++ b/spec/jobs/heartbeat_spec.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true RSpec.describe ::Jobs::Heartbeat do - after do - Discourse.disable_readonly_mode - end + after { Discourse.disable_readonly_mode } it "still enqueues heartbeats in readonly mode" do freeze_time 1.week.from_now diff --git a/spec/jobs/ignored_users_summary_spec.rb b/spec/jobs/ignored_users_summary_spec.rb index 1768161a7c..461d94081a 100644 --- a/spec/jobs/ignored_users_summary_spec.rb +++ b/spec/jobs/ignored_users_summary_spec.rb @@ -27,9 +27,7 @@ RSpec.describe Jobs::IgnoredUsersSummary do context "when no system message exists for the ignored users" do context "when threshold is not hit" do - before do - SiteSetting.ignored_users_count_message_threshold = 5 - end + before { SiteSetting.ignored_users_count_message_threshold = 5 } it "does nothing" do subject @@ -40,10 +38,13 @@ RSpec.describe Jobs::IgnoredUsersSummary do context "when threshold is hit" do it "creates a system message" do subject - posts = Post.joins(:topic).where(topics: { - archetype: Archetype.private_message, - subtype: TopicSubtype.system_message - }) + posts = + Post.joins(:topic).where( + topics: { + archetype: Archetype.private_message, + subtype: TopicSubtype.system_message, + }, + ) expect(posts.count).to eq(2) expect(posts.find { |post| post.raw.include?(matt.username) }).to be_present expect(posts.find { |post| post.raw.include?(john.username) }).to be_present @@ -53,9 +54,7 @@ RSpec.describe Jobs::IgnoredUsersSummary do context "when a system message already exists for the ignored users" do context "when threshold is not hit" do - before do - SiteSetting.ignored_users_count_message_threshold = 5 - end + before { SiteSetting.ignored_users_count_message_threshold = 5 } it "does nothing" do subject diff --git a/spec/jobs/invalidate_inactive_admins_spec.rb b/spec/jobs/invalidate_inactive_admins_spec.rb index 8994e9f730..7c070aa5c1 100644 --- a/spec/jobs/invalidate_inactive_admins_spec.rb +++ b/spec/jobs/invalidate_inactive_admins_spec.rb @@ -17,17 +17,15 @@ RSpec.describe Jobs::InvalidateInactiveAdmins do fab!(:not_seen_admin) { Fabricate(:admin, last_seen_at: 370.days.ago) } before { not_seen_admin.email_tokens.update_all(confirmed: true) } - context 'when invalidate_inactive_admin_email_after_days = 365' do - before do - SiteSetting.invalidate_inactive_admin_email_after_days = 365 - end + context "when invalidate_inactive_admin_email_after_days = 365" do + before { SiteSetting.invalidate_inactive_admin_email_after_days = 365 } - it 'marks email tokens as unconfirmed' do + it "marks email tokens as unconfirmed" do subject expect(not_seen_admin.reload.email_tokens.where(confirmed: true).exists?).to eq(false) end - it 'makes the user as not active and logs the action' do + it "makes the user as not active and logs the action" do subject expect(not_seen_admin.reload.active).to eq(false) @@ -36,17 +34,24 @@ RSpec.describe Jobs::InvalidateInactiveAdmins do expect(log.action).to eq(UserHistory.actions[:deactivate_user]) end - it 'adds a staff log' do + it "adds a staff log" do subject expect(not_seen_admin.reload.active).to eq(false) end - context 'with social logins' do + context "with social logins" do before do - UserAssociatedAccount.create!(provider_name: "google_oauth2", user_id: not_seen_admin.id, provider_uid: 100, info: { email: "bob@google.account.com" }) + UserAssociatedAccount.create!( + provider_name: "google_oauth2", + user_id: not_seen_admin.id, + provider_uid: 100, + info: { + email: "bob@google.account.com", + }, + ) end - it 'removes the social logins' do + it "removes the social logins" do subject expect(UserAssociatedAccount.where(user_id: not_seen_admin.id).exists?).to eq(false) end @@ -65,12 +70,10 @@ RSpec.describe Jobs::InvalidateInactiveAdmins do end end - context 'when invalidate_inactive_admin_email_after_days = 0 to disable this feature' do - before do - SiteSetting.invalidate_inactive_admin_email_after_days = 0 - end + context "when invalidate_inactive_admin_email_after_days = 0 to disable this feature" do + before { SiteSetting.invalidate_inactive_admin_email_after_days = 0 } - it 'does nothing' do + it "does nothing" do subject expect(active_admin.reload.active).to eq(true) expect(active_admin.email_tokens.where(confirmed: true).exists?).to eq(true) diff --git a/spec/jobs/invite_email_spec.rb b/spec/jobs/invite_email_spec.rb index ec4bae6c51..0d6b52d483 100644 --- a/spec/jobs/invite_email_spec.rb +++ b/spec/jobs/invite_email_spec.rb @@ -1,19 +1,18 @@ # frozen_string_literal: true RSpec.describe Jobs::InviteEmail do - - describe '.execute' do - - it 'raises an error when the invite_id is missing' do + describe ".execute" do + it "raises an error when the invite_id is missing" do expect { Jobs::InviteEmail.new.execute({}) }.to raise_error(Discourse::InvalidParameters) end - context 'with an invite id' do - - let (:mailer) { Mail::Message.new(to: 'eviltrout@test.domain') } + context "with an invite id" do + let (:mailer) { + Mail::Message.new(to: "eviltrout@test.domain") + } fab!(:invite) { Fabricate(:invite) } - it 'delegates to the test mailer' do + it "delegates to the test mailer" do Email::Sender.any_instance.expects(:send) InviteMailer.expects(:send_invite).with(invite, anything).returns(mailer) Jobs::InviteEmail.new.execute(invite_id: invite.id) diff --git a/spec/jobs/jobs_base_spec.rb b/spec/jobs/jobs_base_spec.rb index 6b267af9c3..19899879bf 100644 --- a/spec/jobs/jobs_base_spec.rb +++ b/spec/jobs/jobs_base_spec.rb @@ -22,14 +22,14 @@ RSpec.describe ::Jobs::Base do end end - it 'handles correct jobs' do + it "handles correct jobs" do job = GoodJob.new job.perform({}) expect(job.count).to eq(1) end - it 'handles errors in multisite' do - RailsMultisite::ConnectionManagement.expects(:all_dbs).returns(['default', 'default', 'default']) + it "handles errors in multisite" do + RailsMultisite::ConnectionManagement.expects(:all_dbs).returns(%w[default default default]) # one exception per database Discourse.expects(:handle_job_exception).times(3) @@ -38,17 +38,13 @@ RSpec.describe ::Jobs::Base do expect(bad.fail_count).to eq(3) end - describe '#perform' do - context 'when a job raises an error' do - before do - Discourse.reset_job_exception_stats! - end + describe "#perform" do + context "when a job raises an error" do + before { Discourse.reset_job_exception_stats! } - after do - Discourse.reset_job_exception_stats! - end + after { Discourse.reset_job_exception_stats! } - it 'collects stats for failing jobs in Discourse.job_exception_stats' do + it "collects stats for failing jobs in Discourse.job_exception_stats" do bad = BadJob.new 3.times do # During test env handle_job_exception errors out @@ -61,51 +57,47 @@ RSpec.describe ::Jobs::Base do end end - it 'delegates the process call to execute' do - ::Jobs::Base.any_instance.expects(:execute).with({ 'hello' => 'world' }) - ::Jobs::Base.new.perform('hello' => 'world', 'sync_exec' => true) + it "delegates the process call to execute" do + ::Jobs::Base.any_instance.expects(:execute).with({ "hello" => "world" }) + ::Jobs::Base.new.perform("hello" => "world", "sync_exec" => true) end - it 'converts to an indifferent access hash' do + it "converts to an indifferent access hash" do ::Jobs::Base.any_instance.expects(:execute).with(instance_of(HashWithIndifferentAccess)) - ::Jobs::Base.new.perform('hello' => 'world', 'sync_exec' => true) + ::Jobs::Base.new.perform("hello" => "world", "sync_exec" => true) end context "with fake jobs" do let(:common_state) { [] } - let(:test_job_1) { - Class.new(Jobs::Base).tap do |klass| - state = common_state - klass.define_method(:execute) do |args| - state << "job_1_executed" + let(:test_job_1) do + Class + .new(Jobs::Base) + .tap do |klass| + state = common_state + klass.define_method(:execute) { |args| state << "job_1_executed" } end - end - } + end - let(:test_job_2) { - Class.new(Jobs::Base).tap do |klass| - state = common_state - job_1 = test_job_1 - klass.define_method(:execute) do |args| - state << "job_2_started" - Jobs.enqueue(job_1) - state << "job_2_finished" + let(:test_job_2) do + Class + .new(Jobs::Base) + .tap do |klass| + state = common_state + job_1 = test_job_1 + klass.define_method(:execute) do |args| + state << "job_2_started" + Jobs.enqueue(job_1) + state << "job_2_finished" + end end - end - } + end it "runs jobs synchronously sequentially in tests" do Jobs.run_immediately! Jobs.enqueue(test_job_2) - expect(common_state).to eq([ - "job_2_started", - "job_2_finished", - "job_1_executed" - ]) + expect(common_state).to eq(%w[job_2_started job_2_finished job_1_executed]) end - end - end diff --git a/spec/jobs/jobs_spec.rb b/spec/jobs/jobs_spec.rb index 09406eb66b..c20f23a89d 100644 --- a/spec/jobs/jobs_spec.rb +++ b/spec/jobs/jobs_spec.rb @@ -1,15 +1,11 @@ # frozen_string_literal: true RSpec.describe Jobs do + describe "enqueue" do + describe "run_later!" do + before { Jobs.run_later! } - describe 'enqueue' do - - describe 'run_later!' do - before do - Jobs.run_later! - end - - it 'enqueues a job in sidekiq' do + it "enqueues a job in sidekiq" do Sidekiq::Testing.fake! do jobs = Jobs::ProcessPost.jobs @@ -21,7 +17,7 @@ RSpec.describe Jobs do expected = { "class" => "Jobs::ProcessPost", "args" => [{ "post_id" => 1, "current_site_id" => "default" }], - "queue" => "default" + "queue" => "default", } expect(job.slice("class", "args", "queue")).to eq(expected) end @@ -62,7 +58,7 @@ RSpec.describe Jobs do expected = { "class" => "Jobs::ProcessPost", "args" => [{ "post_id" => 1 }], - "queue" => "default" + "queue" => "default", } expect(job.slice("class", "args", "queue")).to eq(expected) end @@ -75,12 +71,11 @@ RSpec.describe Jobs do end it "should enqueue with the correct database id when the current_site_id option is given" do - Sidekiq::Testing.fake! do jobs = Jobs::ProcessPost.jobs jobs.clear - Jobs.enqueue(:process_post, post_id: 1, current_site_id: 'test_db') + Jobs.enqueue(:process_post, post_id: 1, current_site_id: "test_db") expect(jobs.length).to eq(1) job = jobs.first @@ -88,17 +83,15 @@ RSpec.describe Jobs do expected = { "class" => "Jobs::ProcessPost", "args" => [{ "post_id" => 1, "current_site_id" => "test_db" }], - "queue" => "default" + "queue" => "default", } expect(job.slice("class", "args", "queue")).to eq(expected) end end end - describe 'run_immediately!' do - before do - Jobs.run_immediately! - end + describe "run_immediately!" do + before { Jobs.run_immediately! } it "doesn't enqueue in sidekiq" do Sidekiq::Client.expects(:enqueue).with(Jobs::ProcessPost, {}).never @@ -106,45 +99,45 @@ RSpec.describe Jobs do end it "executes the job right away" do - Jobs::ProcessPost.any_instance.expects(:perform).with({ "post_id" => 1, "sync_exec" => true, "current_site_id" => "default" }) + Jobs::ProcessPost + .any_instance + .expects(:perform) + .with({ "post_id" => 1, "sync_exec" => true, "current_site_id" => "default" }) Jobs.enqueue(:process_post, post_id: 1) end - context 'when current_site_id option is given and does not match the current connection' do + context "when current_site_id option is given and does not match the current connection" do before do Sidekiq::Client.stubs(:enqueue) Jobs::ProcessPost.any_instance.stubs(:execute).returns(true) end - it 'should raise an exception' do + it "should raise an exception" do Jobs::ProcessPost.any_instance.expects(:execute).never RailsMultisite::ConnectionManagement.expects(:establish_connection).never expect { - Jobs.enqueue(:process_post, post_id: 1, current_site_id: 'test_db') + Jobs.enqueue(:process_post, post_id: 1, current_site_id: "test_db") }.to raise_error(ArgumentError) end end end - end - describe 'cancel_scheduled_job' do + describe "cancel_scheduled_job" do let(:scheduled_jobs) { Sidekiq::ScheduledSet.new } - after do - scheduled_jobs.clear - end + after { scheduled_jobs.clear } - it 'deletes the matching job' do + it "deletes the matching job" do Sidekiq::Testing.disable! do scheduled_jobs.clear expect(scheduled_jobs.size).to eq(0) Jobs.enqueue_in(1.year, :run_heartbeat, topic_id: 123) Jobs.enqueue_in(2.years, :run_heartbeat, topic_id: 456) - Jobs.enqueue_in(3.years, :run_heartbeat, topic_id: 123, current_site_id: 'foo') - Jobs.enqueue_in(4.years, :run_heartbeat, topic_id: 123, current_site_id: 'bar') + Jobs.enqueue_in(3.years, :run_heartbeat, topic_id: 123, current_site_id: "foo") + Jobs.enqueue_in(4.years, :run_heartbeat, topic_id: 123, current_site_id: "bar") expect(scheduled_jobs.size).to eq(4) @@ -157,11 +150,10 @@ RSpec.describe Jobs do expect(scheduled_jobs.size).to eq(1) end end - end - describe 'enqueue_at' do - it 'calls enqueue_in for you' do + describe "enqueue_at" do + it "calls enqueue_in for you" do freeze_time expect_enqueued_with(job: :process_post, at: 3.hours.from_now) do @@ -169,7 +161,7 @@ RSpec.describe Jobs do end end - it 'handles datetimes that are in the past' do + it "handles datetimes that are in the past" do freeze_time expect_enqueued_with(job: :process_post, at: Time.zone.now) do @@ -177,5 +169,4 @@ RSpec.describe Jobs do end end end - end diff --git a/spec/jobs/mass_award_badge_spec.rb b/spec/jobs/mass_award_badge_spec.rb index 024014a84a..361dfda270 100644 --- a/spec/jobs/mass_award_badge_spec.rb +++ b/spec/jobs/mass_award_badge_spec.rb @@ -1,28 +1,35 @@ # frozen_string_literal: true RSpec.describe Jobs::MassAwardBadge do - describe '#execute' do + describe "#execute" do fab!(:badge) { Fabricate(:badge) } fab!(:user) { Fabricate(:user) } - let(:email_mode) { 'email' } + let(:email_mode) { "email" } - it 'creates the badge for an existing user' do + it "creates the badge for an existing user" do execute_job(user) expect(UserBadge.where(user: user, badge: badge).exists?).to eq(true) end - it 'also creates a notification for the user' do + it "also creates a notification for the user" do execute_job(user) expect(Notification.exists?(user: user)).to eq(true) - expect(UserBadge.where.not(notification_id: nil).exists?(user: user, badge: badge)).to eq(true) + expect(UserBadge.where.not(notification_id: nil).exists?(user: user, badge: badge)).to eq( + true, + ) end - it 'updates badge ranks correctly' do + it "updates badge ranks correctly" do user_2 = Fabricate(:user) - UserBadge.create!(badge_id: Badge::Member, user: user, granted_by: Discourse.system_user, granted_at: Time.now) + UserBadge.create!( + badge_id: Badge::Member, + user: user, + granted_by: Discourse.system_user, + granted_at: Time.now, + ) execute_job(user) execute_job(user_2) @@ -31,7 +38,7 @@ RSpec.describe Jobs::MassAwardBadge do expect(UserBadge.find_by(user: user_2, badge: badge).featured_rank).to eq(1) end - it 'grants a badge multiple times to a user' do + it "grants a badge multiple times to a user" do badge.update!(multiple_grant: true) Notification.destroy_all execute_job(user, count: 4, grant_existing_holders: true) @@ -44,7 +51,12 @@ RSpec.describe Jobs::MassAwardBadge do end def execute_job(user, count: 1, grant_existing_holders: false) - subject.execute(user: user.id, badge: badge.id, count: count, grant_existing_holders: grant_existing_holders) + subject.execute( + user: user.id, + badge: badge.id, + count: count, + grant_existing_holders: grant_existing_holders, + ) end end end diff --git a/spec/jobs/migrate_badge_image_to_uploads_spec.rb b/spec/jobs/migrate_badge_image_to_uploads_spec.rb index 21dfabe780..21c39338ea 100644 --- a/spec/jobs/migrate_badge_image_to_uploads_spec.rb +++ b/spec/jobs/migrate_badge_image_to_uploads_spec.rb @@ -9,21 +9,18 @@ RSpec.describe Jobs::MigrateBadgeImageToUploads do Rails.logger = @fake_logger = FakeLogger.new end - after do - Rails.logger = @orig_logger - end + after { Rails.logger = @orig_logger } - it 'should migrate to the new badge `image_upload_id` column correctly' do + it "should migrate to the new badge `image_upload_id` column correctly" do stub_request(:get, image_url).to_return( - status: 200, body: file_from_fixtures("smallest.png").read + status: 200, + body: file_from_fixtures("smallest.png").read, ) DB.exec(<<~SQL, flair_url: image_url, id: badge.id) UPDATE badges SET image = :flair_url WHERE id = :id SQL - expect do - described_class.new.execute_onceoff({}) - end.to change { Upload.count }.by(1) + expect do described_class.new.execute_onceoff({}) end.to change { Upload.count }.by(1) badge.reload upload = Upload.last @@ -32,7 +29,7 @@ RSpec.describe Jobs::MigrateBadgeImageToUploads do expect(badge[:image]).to eq(nil) end - it 'should skip badges with invalid flair URLs' do + it "should skip badges with invalid flair URLs" do DB.exec("UPDATE badges SET image = 'abc' WHERE id = ?", badge.id) described_class.new.execute_onceoff({}) expect(@fake_logger.warnings.count).to eq(0) @@ -41,7 +38,7 @@ RSpec.describe Jobs::MigrateBadgeImageToUploads do # this case has a couple of hacks that are needed to test this behavior, so if it # starts failing randomly in the future, I'd just delete it and not bother with it - it 'should not keep retrying forever if download fails' do + it "should not keep retrying forever if download fails" do stub_request(:get, image_url).to_return(status: 403) instance = described_class.new instance.expects(:sleep).times(2) @@ -50,9 +47,7 @@ RSpec.describe Jobs::MigrateBadgeImageToUploads do UPDATE badges SET image = :flair_url WHERE id = :id SQL - expect do - instance.execute_onceoff({}) - end.not_to change { Upload.count } + expect do instance.execute_onceoff({}) end.not_to change { Upload.count } badge.reload expect(badge.image_upload).to eq(nil) diff --git a/spec/jobs/notify_category_change_spec.rb b/spec/jobs/notify_category_change_spec.rb index d218760aed..567aab68f6 100644 --- a/spec/jobs/notify_category_change_spec.rb +++ b/spec/jobs/notify_category_change_spec.rb @@ -4,13 +4,19 @@ RSpec.describe ::Jobs::NotifyCategoryChange do fab!(:user) { Fabricate(:user) } fab!(:regular_user) { Fabricate(:trust_level_4) } fab!(:post) { Fabricate(:post, user: regular_user) } - fab!(:category) { Fabricate(:category, name: 'test') } + fab!(:category) { Fabricate(:category, name: "test") } it "doesn't create notification for the editor who watches new tag" do - CategoryUser.set_notification_level_for_category(user, CategoryUser.notification_levels[:watching_first_post], category.id) + CategoryUser.set_notification_level_for_category( + user, + CategoryUser.notification_levels[:watching_first_post], + category.id, + ) post.topic.update!(category: category) post.update!(last_editor_id: user.id) - expect { described_class.new.execute(post_id: post.id, notified_user_ids: []) }.not_to change { Notification.count } + expect { described_class.new.execute(post_id: post.id, notified_user_ids: []) }.not_to change { + Notification.count + } end end diff --git a/spec/jobs/notify_mailing_list_subscribers_spec.rb b/spec/jobs/notify_mailing_list_subscribers_spec.rb index ace5161f91..71cf7347a4 100644 --- a/spec/jobs/notify_mailing_list_subscribers_spec.rb +++ b/spec/jobs/notify_mailing_list_subscribers_spec.rb @@ -3,10 +3,10 @@ RSpec.describe Jobs::NotifyMailingListSubscribers do fab!(:mailing_list_user) { Fabricate(:user) } - before { mailing_list_user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 1) } before do - SiteSetting.tagging_enabled = true + mailing_list_user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 1) end + before { SiteSetting.tagging_enabled = true } fab!(:tag) { Fabricate(:tag) } fab!(:topic) { Fabricate(:topic, tags: [tag]) } @@ -27,10 +27,14 @@ RSpec.describe Jobs::NotifyMailingListSubscribers do end it "triggers :notify_mailing_list_subscribers" do - events = DiscourseEvent.track_events do - Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id) - end - expect(events).to include(event_name: :notify_mailing_list_subscribers, params: [[mailing_list_user], post]) + events = + DiscourseEvent.track_events do + Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id) + end + expect(events).to include( + event_name: :notify_mailing_list_subscribers, + params: [[mailing_list_user], post], + ) end end @@ -133,12 +137,24 @@ RSpec.describe Jobs::NotifyMailingListSubscribers do end context "when from a muted topic" do - before { TopicUser.create(user: mailing_list_user, topic: post.topic, notification_level: TopicUser.notification_levels[:muted]) } + before do + TopicUser.create( + user: mailing_list_user, + topic: post.topic, + notification_level: TopicUser.notification_levels[:muted], + ) + end include_examples "no emails" end context "when from a muted category" do - before { CategoryUser.create(user: mailing_list_user, category: post.topic.category, notification_level: CategoryUser.notification_levels[:muted]) } + before do + CategoryUser.create( + user: mailing_list_user, + category: post.topic.category, + notification_level: CategoryUser.notification_levels[:muted], + ) + end include_examples "no emails" end @@ -150,7 +166,11 @@ RSpec.describe Jobs::NotifyMailingListSubscribers do context "with mute all categories by default setting but user is watching category" do before do SiteSetting.mute_all_categories_by_default = true - CategoryUser.create(user: mailing_list_user, category: post.topic.category, notification_level: CategoryUser.notification_levels[:watching]) + CategoryUser.create( + user: mailing_list_user, + category: post.topic.category, + notification_level: CategoryUser.notification_levels[:watching], + ) end include_examples "one email" end @@ -158,7 +178,11 @@ RSpec.describe Jobs::NotifyMailingListSubscribers do context "with mute all categories by default setting but user is watching tag" do before do SiteSetting.mute_all_categories_by_default = true - TagUser.create(user: mailing_list_user, tag: tag, notification_level: TagUser.notification_levels[:watching]) + TagUser.create( + user: mailing_list_user, + tag: tag, + notification_level: TagUser.notification_levels[:watching], + ) end include_examples "one email" end @@ -166,13 +190,23 @@ RSpec.describe Jobs::NotifyMailingListSubscribers do context "with mute all categories by default setting but user is watching topic" do before do SiteSetting.mute_all_categories_by_default = true - TopicUser.create(user: mailing_list_user, topic: post.topic, notification_level: TopicUser.notification_levels[:watching]) + TopicUser.create( + user: mailing_list_user, + topic: post.topic, + notification_level: TopicUser.notification_levels[:watching], + ) end include_examples "one email" end context "when from a muted tag" do - before { TagUser.create(user: mailing_list_user, tag: tag, notification_level: TagUser.notification_levels[:muted]) } + before do + TagUser.create( + user: mailing_list_user, + tag: tag, + notification_level: TagUser.notification_levels[:muted], + ) + end include_examples "no emails" end @@ -180,44 +214,39 @@ RSpec.describe Jobs::NotifyMailingListSubscribers do before { SiteSetting.max_emails_per_day_per_user = 2 } it "doesn't send any emails" do - (SiteSetting.max_emails_per_day_per_user + 1).times { - mailing_list_user.email_logs.create(email_type: 'foobar', to_address: mailing_list_user.email) - } + (SiteSetting.max_emails_per_day_per_user + 1).times do + mailing_list_user.email_logs.create( + email_type: "foobar", + to_address: mailing_list_user.email, + ) + end expect do - UserNotifications.expects(:mailing_list_notify) - .with(mailing_list_user, post) - .never + UserNotifications.expects(:mailing_list_notify).with(mailing_list_user, post).never - 2.times do - Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id) - end + 2.times { Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id) } - Jobs::NotifyMailingListSubscribers.new.execute( - post_id: Fabricate(:post, user: user).id - ) + Jobs::NotifyMailingListSubscribers.new.execute(post_id: Fabricate(:post, user: user).id) end.to change { SkippedEmailLog.count }.by(1) - expect(SkippedEmailLog.exists?( - email_type: "mailing_list", - user: mailing_list_user, - post: post, - to_address: mailing_list_user.email, - reason_type: SkippedEmailLog.reason_types[:exceeded_emails_limit] - )).to eq(true) + expect( + SkippedEmailLog.exists?( + email_type: "mailing_list", + user: mailing_list_user, + post: post, + to_address: mailing_list_user.email, + reason_type: SkippedEmailLog.reason_types[:exceeded_emails_limit], + ), + ).to eq(true) freeze_time(Time.zone.now.tomorrow + 1.second) expect do post = Fabricate(:post, user: user) - UserNotifications.expects(:mailing_list_notify) - .with(mailing_list_user, post) - .once + UserNotifications.expects(:mailing_list_notify).with(mailing_list_user, post).once - Jobs::NotifyMailingListSubscribers.new.execute( - post_id: post.id - ) + Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id) end.not_to change { SkippedEmailLog.count } end end @@ -229,13 +258,15 @@ RSpec.describe Jobs::NotifyMailingListSubscribers do Jobs::NotifyMailingListSubscribers.new.execute(post_id: post.id) UserNotifications.expects(:mailing_list_notify).with(mailing_list_user, post).never - expect(SkippedEmailLog.exists?( - email_type: "mailing_list", - user: mailing_list_user, - post: post, - to_address: mailing_list_user.email, - reason_type: SkippedEmailLog.reason_types[:exceeded_bounces_limit] - )).to eq(true) + expect( + SkippedEmailLog.exists?( + email_type: "mailing_list", + user: mailing_list_user, + post: post, + to_address: mailing_list_user.email, + reason_type: SkippedEmailLog.reason_types[:exceeded_bounces_limit], + ), + ).to eq(true) end end end diff --git a/spec/jobs/notify_moved_posts_spec.rb b/spec/jobs/notify_moved_posts_spec.rb index dda5fdbbe6..35e40b24e8 100644 --- a/spec/jobs/notify_moved_posts_spec.rb +++ b/spec/jobs/notify_moved_posts_spec.rb @@ -1,33 +1,43 @@ # frozen_string_literal: true RSpec.describe Jobs::NotifyMovedPosts do - it "raises an error without post_ids" do - expect { Jobs::NotifyMovedPosts.new.execute(moved_by_id: 1234) }.to raise_error(Discourse::InvalidParameters) + expect { Jobs::NotifyMovedPosts.new.execute(moved_by_id: 1234) }.to raise_error( + Discourse::InvalidParameters, + ) end it "raises an error without moved_by_id" do - expect { Jobs::NotifyMovedPosts.new.execute(post_ids: [1, 2, 3]) }.to raise_error(Discourse::InvalidParameters) + expect { Jobs::NotifyMovedPosts.new.execute(post_ids: [1, 2, 3]) }.to raise_error( + Discourse::InvalidParameters, + ) end - context 'with post ids' do + context "with post ids" do fab!(:p1) { Fabricate(:post) } fab!(:p2) { Fabricate(:post, user: Fabricate(:evil_trout), topic: p1.topic) } fab!(:p3) { Fabricate(:post, user: p1.user, topic: p1.topic) } fab!(:admin) { Fabricate(:admin) } - let(:moved_post_notifications) { Notification.where(notification_type: Notification.types[:moved_post]) } + let(:moved_post_notifications) do + Notification.where(notification_type: Notification.types[:moved_post]) + end it "should create two notifications" do - expect { Jobs::NotifyMovedPosts.new.execute(post_ids: [p1.id, p2.id, p3.id], moved_by_id: admin.id) }.to change(moved_post_notifications, :count).by(2) + expect { + Jobs::NotifyMovedPosts.new.execute(post_ids: [p1.id, p2.id, p3.id], moved_by_id: admin.id) + }.to change(moved_post_notifications, :count).by(2) end - context 'when moved by one of the posters' do + context "when moved by one of the posters" do it "create one notifications, because the poster is the mover" do - expect { Jobs::NotifyMovedPosts.new.execute(post_ids: [p1.id, p2.id, p3.id], moved_by_id: p1.user_id) }.to change(moved_post_notifications, :count).by(1) + expect { + Jobs::NotifyMovedPosts.new.execute( + post_ids: [p1.id, p2.id, p3.id], + moved_by_id: p1.user_id, + ) + }.to change(moved_post_notifications, :count).by(1) end end - end - end diff --git a/spec/jobs/notify_reviewable_spec.rb b/spec/jobs/notify_reviewable_spec.rb index fe7c46bd98..4eecaf75e1 100644 --- a/spec/jobs/notify_reviewable_spec.rb +++ b/spec/jobs/notify_reviewable_spec.rb @@ -3,7 +3,7 @@ RSpec.describe Jobs::NotifyReviewable do # remove all the legacy stuff here when redesigned_user_menu_enabled is # removed - describe '#execute' do + describe "#execute" do fab!(:admin) { Fabricate(:admin, moderator: true) } fab!(:moderator) { Fabricate(:moderator) } fab!(:group_user) { Fabricate(:group_user) } @@ -20,9 +20,8 @@ RSpec.describe Jobs::NotifyReviewable do admin_reviewable = Fabricate(:reviewable, reviewable_by_moderator: false) admin.update!(last_seen_reviewable_id: admin_reviewable.id) - messages = MessageBus.track_publish do - described_class.new.execute(reviewable_id: admin_reviewable.id) - end + messages = + MessageBus.track_publish { described_class.new.execute(reviewable_id: admin_reviewable.id) } expect(messages.size).to eq(1) @@ -36,9 +35,10 @@ RSpec.describe Jobs::NotifyReviewable do # Content for moderators moderator_reviewable = Fabricate(:reviewable, reviewable_by_moderator: true) - messages = MessageBus.track_publish do - described_class.new.execute(reviewable_id: moderator_reviewable.id) - end + messages = + MessageBus.track_publish do + described_class.new.execute(reviewable_id: moderator_reviewable.id) + end expect(messages.size).to eq(2) admin_message = messages.find { |m| m.user_ids == [admin.id] } @@ -56,11 +56,11 @@ RSpec.describe Jobs::NotifyReviewable do moderator.update!(last_seen_reviewable_id: moderator_reviewable.id) # Content for a group - group_reviewable = Fabricate(:reviewable, reviewable_by_moderator: true, reviewable_by_group: group) + group_reviewable = + Fabricate(:reviewable, reviewable_by_moderator: true, reviewable_by_group: group) - messages = MessageBus.track_publish do - described_class.new.execute(reviewable_id: group_reviewable.id) - end + messages = + MessageBus.track_publish { described_class.new.execute(reviewable_id: group_reviewable.id) } expect(messages.size).to eq(3) @@ -93,9 +93,8 @@ RSpec.describe Jobs::NotifyReviewable do admin_reviewable = Fabricate(:reviewable, reviewable_by_moderator: false) admin.update!(last_seen_reviewable_id: admin_reviewable.id) - messages = MessageBus.track_publish do - described_class.new.execute(reviewable_id: admin_reviewable.id) - end + messages = + MessageBus.track_publish { described_class.new.execute(reviewable_id: admin_reviewable.id) } expect(messages.size).to eq(1) @@ -109,9 +108,10 @@ RSpec.describe Jobs::NotifyReviewable do # Content for moderators moderator_reviewable = Fabricate(:reviewable, reviewable_by_moderator: true) - messages = MessageBus.track_publish do - described_class.new.execute(reviewable_id: moderator_reviewable.id) - end + messages = + MessageBus.track_publish do + described_class.new.execute(reviewable_id: moderator_reviewable.id) + end expect(messages.size).to eq(2) @@ -128,11 +128,11 @@ RSpec.describe Jobs::NotifyReviewable do moderator.update!(last_seen_reviewable_id: moderator_reviewable.id) # Content for a group - group_reviewable = Fabricate(:reviewable, reviewable_by_moderator: true, reviewable_by_group: group) + group_reviewable = + Fabricate(:reviewable, reviewable_by_moderator: true, reviewable_by_group: group) - messages = MessageBus.track_publish do - described_class.new.execute(reviewable_id: group_reviewable.id) - end + messages = + MessageBus.track_publish { described_class.new.execute(reviewable_id: group_reviewable.id) } expect(messages.size).to eq(3) @@ -158,9 +158,10 @@ RSpec.describe Jobs::NotifyReviewable do GroupUser.create!(group_id: group.id, user_id: moderator.id) reviewable = Fabricate(:reviewable, reviewable_by_moderator: true, reviewable_by_group: group) - messages = MessageBus.track_publish("/reviewable_counts") do - described_class.new.execute(reviewable_id: reviewable.id) - end + messages = + MessageBus.track_publish("/reviewable_counts") do + described_class.new.execute(reviewable_id: reviewable.id) + end group_user_message = messages.find { |m| m.user_ids.include?(user.id) } @@ -171,16 +172,17 @@ RSpec.describe Jobs::NotifyReviewable do SiteSetting.navigation_menu = "legacy" SiteSetting.enable_category_group_moderation = true Reviewable.set_priorities(medium: 2.0) - SiteSetting.reviewable_default_visibility = 'medium' + SiteSetting.reviewable_default_visibility = "medium" GroupUser.create!(group_id: group.id, user_id: moderator.id) # Content for admins only admin_reviewable = Fabricate(:reviewable, reviewable_by_moderator: false) - messages = MessageBus.track_publish("/reviewable_counts") do - described_class.new.execute(reviewable_id: admin_reviewable.id) - end + messages = + MessageBus.track_publish("/reviewable_counts") do + described_class.new.execute(reviewable_id: admin_reviewable.id) + end admin_message = messages.find { |m| m.user_ids.include?(admin.id) } expect(admin_message.data[:reviewable_count]).to eq(0) @@ -188,9 +190,10 @@ RSpec.describe Jobs::NotifyReviewable do # Content for moderators moderator_reviewable = Fabricate(:reviewable, reviewable_by_moderator: true) - messages = MessageBus.track_publish("/reviewable_counts") do - described_class.new.execute(reviewable_id: moderator_reviewable.id) - end + messages = + MessageBus.track_publish("/reviewable_counts") do + described_class.new.execute(reviewable_id: moderator_reviewable.id) + end admin_message = messages.find { |m| m.user_ids.include?(admin.id) } @@ -200,11 +203,13 @@ RSpec.describe Jobs::NotifyReviewable do expect(moderator_message.data[:reviewable_count]).to eq(0) # Content for a group - group_reviewable = Fabricate(:reviewable, reviewable_by_moderator: true, reviewable_by_group: group) + group_reviewable = + Fabricate(:reviewable, reviewable_by_moderator: true, reviewable_by_group: group) - messages = MessageBus.track_publish("/reviewable_counts") do - described_class.new.execute(reviewable_id: group_reviewable.id) - end + messages = + MessageBus.track_publish("/reviewable_counts") do + described_class.new.execute(reviewable_id: group_reviewable.id) + end admin_message = messages.find { |m| m.user_ids.include?(admin.id) } expect(admin_message.data[:reviewable_count]).to eq(0) @@ -218,13 +223,14 @@ RSpec.describe Jobs::NotifyReviewable do end end - it 'skips sending notifications if user_ids is empty' do + it "skips sending notifications if user_ids is empty" do reviewable = Fabricate(:reviewable, reviewable_by_moderator: true) regular_user = Fabricate(:user) - messages = MessageBus.track_publish("/reviewable_counts") do - described_class.new.execute(reviewable_id: reviewable.id) - end + messages = + MessageBus.track_publish("/reviewable_counts") do + described_class.new.execute(reviewable_id: reviewable.id) + end expect(messages.size).to eq(0) end diff --git a/spec/jobs/notify_tag_change_spec.rb b/spec/jobs/notify_tag_change_spec.rb index 581fd0e251..964024f44a 100644 --- a/spec/jobs/notify_tag_change_spec.rb +++ b/spec/jobs/notify_tag_change_spec.rb @@ -4,20 +4,19 @@ RSpec.describe ::Jobs::NotifyTagChange do fab!(:user) { Fabricate(:user) } fab!(:regular_user) { Fabricate(:trust_level_4) } fab!(:post) { Fabricate(:post, user: regular_user) } - fab!(:tag) { Fabricate(:tag, name: 'test') } + fab!(:tag) { Fabricate(:tag, name: "test") } it "creates notification for watched tag" do TagUser.create!( user_id: user.id, tag_id: tag.id, - notification_level: NotificationLevels.topic_levels[:watching] - ) - TopicTag.create!( - topic_id: post.topic.id, - tag_id: tag.id + notification_level: NotificationLevels.topic_levels[:watching], ) + TopicTag.create!(topic_id: post.topic.id, tag_id: tag.id) - expect { described_class.new.execute(post_id: post.id, notified_user_ids: [regular_user.id]) }.to change { Notification.count } + expect { + described_class.new.execute(post_id: post.id, notified_user_ids: [regular_user.id]) + }.to change { Notification.count } notification = Notification.last expect(notification.user_id).to eq(user.id) expect(notification.topic_id).to eq(post.topic_id) @@ -30,14 +29,13 @@ RSpec.describe ::Jobs::NotifyTagChange do TagUser.create!( user_id: user.id, tag_id: tag.id, - notification_level: NotificationLevels.topic_levels[:watching] - ) - TopicTag.create!( - topic_id: post.topic.id, - tag_id: tag.id + notification_level: NotificationLevels.topic_levels[:watching], ) + TopicTag.create!(topic_id: post.topic.id, tag_id: tag.id) - expect { described_class.new.execute(post_id: post.id, notified_user_ids: [regular_user.id]) }.not_to change { Notification.count } + expect { + described_class.new.execute(post_id: post.id, notified_user_ids: [regular_user.id]) + }.not_to change { Notification.count } end it "doesn't create notification for the editor who watches new tag" do @@ -45,41 +43,78 @@ RSpec.describe ::Jobs::NotifyTagChange do TopicTag.create!(topic: post.topic, tag: tag) post.update!(last_editor_id: user.id) - expect { described_class.new.execute(post_id: post.id, notified_user_ids: []) }.not_to change { Notification.count } + expect { described_class.new.execute(post_id: post.id, notified_user_ids: []) }.not_to change { + Notification.count + } end - it 'doesnt create notification for user watching category' do + it "doesnt create notification for user watching category" do CategoryUser.create!( user_id: user.id, category_id: post.topic.category_id, - notification_level: TopicUser.notification_levels[:watching] + notification_level: TopicUser.notification_levels[:watching], ) - expect { described_class.new.execute(post_id: post.id, notified_user_ids: [regular_user.id]) }.not_to change { Notification.count } + expect { + described_class.new.execute(post_id: post.id, notified_user_ids: [regular_user.id]) + }.not_to change { Notification.count } end - describe 'hidden tag' do - let!(:hidden_group) { Fabricate(:group, name: 'hidden_group') } - let!(:hidden_tag_group) { Fabricate(:tag_group, name: 'hidden', permissions: [[hidden_group.id, :full]]) } - let!(:topic_user) { Fabricate(:topic_user, user: user, topic: post.topic, notification_level: TopicUser.notification_levels[:watching]) } + describe "hidden tag" do + let!(:hidden_group) { Fabricate(:group, name: "hidden_group") } + let!(:hidden_tag_group) do + Fabricate(:tag_group, name: "hidden", permissions: [[hidden_group.id, :full]]) + end + let!(:topic_user) do + Fabricate( + :topic_user, + user: user, + topic: post.topic, + notification_level: TopicUser.notification_levels[:watching], + ) + end - it 'does not create notification for watching user who does not belong to group' do + it "does not create notification for watching user who does not belong to group" do TagGroupMembership.create!(tag_group_id: hidden_tag_group.id, tag_id: tag.id) - expect { described_class.new.execute(post_id: post.id, notified_user_ids: [regular_user.id], diff_tags: [tag.name]) }.not_to change { Notification.count } + expect { + described_class.new.execute( + post_id: post.id, + notified_user_ids: [regular_user.id], + diff_tags: [tag.name], + ) + }.not_to change { Notification.count } Fabricate(:group_user, group: hidden_group, user: user) - expect { described_class.new.execute(post_id: post.id, notified_user_ids: [regular_user.id], diff_tags: [tag.name]) }.to change { Notification.count } + expect { + described_class.new.execute( + post_id: post.id, + notified_user_ids: [regular_user.id], + diff_tags: [tag.name], + ) + }.to change { Notification.count } end - it 'creates notification when at least added or removed tag is visible to everyone' do - visible_tag = Fabricate(:tag, name: 'visible tag') - visible_group = Fabricate(:tag_group, name: 'visible group') + it "creates notification when at least added or removed tag is visible to everyone" do + visible_tag = Fabricate(:tag, name: "visible tag") + visible_group = Fabricate(:tag_group, name: "visible group") TagGroupMembership.create!(tag_group_id: visible_group.id, tag_id: visible_tag.id) TagGroupMembership.create!(tag_group_id: hidden_tag_group.id, tag_id: tag.id) - expect { described_class.new.execute(post_id: post.id, notified_user_ids: [regular_user.id], diff_tags: [tag.name]) }.not_to change { Notification.count } - expect { described_class.new.execute(post_id: post.id, notified_user_ids: [regular_user.id], diff_tags: [tag.name, visible_tag.name]) }.to change { Notification.count } + expect { + described_class.new.execute( + post_id: post.id, + notified_user_ids: [regular_user.id], + diff_tags: [tag.name], + ) + }.not_to change { Notification.count } + expect { + described_class.new.execute( + post_id: post.id, + notified_user_ids: [regular_user.id], + diff_tags: [tag.name, visible_tag.name], + ) + }.to change { Notification.count } end end end diff --git a/spec/jobs/old_keys_reminder_spec.rb b/spec/jobs/old_keys_reminder_spec.rb index a6d4fb89db..f2ccae695d 100644 --- a/spec/jobs/old_keys_reminder_spec.rb +++ b/spec/jobs/old_keys_reminder_spec.rb @@ -1,28 +1,48 @@ # frozen_string_literal: true RSpec.describe Jobs::OldKeysReminder do - let!(:google_secret) { SiteSetting.create!(name: 'google_oauth2_client_secret', value: '123', data_type: 1) } - let!(:github_secret) { SiteSetting.create!(name: 'github_client_secret', value: '123', data_type: 1) } - let!(:api_key) { Fabricate(:api_key, description: 'api key description') } + let!(:google_secret) do + SiteSetting.create!(name: "google_oauth2_client_secret", value: "123", data_type: 1) + end + let!(:github_secret) do + SiteSetting.create!(name: "github_client_secret", value: "123", data_type: 1) + end + let!(:api_key) { Fabricate(:api_key, description: "api key description") } let!(:admin) { Fabricate(:admin) } let!(:another_admin) { Fabricate(:admin) } - let!(:recent_twitter_secret) { SiteSetting.create!(name: 'twitter_consumer_secret', value: '123', data_type: 1, updated_at: 2.years.from_now) } - let!(:recent_api_key) { Fabricate(:api_key, description: 'recent api key description', created_at: 2.years.from_now, user_id: admin.id) } + let!(:recent_twitter_secret) do + SiteSetting.create!( + name: "twitter_consumer_secret", + value: "123", + data_type: 1, + updated_at: 2.years.from_now, + ) + end + let!(:recent_api_key) do + Fabricate( + :api_key, + description: "recent api key description", + created_at: 2.years.from_now, + user_id: admin.id, + ) + end - it 'is disabled be default' do + it "is disabled be default" do freeze_time 2.years.from_now expect { described_class.new.execute({}) }.not_to change { Post.count } end - it 'sends message to admin with old credentials' do - SiteSetting.send_old_credential_reminder_days = '365' + it "sends message to admin with old credentials" do + SiteSetting.send_old_credential_reminder_days = "365" freeze_time 2.years.from_now expect { described_class.new.execute({}) }.to change { Post.count }.by(1) post = Post.last expect(post.archetype).to eq(Archetype.private_message) - expect(post.topic.topic_allowed_users.map(&:user_id).sort).to eq([Discourse.system_user.id, admin.id, another_admin.id].sort) - expect(post.topic.title).to eq('Reminder about old credentials') + expect(post.topic.topic_allowed_users.map(&:user_id).sort).to eq( + [Discourse.system_user.id, admin.id, another_admin.id].sort, + ) + expect(post.topic.title).to eq("Reminder about old credentials") expect(post.raw).to eq(<<~TEXT.rstrip) Hello! This is a routine yearly security reminder from your Discourse instance. @@ -39,7 +59,7 @@ RSpec.describe Jobs::OldKeysReminder do freeze_time 4.years.from_now described_class.new.execute({}) post = Post.last - expect(post.topic.title).to eq('Reminder about old credentials') + expect(post.topic.title).to eq("Reminder about old credentials") expect(post.raw).to eq(<<~TEXT.rstrip) Hello! This is a routine yearly security reminder from your Discourse instance. @@ -55,15 +75,15 @@ RSpec.describe Jobs::OldKeysReminder do TEXT end - it 'does not send message when send_old_credential_reminder_days is set to 0 or no old keys' do + it "does not send message when send_old_credential_reminder_days is set to 0 or no old keys" do expect { described_class.new.execute({}) }.not_to change { Post.count } - SiteSetting.send_old_credential_reminder_days = '0' + SiteSetting.send_old_credential_reminder_days = "0" freeze_time 2.years.from_now expect { described_class.new.execute({}) }.not_to change { Post.count } end - it 'does not send a message if already exists' do - SiteSetting.send_old_credential_reminder_days = '367' + it "does not send a message if already exists" do + SiteSetting.send_old_credential_reminder_days = "367" freeze_time 2.years.from_now expect { described_class.new.execute({}) }.to change { Post.count }.by(1) Topic.last.trash! diff --git a/spec/jobs/open_topic_spec.rb b/spec/jobs/open_topic_spec.rb index 9a401db727..fc6bc26bab 100644 --- a/spec/jobs/open_topic_spec.rb +++ b/spec/jobs/open_topic_spec.rb @@ -3,23 +3,17 @@ RSpec.describe Jobs::OpenTopic do fab!(:admin) { Fabricate(:admin) } - fab!(:topic) do - Fabricate(:topic_timer, user: admin).topic - end + fab!(:topic) { Fabricate(:topic_timer, user: admin).topic } - before do - topic.update!(closed: true) - end + before { topic.update!(closed: true) } - it 'should work' do + it "should work" do freeze_time(61.minutes.from_now) do described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) expect(topic.reload.open?).to eq(true) - expect(Post.last.raw).to eq(I18n.t( - 'topic_statuses.autoclosed_disabled_minutes', count: 61 - )) + expect(Post.last.raw).to eq(I18n.t("topic_statuses.autoclosed_disabled_minutes", count: 61)) end end @@ -31,22 +25,15 @@ RSpec.describe Jobs::OpenTopic do end end - describe 'when category has auto close configured' do + describe "when category has auto close configured" do fab!(:category) do - Fabricate(:category, - auto_close_based_on_last_post: true, - auto_close_hours: 5 - ) + Fabricate(:category, auto_close_based_on_last_post: true, auto_close_hours: 5) end fab!(:topic) { Fabricate(:topic, category: category, closed: true) } it "should restore the category's auto close timer" do - Fabricate(:topic_timer, - status_type: TopicTimer.types[:open], - topic: topic, - user: admin - ) + Fabricate(:topic_timer, status_type: TopicTimer.types[:open], topic: topic, user: admin) freeze_time(61.minutes.from_now) do described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) @@ -61,14 +48,12 @@ RSpec.describe Jobs::OpenTopic do end end - describe 'when user is no longer authorized to open topics' do + describe "when user is no longer authorized to open topics" do fab!(:user) { Fabricate(:user) } - fab!(:topic) do - Fabricate(:topic_timer, user: user).topic - end + fab!(:topic) { Fabricate(:topic_timer, user: user).topic } - it 'should destroy the topic timer' do + it "should destroy the topic timer" do topic.update!(closed: true) freeze_time(topic.public_topic_timer.execute_at + 1.minute) @@ -80,10 +65,7 @@ RSpec.describe Jobs::OpenTopic do end it "should reconfigure topic timer if category's topics are set to autoclose" do - category = Fabricate(:category, - auto_close_based_on_last_post: true, - auto_close_hours: 5 - ) + category = Fabricate(:category, auto_close_based_on_last_post: true, auto_close_hours: 5) topic = Fabricate(:topic, category: category) topic.public_topic_timer.update!(user: user) @@ -92,12 +74,10 @@ RSpec.describe Jobs::OpenTopic do freeze_time(topic.public_topic_timer.execute_at + 1.minute) expect do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: true - ) - end.to change { topic.reload.public_topic_timer.user }.from(user).to(Discourse.system_user) - .and change { topic.public_topic_timer.id } + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: true) + end.to change { topic.reload.public_topic_timer.user }.from(user).to( + Discourse.system_user, + ).and change { topic.public_topic_timer.id } expect(topic.reload.closed).to eq(false) end diff --git a/spec/jobs/pending_queued_posts_reminder_spec.rb b/spec/jobs/pending_queued_posts_reminder_spec.rb index 7611556dd5..0d33627c04 100644 --- a/spec/jobs/pending_queued_posts_reminder_spec.rb +++ b/spec/jobs/pending_queued_posts_reminder_spec.rb @@ -8,9 +8,7 @@ RSpec.describe Jobs::PendingQueuedPostsReminder do it "never emails" do described_class.any_instance.expects(:should_notify_ids).never - expect { - job.execute({}) - }.to_not change { Post.count } + expect { job.execute({}) }.to_not change { Post.count } end end @@ -25,37 +23,35 @@ RSpec.describe Jobs::PendingQueuedPostsReminder do Fabricate(:reviewable_queued_post, created_at: 14.minutes.ago) # expect 16 minute post to be picked up but not 14 min post expect { job.execute({}) }.to change { Post.count }.by(1) - expect(Topic.where( - subtype: TopicSubtype.system_message, - title: I18n.t('system_messages.queued_posts_reminder.subject_template', count: 1) - ).exists?).to eq(true) + expect( + Topic.where( + subtype: TopicSubtype.system_message, + title: I18n.t("system_messages.queued_posts_reminder.subject_template", count: 1), + ).exists?, + ).to eq(true) end end context "when notify_about_queued_posts_after is 24" do - before do - SiteSetting.notify_about_queued_posts_after = 24 - end + before { SiteSetting.notify_about_queued_posts_after = 24 } context "when we haven't been notified in a while" do - before do - job.last_notified_id = nil - end + before { job.last_notified_id = nil } it "doesn't create system message if there are no queued posts" do - expect { - job.execute({}) - }.to_not change { Post.count } + expect { job.execute({}) }.to_not change { Post.count } end it "creates system message if there are new queued posts" do Fabricate(:reviewable_queued_post, created_at: 48.hours.ago) Fabricate(:reviewable_queued_post, created_at: 45.hours.ago) expect { job.execute({}) }.to change { Post.count }.by(1) - expect(Topic.where( - subtype: TopicSubtype.system_message, - title: I18n.t('system_messages.queued_posts_reminder.subject_template', count: 2) - ).exists?).to eq(true) + expect( + Topic.where( + subtype: TopicSubtype.system_message, + title: I18n.t("system_messages.queued_posts_reminder.subject_template", count: 2), + ).exists?, + ).to eq(true) end end diff --git a/spec/jobs/pending_reviewables_reminder_spec.rb b/spec/jobs/pending_reviewables_reminder_spec.rb index 2b6d4deafd..bb3fa0f4c7 100644 --- a/spec/jobs/pending_reviewables_reminder_spec.rb +++ b/spec/jobs/pending_reviewables_reminder_spec.rb @@ -4,7 +4,12 @@ RSpec.describe Jobs::PendingReviewablesReminder do let(:job) { described_class.new } def create_flag(created_at) - PostActionCreator.create(Fabricate(:user), Fabricate(:post), :spam, created_at: created_at).reviewable + PostActionCreator.create( + Fabricate(:user), + Fabricate(:post), + :spam, + created_at: created_at, + ).reviewable end def execute @@ -44,9 +49,7 @@ RSpec.describe Jobs::PendingReviewablesReminder do described_class.clear_key end - after do - described_class.clear_key - end + after { described_class.clear_key } it "doesn't send message when flags are less than 48 hours old" do create_flag(47.hours.ago) @@ -76,27 +79,30 @@ RSpec.describe Jobs::PendingReviewablesReminder do it "doesn't send a message when `reviewable_default_visibility` is not met" do Reviewable.set_priorities(medium: 3.0) - SiteSetting.reviewable_default_visibility = 'medium' + SiteSetting.reviewable_default_visibility = "medium" expect(execute.sent_reminder).to eq(false) end it "sends a message when `reviewable_default_visibility` is met" do Reviewable.set_priorities(medium: 2.0) - SiteSetting.reviewable_default_visibility = 'medium' + SiteSetting.reviewable_default_visibility = "medium" expect(execute.sent_reminder).to eq(true) end end - it 'deletes previous messages' do + it "deletes previous messages" do GroupMessage.create( - Group[:moderators].name, 'reviewables_reminder', - { limit_once_per: false, message_params: { mentions: '', count: 1 } } + Group[:moderators].name, + "reviewables_reminder", + { limit_once_per: false, message_params: { mentions: "", count: 1 } }, ) create_flag(49.hours.ago) execute - expect(Topic.where(title: I18n.t("system_messages.reviewables_reminder.subject_template")).count).to eq(1) + expect( + Topic.where(title: I18n.t("system_messages.reviewables_reminder.subject_template")).count, + ).to eq(1) end end end diff --git a/spec/jobs/pending_users_reminder_spec.rb b/spec/jobs/pending_users_reminder_spec.rb index c482231d6e..bb2e32c6b0 100644 --- a/spec/jobs/pending_users_reminder_spec.rb +++ b/spec/jobs/pending_users_reminder_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe Jobs::PendingUsersReminder do - context 'when must_approve_users is true' do + context "when must_approve_users is true" do before do SiteSetting.must_approve_users = true Jobs::PendingUsersReminder.any_instance.stubs(:previous_newest_username).returns(nil) @@ -42,16 +42,17 @@ RSpec.describe Jobs::PendingUsersReminder do it "sets the correct pending user count in the notification" do SiteSetting.pending_users_reminder_delay_minutes = 8 Fabricate(:user, created_at: 9.minutes.ago) - PostCreator.expects(:create).with(Discourse.system_user, has_entries(title: '1 user waiting for approval')) + PostCreator.expects(:create).with( + Discourse.system_user, + has_entries(title: "1 user waiting for approval"), + ) Jobs::PendingUsersReminder.new.execute({}) end end end - context 'when must_approve_users is false' do - before do - SiteSetting.must_approve_users = false - end + context "when must_approve_users is false" do + before { SiteSetting.must_approve_users = false } it "doesn't send a message to anyone when there are pending users" do AdminUserIndexQuery.any_instance.stubs(:find_users_query).returns(stub_everything(count: 1)) diff --git a/spec/jobs/periodical_updates_spec.rb b/spec/jobs/periodical_updates_spec.rb index f6282fb956..3b0749eb4e 100644 --- a/spec/jobs/periodical_updates_spec.rb +++ b/spec/jobs/periodical_updates_spec.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true RSpec.describe Jobs::PeriodicalUpdates do - it "works" do - # does not blow up, no mocks, everything is called Jobs::PeriodicalUpdates.new.execute(nil) end diff --git a/spec/jobs/poll_mailbox_spec.rb b/spec/jobs/poll_mailbox_spec.rb index 3ba8fe72fe..ab6162e60b 100644 --- a/spec/jobs/poll_mailbox_spec.rb +++ b/spec/jobs/poll_mailbox_spec.rb @@ -20,8 +20,7 @@ RSpec.describe Jobs::PollMailbox do describe ".poll_pop3" do # the date is dynamic here because there is a 1 week cutoff for # the pop3 polling - let(:example_email) do - email = <<~EMAIL + let(:example_email) { email = <<~EMAIL } Return-Path: From: One To: team@bar.com @@ -34,27 +33,22 @@ RSpec.describe Jobs::PollMailbox do This is an email example. EMAIL - end context "with pop errors" do - before do - Discourse.expects(:handle_job_exception).at_least_once - end + before { Discourse.expects(:handle_job_exception).at_least_once } - after do - Discourse.redis.del(Jobs::PollMailbox::POLL_MAILBOX_TIMEOUT_ERROR_KEY) - end + after { Discourse.redis.del(Jobs::PollMailbox::POLL_MAILBOX_TIMEOUT_ERROR_KEY) } it "add an admin dashboard message on pop authentication error" do - Net::POP3.any_instance.expects(:start) - .raises(Net::POPAuthenticationError.new).at_least_once + Net::POP3.any_instance.expects(:start).raises(Net::POPAuthenticationError.new).at_least_once poller.poll_pop3 - i18n_key = 'dashboard.poll_pop3_auth_error' + i18n_key = "dashboard.poll_pop3_auth_error" - expect(AdminDashboardData.problem_message_check(i18n_key)) - .to eq(I18n.t(i18n_key, base_path: Discourse.base_path)) + expect(AdminDashboardData.problem_message_check(i18n_key)).to eq( + I18n.t(i18n_key, base_path: Discourse.base_path), + ) end it "logs an error on pop connection timeout error" do @@ -62,10 +56,11 @@ RSpec.describe Jobs::PollMailbox do 4.times { poller.poll_pop3 } - i18n_key = 'dashboard.poll_pop3_timeout' + i18n_key = "dashboard.poll_pop3_timeout" - expect(AdminDashboardData.problem_message_check(i18n_key)) - .to eq(I18n.t(i18n_key, base_path: Discourse.base_path)) + expect(AdminDashboardData.problem_message_check(i18n_key)).to eq( + I18n.t(i18n_key, base_path: Discourse.base_path), + ) end it "logs an error when pop fails and continues with next message" do @@ -91,7 +86,14 @@ RSpec.describe Jobs::PollMailbox do SiteSetting.pop3_polling_delete_from_server = true - poller.expects(:mail_too_old?).returns(false).then.raises(RuntimeError).then.returns(false).times(3) + poller + .expects(:mail_too_old?) + .returns(false) + .then + .raises(RuntimeError) + .then + .returns(false) + .times(3) poller.expects(:process_popmail).times(2) poller.poll_pop3 end @@ -162,12 +164,14 @@ RSpec.describe Jobs::PollMailbox do end it "does not reply to a bounced email" do - expect { process_popmail(:bounced_email) }.to_not change { ActionMailer::Base.deliveries.count } + expect { process_popmail(:bounced_email) }.to_not change { + ActionMailer::Base.deliveries.count + } incoming_email = IncomingEmail.last expect(incoming_email.rejection_message).to eq( - I18n.t("emails.incoming.errors.bounced_email_error") + I18n.t("emails.incoming.errors.bounced_email_error"), ) end end diff --git a/spec/jobs/post_update_topic_tracking_state_spec.rb b/spec/jobs/post_update_topic_tracking_state_spec.rb index 3bbed8a1ef..442f557401 100644 --- a/spec/jobs/post_update_topic_tracking_state_spec.rb +++ b/spec/jobs/post_update_topic_tracking_state_spec.rb @@ -3,12 +3,12 @@ RSpec.describe Jobs::PostUpdateTopicTrackingState do fab!(:post) { Fabricate(:post) } - it 'should publish messages' do + it "should publish messages" do messages = MessageBus.track_publish { subject.execute({ post_id: post.id }) } expect(messages.size).not_to eq(0) end - it 'should not publish messages for deleted topics' do + it "should not publish messages for deleted topics" do post.topic.trash! messages = MessageBus.track_publish { subject.execute({ post_id: post.id }) } expect(messages.size).to eq(0) diff --git a/spec/jobs/post_uploads_recovery_spec.rb b/spec/jobs/post_uploads_recovery_spec.rb index 58ab91a506..57c7633406 100644 --- a/spec/jobs/post_uploads_recovery_spec.rb +++ b/spec/jobs/post_uploads_recovery_spec.rb @@ -1,15 +1,13 @@ # frozen_string_literal: true RSpec.describe Jobs::PostUploadsRecovery do - describe '#grace_period' do - it 'should restrict the grace period to the right range' do - SiteSetting.purge_deleted_uploads_grace_period_days = - described_class::MIN_PERIOD - 1 + describe "#grace_period" do + it "should restrict the grace period to the right range" do + SiteSetting.purge_deleted_uploads_grace_period_days = described_class::MIN_PERIOD - 1 expect(described_class.new.grace_period).to eq(30) - SiteSetting.purge_deleted_uploads_grace_period_days = - described_class::MAX_PERIOD + 1 + SiteSetting.purge_deleted_uploads_grace_period_days = described_class::MAX_PERIOD + 1 expect(described_class.new.grace_period).to eq(120) end diff --git a/spec/jobs/problem_checks_spec.rb b/spec/jobs/problem_checks_spec.rb index 7f254f001d..39144f64fc 100644 --- a/spec/jobs/problem_checks_spec.rb +++ b/spec/jobs/problem_checks_spec.rb @@ -22,7 +22,11 @@ RSpec.describe Jobs::ProblemChecks do AdminDashboardData.add_scheduled_problem_check(:test_identifier) do [ AdminDashboardData::Problem.new("big problem"), - AdminDashboardData::Problem.new("yuge problem", priority: "high", identifier: "config_is_a_mess") + AdminDashboardData::Problem.new( + "yuge problem", + priority: "high", + identifier: "config_is_a_mess", + ), ] end @@ -34,8 +38,16 @@ RSpec.describe Jobs::ProblemChecks do it "does not add the same problem twice if the identifier already exists" do AdminDashboardData.add_scheduled_problem_check(:test_identifier) do [ - AdminDashboardData::Problem.new("yuge problem", priority: "high", identifier: "config_is_a_mess"), - AdminDashboardData::Problem.new("nasty problem", priority: "high", identifier: "config_is_a_mess") + AdminDashboardData::Problem.new( + "yuge problem", + priority: "high", + identifier: "config_is_a_mess", + ), + AdminDashboardData::Problem.new( + "nasty problem", + priority: "high", + identifier: "config_is_a_mess", + ), ] end diff --git a/spec/jobs/process_bulk_invite_emails_spec.rb b/spec/jobs/process_bulk_invite_emails_spec.rb index a177e244b8..021dcb0b34 100644 --- a/spec/jobs/process_bulk_invite_emails_spec.rb +++ b/spec/jobs/process_bulk_invite_emails_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true RSpec.describe Jobs::ProcessBulkInviteEmails do - describe '#execute' do - it 'processes pending invites' do + describe "#execute" do + it "processes pending invites" do invite = Fabricate(:invite, emailed_status: Invite.emailed_status_types[:bulk_pending]) described_class.new.execute({}) diff --git a/spec/jobs/process_email_spec.rb b/spec/jobs/process_email_spec.rb index eeedaf8ace..a6240d4464 100644 --- a/spec/jobs/process_email_spec.rb +++ b/spec/jobs/process_email_spec.rb @@ -1,12 +1,10 @@ # frozen_string_literal: true RSpec.describe Jobs::ProcessEmail do - let(:mail) { "From: foo@bar.com\nTo: bar@foo.com\nSubject: FOO BAR\n\nFoo foo bar bar?" } it "process an email without retry" do Email::Processor.expects(:process!).with(mail, retry_on_rate_limit: false, source: nil) Jobs::ProcessEmail.new.execute(mail: mail) end - end diff --git a/spec/jobs/process_post_spec.rb b/spec/jobs/process_post_spec.rb index 98d843fcdc..d7f35b3ff1 100644 --- a/spec/jobs/process_post_spec.rb +++ b/spec/jobs/process_post_spec.rb @@ -5,12 +5,12 @@ RSpec.describe Jobs::ProcessPost do expect { Jobs::ProcessPost.new.perform(post_id: 1, sync_exec: true) }.not_to raise_error end - context 'with a post' do + context "with a post" do fab!(:post) { Fabricate(:post) } - it 'does not erase posts when CookedPostProcessor malfunctions' do + it "does not erase posts when CookedPostProcessor malfunctions" do # Look kids, an actual reason why you want to use mocks - CookedPostProcessor.any_instance.expects(:html).returns(' ') + CookedPostProcessor.any_instance.expects(:html).returns(" ") cooked = post.cooked post.reload @@ -19,7 +19,7 @@ RSpec.describe Jobs::ProcessPost do Jobs::ProcessPost.new.execute(post_id: post.id, cook: true) end - it 'recooks if needed' do + it "recooks if needed" do cooked = post.cooked post.update_columns(cooked: "frogs") @@ -29,8 +29,9 @@ RSpec.describe Jobs::ProcessPost do expect(post.cooked).to eq(cooked) end - it 'processes posts' do - post = Fabricate(:post, raw: "") + it "processes posts" do + post = + Fabricate(:post, raw: "") expect(post.cooked).to match(/http/) stub_image_size @@ -47,8 +48,16 @@ RSpec.describe Jobs::ProcessPost do end it "extracts links to quoted posts" do - quoted_post = Fabricate(:post, raw: "This is a post with a link to https://www.discourse.org", post_number: 42) - post.update_columns(raw: "This quote is the best\n\n[quote=\"#{quoted_post.user.username}, topic:#{quoted_post.topic_id}, post:#{quoted_post.post_number}\"]\n#{quoted_post.excerpt}\n[/quote]") + quoted_post = + Fabricate( + :post, + raw: "This is a post with a link to https://www.discourse.org", + post_number: 42, + ) + post.update_columns( + raw: + "This quote is the best\n\n[quote=\"#{quoted_post.user.username}, topic:#{quoted_post.topic_id}, post:#{quoted_post.post_number}\"]\n#{quoted_post.excerpt}\n[/quote]", + ) stub_image_size # when creating a quote, we also create the reflexion link expect { Jobs::ProcessPost.new.execute(post_id: post.id) }.to change { TopicLink.count }.by(2) @@ -102,9 +111,7 @@ RSpec.describe Jobs::ProcessPost do end context "when download_remote_images_to_local? is enabled" do - before do - SiteSetting.download_remote_images_to_local = true - end + before { SiteSetting.download_remote_images_to_local = true } it "enqueues" do expect_enqueued_with(job: :pull_hotlinked_images, args: { post_id: post.id }) do @@ -117,6 +124,5 @@ RSpec.describe Jobs::ProcessPost do expect(Jobs::PullHotlinkedImages.jobs.size).to eq(0) end end - end end diff --git a/spec/jobs/process_shelved_notifications_spec.rb b/spec/jobs/process_shelved_notifications_spec.rb index fb100d36da..f0e419f733 100644 --- a/spec/jobs/process_shelved_notifications_spec.rb +++ b/spec/jobs/process_shelved_notifications_spec.rb @@ -8,16 +8,22 @@ RSpec.describe Jobs::ProcessShelvedNotifications do future = Fabricate(:do_not_disturb_timing, ends_at: 1.day.from_now) past = Fabricate(:do_not_disturb_timing, starts_at: 2.day.ago, ends_at: 1.minute.ago) - expect { - subject.execute({}) - }.to change { DoNotDisturbTiming.count }.by (-1) + expect { subject.execute({}) }.to change { DoNotDisturbTiming.count }.by (-1) expect(DoNotDisturbTiming.find_by(id: future.id)).to eq(future) expect(DoNotDisturbTiming.find_by(id: past.id)).to eq(nil) end it "does not process shelved_notifications when the user is in DND" do user.do_not_disturb_timings.create(starts_at: 2.days.ago, ends_at: 2.days.from_now) - notification = Notification.create(read: false, user_id: user.id, topic_id: 2, post_number: 1, data: '{}', notification_type: 1) + notification = + Notification.create( + read: false, + user_id: user.id, + topic_id: 2, + post_number: 1, + data: "{}", + notification_type: 1, + ) expect(notification.shelved_notification).to be_present subject.execute({}) expect(notification.shelved_notification).to be_present @@ -25,7 +31,15 @@ RSpec.describe Jobs::ProcessShelvedNotifications do it "processes and destroys shelved_notifications when the user leaves DND" do user.do_not_disturb_timings.create(starts_at: 2.days.ago, ends_at: 2.days.from_now) - notification = Notification.create(read: false, user_id: user.id, topic_id: 2, post_number: 1, data: '{}', notification_type: 1) + notification = + Notification.create( + read: false, + user_id: user.id, + topic_id: 2, + post_number: 1, + data: "{}", + notification_type: 1, + ) user.do_not_disturb_timings.last.update(ends_at: 1.days.ago) expect(notification.shelved_notification).to be_present diff --git a/spec/jobs/publish_topic_to_category_spec.rb b/spec/jobs/publish_topic_to_category_spec.rb index 0f5c213f19..7632005561 100644 --- a/spec/jobs/publish_topic_to_category_spec.rb +++ b/spec/jobs/publish_topic_to_category_spec.rb @@ -7,12 +7,13 @@ RSpec.describe Jobs::PublishTopicToCategory do let(:topic) do topic = Fabricate(:topic, category: category) - Fabricate(:topic_timer, + Fabricate( + :topic_timer, status_type: TopicTimer.types[:publish_to_category], category_id: another_category.id, topic: topic, execute_at: 1.minute.ago, - created_at: 5.minutes.ago + created_at: 5.minutes.ago, ) Fabricate(:post, topic: topic, user: topic.user) @@ -20,8 +21,8 @@ RSpec.describe Jobs::PublishTopicToCategory do topic end - describe 'when topic has been deleted' do - it 'should not publish the topic to the new category' do + describe "when topic has been deleted" do + it "should not publish the topic to the new category" do created_at = freeze_time 1.hour.ago topic @@ -36,18 +37,17 @@ RSpec.describe Jobs::PublishTopicToCategory do end end - it 'should publish the topic to the new category' do + it "should publish the topic to the new category" do freeze_time 1.hour.ago do topic.update!(visible: false) end now = freeze_time - message = MessageBus.track_publish do - described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) - end.find do |m| - Hash === m.data && m.data.key?(:reload_topic) && m.data.key?(:refresh_stream) - end + message = + MessageBus + .track_publish { described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) } + .find { |m| Hash === m.data && m.data.key?(:reload_topic) && m.data.key?(:refresh_stream) } topic.reload expect(topic.category).to eq(another_category) @@ -55,32 +55,36 @@ RSpec.describe Jobs::PublishTopicToCategory do expect(topic.public_topic_timer).to eq(nil) expect(message.channel).to eq("/topic/#{topic.id}") - %w{created_at bumped_at updated_at last_posted_at}.each do |attribute| + %w[created_at bumped_at updated_at last_posted_at].each do |attribute| expect(topic.public_send(attribute)).to eq_time(now) end end - describe 'when topic is a private message' do - it 'should publish the topic to the new category' do + describe "when topic is a private message" do + it "should publish the topic to the new category" do freeze_time 1.hour.ago do - expect { topic.convert_to_private_message(Discourse.system_user) } - .to change { topic.private_message? }.to(true) + expect { topic.convert_to_private_message(Discourse.system_user) }.to change { + topic.private_message? + }.to(true) end topic.allowed_users << topic.public_topic_timer.user now = freeze_time - message = MessageBus.track_publish do - described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) - end.last + message = + MessageBus + .track_publish do + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id) + end + .last topic.reload expect(topic.category).to eq(another_category) expect(topic.visible).to eq(true) expect(topic.private_message?).to eq(false) - %w{created_at bumped_at updated_at last_posted_at}.each do |attribute| + %w[created_at bumped_at updated_at last_posted_at].each do |attribute| expect(topic.public_send(attribute)).to eq_time(now) end @@ -118,8 +122,8 @@ RSpec.describe Jobs::PublishTopicToCategory do end end - describe 'when new category has a default auto-close' do - it 'should apply the auto-close timer upon publishing' do + describe "when new category has a default auto-close" do + it "should apply the auto-close timer upon publishing" do freeze_time another_category.update!(auto_close_hours: 5) diff --git a/spec/jobs/pull_hotlinked_images_spec.rb b/spec/jobs/pull_hotlinked_images_spec.rb index b16915ae42..3f842c275c 100644 --- a/spec/jobs/pull_hotlinked_images_spec.rb +++ b/spec/jobs/pull_hotlinked_images_spec.rb @@ -5,26 +5,44 @@ RSpec.describe Jobs::PullHotlinkedImages do let(:broken_image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat2.png" } let(:large_image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat3.png" } let(:encoded_image_url) { "https://example.com/אלחוט-.jpg" } - let(:png) { Base64.decode64("R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==") } - let(:large_png) { Base64.decode64("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAK10lEQVR42r3aeVRTVx4H8Oc2atWO7Sw9OnM6HWvrOON0aFlcAZ3RopZWOyqgoCACKqPWBUVQi4gIqAVllciiKPu+JOyGnQQSNgkIIQgoKljAYVARCZnf4yXhkeXlJmDP+f4hOUF+n3fvffe++y5W0i4qJqWoDU8hKQUPxWFKcq9VnHxJ8gTi5EqS0yJOtiRZfHEyJWE0i0MnJaMJTzopaQ/wpJKS0ogneTQYABANTDlDvpxBCsiu72eUP0zPq8Fzr45e8TircRDFQAAy5ABpcgDCgJV2iCbRQM+rinU/E26ie9NgfrDO1GBtTBy96SH/WhBhaxwfGEjndmfKGeiaGsYAJXIANQyCkfR05u3dhuOKVhLamnmRzocyKp9mNo9QG9IRDDiAiMaG3Nqfo45aoJROzk3DDxNCbjGahBM0yAKoDfIDOpNZE/bNYrVKJyfylB2D91pdA3lAjwE0MDAyS+BCalw9kdu2xvT6AY0NWBkJoNaAzsrj4CN1YtUTidi/hdH4BvGmJGPAAYgGMuMery/U6ONJqZ5I1PlTjNExre7kgJU/EqEbJC0gjDpiiv9hnSkJ2z+t9dzxwNcSUudlUuuxnXP+W/bZTWWO64uO6hccWQ0pPm4IP1a6GFe5bYXvNF7f0xxg3XrzgCDYjn1m4+218/D/SndaYnSqBpMDDlDXkHYnMlh7Srj+HLanxfOsyyOVN0ScYI0zkOeVZvYZGEI2/DFDMkWgTw7jAGWUA5owMOt7QtcvDF09qybA/mGC6zA7aCLVExkq9U3895/wm9LpgyonBxmDGKDQoHBySPQ8B5e/zM2kJdalN/fqxKsn8oLhFr5mdvDyX6UVNqqcpMmDAWNJACjtUMDrDVn7m6SdS/kxPwrizg+zAycLAKm5tA0a4a7DPpSFhmIAxWAgDKm0IJrutBr/g3D5n9E9J7F6oiNFGf2WtnI2vboH3YADEA0AuG2ml2i2BC4/AAYKr00uAHL/ihk0QnxQMPqKFWM/FiEamFWPYMHD8tgF1UMmZfjKZLDIJ1z/vQibzTKrbop2wAGIhoxbt8IN5zZHnoHqO5LdJr16IkXHDG4afJDJG0B8chADUAxxTnbp1trE5Z/0ASDN09hTcJdLy+EoawQZgyyAwhCxcznr0k4C0JNz5R0BYFqM3PBhQugtxKdQrEICUGFoE4ZtWPAg4jQBeJHv/Y4AkBKHdTHuZ8lP0hSDAQdQGwhAUUNv4s6/EvcfSD/T590B2u8cj3SwltkNUGaQBSgbDAXc9pxTW4jqIf8ruAa37efJLg/DfuBd21ftYU7OA387+QXSk2gHWMmRw/M2F9D2d8WffsW8Sv5+X/mtyBN7s+V2NBQasMpOEYqhuLG3MimMqL4h/GTu4fW01b/z05qrMKEGC96W+8sA8g/qKX281JuWafX350lniG++rIpOTcknb8lQGHAAoqG+pgqqr7hqE2K4kCg0bO3CJDMthvVKInTrlUmm/4j+9vO7mxYNlfrJAJiHVsYaL0g1XZy194scmy+JMCyXxWz+CAD4anTFjLrLpiMVQW+4t1G2lQiDGIBiuF/NLbmwM1B3PpQe892SFtqh4fIAhZ14mBUo34WE7ECFC29hRdDz5LO5dtrwdAGM0pP/HKoMzWsZRtwakwVQGPJjo/2/ej9Q74N8xy19o+tQYcWNzjT3mJNmR/W/uPi9fobr3ifpl6hXeG9Zge1JF5LPWvz4zYoTa7VSzu0mniggMEigNcBQ7GjE5A9Kt/eoOxLGkQBUGkoyGeEbPqnys2+OPlcbdir80PdOX+usmDFdG8OIwCc3bI0vm657WeSrsPouhuelbQZh/9nqY7FB+lsGc2ad27w86oTJo5SLrwu9s/dpVXuYFPEHELcocQC1QXpjhS4EpcMwiPhh2/U9XzfedYYFhe7UKdJSqkNOIt4oMy/uIwP68n6C3/WzMmIFHIUeJawMLm7ul9lmVdYOYgCKob6aK72NEo8yQ+UBtl99BkXoTMFcv1sF3UNaIpd24vCqvykDvCr2PbJ6GQFwNtKFrjhuCHFCCvmvcuW2ihUaMO4TWYCyAU0GSJcSsCblRTjDSJAZoFnuNiafLqReMrQlukKTylQvBZC3iikMOIDCQGaQAT9nq1gLqQRQBABFLa9U7tcTBjEApR3IALh1/DIAlQZZAIWBDOjO9HrXAMT3JliVBKCyHciALsYvAUAx4IAqOYDCmxKPBFD5QDNBQHHLS2XvfmQMYgCKgQx4muGhFmCw1B8dIOTQyvj9FO+vyDclrPqpLECZgVczBoAlA3URMCubLv6D9I657ZOP0lws1QJQv4OTGnAAogEdAF+A+TXHw3b0R5qoszLLyx4+gc8RAeUt/SrfIxIGMYDCoBDwONVdaQ9mB+3XWeK87kvJ1EYTDfYLn9XDgsdO+3NYKSACUN6FQsYAKg2IgIqgY6tnzmi6bP8y2X2EmGUbkkWCPJitV82cURfuqPq5nhPM4vchvpDGauQAygxkAMW+ULCdsfWSj/tCTr8IdeqPdBnK94FnFCEr8DXd68CyRXeObkfpRWx+D+JLdRxANlC0QwMaINHZfP37c4oczQkDnjDnvlCnMuc9RvPnxp/ehQKokAAoOlIeGUDdDvKAtsQLyv72mzJ/P6uN+rNnHtf5S7GjRVeQQ6nTbge9pdB/vEzWDso9aqoEUBuw2mciZY0gY0AEEBHEuZzZqAdFG743c/n0aQ7rtBruOKO/y+HwnyMebsABiIbG2jFAa7wryh4bPDaUXD+swWuoKv5TxMMNYgCFgQSoIgHOv7uNLbgLcfldiAc0xgAqDbVtLwTJXgQAeojmLzLKAzjBxyl257vqcgsfChUeDJA3YHUkgEpDQz2vJU7cCDJTEnQSWOHBDK0wMACgL0U7mLptXWO/fGmCk7myGW2gOra09Q36aSUcoIahc4Rfmi59JBi3H5j3k5fJOs8dhgoTYL0Jqi/1PfyMTrUKHOKGcwS9Kg9okA1iALqh+tGggBFIGJRtn2gWWEHwmlsRD5lIDdj9LpG8gXpyuN/yRJBwEQCwRYWytkEcuB28iuK2EXVPXOEAqaEW2dBUzZI+HE/wTT2RnjpGSZtQg1NjYoDa7dA50sKMIgywyTPB6l9VRbPaXmt28m0MQNEOCgdDbXu/IM17tCO5TaQjveWG1Qi6NT75htWTAOoaeA/4gnhXlF0Wiq7f3NSk1okrGQMO0NzQOdLMziU60usSPw2q7+SVlnWMlE3g1BjG6xZNxFDe1s2OO0Z0JHhxBuMBJlroUSgju682ldUxTH24QaVhDFAvB1Bp4HS+PRO/5ZDP7xtjnaXLJGKlBMtVeGqDuRk2If97z/tl0XVYZg+T3nF0F3tcjN1W2vFWrdNK8gYcgGiQvykFFl7a7oFBvG5o5UfvVRQrRuQu+mjgH5lRu7JjLPISLAtTrJ1pf94dj4U0+mhw4opsEAPU6kiEIZ1XYnZlFgFQKzu8MYtYzKYUs63E7Lnz0ls5iKeVFBrGAGq1A6uj1zZw0XZPzPwuZhqE7biiqm4vzNQP/7JVFmZbgdlxxnKienFBe4/G7YA1kADI7TDilmQJZVlE41cRirBlYdZMzIqB7UnGdseRkohZZmDW+ZhNmfibEHvuzAOcaWTD5XpLuBepdfKtiAxQ1xDPTdnhOdXUH7Nlj7uWKDnAme7bvPlI1a/Hfz4ljp+BfnqPPKD/DzQWIVWNoUiJAAAAAElFTkSuQmCC") } + let(:png) do + Base64.decode64( + "R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==", + ) + end + let(:large_png) do + Base64.decode64( + "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAK10lEQVR42r3aeVRTVx4H8Oc2atWO7Sw9OnM6HWvrOON0aFlcAZ3RopZWOyqgoCACKqPWBUVQi4gIqAVllciiKPu+JOyGnQQSNgkIIQgoKljAYVARCZnf4yXhkeXlJmDP+f4hOUF+n3fvffe++y5W0i4qJqWoDU8hKQUPxWFKcq9VnHxJ8gTi5EqS0yJOtiRZfHEyJWE0i0MnJaMJTzopaQ/wpJKS0ogneTQYABANTDlDvpxBCsiu72eUP0zPq8Fzr45e8TircRDFQAAy5ABpcgDCgJV2iCbRQM+rinU/E26ie9NgfrDO1GBtTBy96SH/WhBhaxwfGEjndmfKGeiaGsYAJXIANQyCkfR05u3dhuOKVhLamnmRzocyKp9mNo9QG9IRDDiAiMaG3Nqfo45aoJROzk3DDxNCbjGahBM0yAKoDfIDOpNZE/bNYrVKJyfylB2D91pdA3lAjwE0MDAyS+BCalw9kdu2xvT6AY0NWBkJoNaAzsrj4CN1YtUTidi/hdH4BvGmJGPAAYgGMuMery/U6ONJqZ5I1PlTjNExre7kgJU/EqEbJC0gjDpiiv9hnSkJ2z+t9dzxwNcSUudlUuuxnXP+W/bZTWWO64uO6hccWQ0pPm4IP1a6GFe5bYXvNF7f0xxg3XrzgCDYjn1m4+218/D/SndaYnSqBpMDDlDXkHYnMlh7Srj+HLanxfOsyyOVN0ScYI0zkOeVZvYZGEI2/DFDMkWgTw7jAGWUA5owMOt7QtcvDF09qybA/mGC6zA7aCLVExkq9U3895/wm9LpgyonBxmDGKDQoHBySPQ8B5e/zM2kJdalN/fqxKsn8oLhFr5mdvDyX6UVNqqcpMmDAWNJACjtUMDrDVn7m6SdS/kxPwrizg+zAycLAKm5tA0a4a7DPpSFhmIAxWAgDKm0IJrutBr/g3D5n9E9J7F6oiNFGf2WtnI2vboH3YADEA0AuG2ml2i2BC4/AAYKr00uAHL/ihk0QnxQMPqKFWM/FiEamFWPYMHD8tgF1UMmZfjKZLDIJ1z/vQibzTKrbop2wAGIhoxbt8IN5zZHnoHqO5LdJr16IkXHDG4afJDJG0B8chADUAxxTnbp1trE5Z/0ASDN09hTcJdLy+EoawQZgyyAwhCxcznr0k4C0JNz5R0BYFqM3PBhQugtxKdQrEICUGFoE4ZtWPAg4jQBeJHv/Y4AkBKHdTHuZ8lP0hSDAQdQGwhAUUNv4s6/EvcfSD/T590B2u8cj3SwltkNUGaQBSgbDAXc9pxTW4jqIf8ruAa37efJLg/DfuBd21ftYU7OA387+QXSk2gHWMmRw/M2F9D2d8WffsW8Sv5+X/mtyBN7s+V2NBQasMpOEYqhuLG3MimMqL4h/GTu4fW01b/z05qrMKEGC96W+8sA8g/qKX281JuWafX350lniG++rIpOTcknb8lQGHAAoqG+pgqqr7hqE2K4kCg0bO3CJDMthvVKInTrlUmm/4j+9vO7mxYNlfrJAJiHVsYaL0g1XZy194scmy+JMCyXxWz+CAD4anTFjLrLpiMVQW+4t1G2lQiDGIBiuF/NLbmwM1B3PpQe892SFtqh4fIAhZ14mBUo34WE7ECFC29hRdDz5LO5dtrwdAGM0pP/HKoMzWsZRtwakwVQGPJjo/2/ej9Q74N8xy19o+tQYcWNzjT3mJNmR/W/uPi9fobr3ifpl6hXeG9Zge1JF5LPWvz4zYoTa7VSzu0mniggMEigNcBQ7GjE5A9Kt/eoOxLGkQBUGkoyGeEbPqnys2+OPlcbdir80PdOX+usmDFdG8OIwCc3bI0vm657WeSrsPouhuelbQZh/9nqY7FB+lsGc2ad27w86oTJo5SLrwu9s/dpVXuYFPEHELcocQC1QXpjhS4EpcMwiPhh2/U9XzfedYYFhe7UKdJSqkNOIt4oMy/uIwP68n6C3/WzMmIFHIUeJawMLm7ul9lmVdYOYgCKob6aK72NEo8yQ+UBtl99BkXoTMFcv1sF3UNaIpd24vCqvykDvCr2PbJ6GQFwNtKFrjhuCHFCCvmvcuW2ihUaMO4TWYCyAU0GSJcSsCblRTjDSJAZoFnuNiafLqReMrQlukKTylQvBZC3iikMOIDCQGaQAT9nq1gLqQRQBABFLa9U7tcTBjEApR3IALh1/DIAlQZZAIWBDOjO9HrXAMT3JliVBKCyHciALsYvAUAx4IAqOYDCmxKPBFD5QDNBQHHLS2XvfmQMYgCKgQx4muGhFmCw1B8dIOTQyvj9FO+vyDclrPqpLECZgVczBoAlA3URMCubLv6D9I657ZOP0lws1QJQv4OTGnAAogEdAF+A+TXHw3b0R5qoszLLyx4+gc8RAeUt/SrfIxIGMYDCoBDwONVdaQ9mB+3XWeK87kvJ1EYTDfYLn9XDgsdO+3NYKSACUN6FQsYAKg2IgIqgY6tnzmi6bP8y2X2EmGUbkkWCPJitV82cURfuqPq5nhPM4vchvpDGauQAygxkAMW+ULCdsfWSj/tCTr8IdeqPdBnK94FnFCEr8DXd68CyRXeObkfpRWx+D+JLdRxANlC0QwMaINHZfP37c4oczQkDnjDnvlCnMuc9RvPnxp/ehQKokAAoOlIeGUDdDvKAtsQLyv72mzJ/P6uN+rNnHtf5S7GjRVeQQ6nTbge9pdB/vEzWDso9aqoEUBuw2mciZY0gY0AEEBHEuZzZqAdFG743c/n0aQ7rtBruOKO/y+HwnyMebsABiIbG2jFAa7wryh4bPDaUXD+swWuoKv5TxMMNYgCFgQSoIgHOv7uNLbgLcfldiAc0xgAqDbVtLwTJXgQAeojmLzLKAzjBxyl257vqcgsfChUeDJA3YHUkgEpDQz2vJU7cCDJTEnQSWOHBDK0wMACgL0U7mLptXWO/fGmCk7myGW2gOra09Q36aSUcoIahc4Rfmi59JBi3H5j3k5fJOs8dhgoTYL0Jqi/1PfyMTrUKHOKGcwS9Kg9okA1iALqh+tGggBFIGJRtn2gWWEHwmlsRD5lIDdj9LpG8gXpyuN/yRJBwEQCwRYWytkEcuB28iuK2EXVPXOEAqaEW2dBUzZI+HE/wTT2RnjpGSZtQg1NjYoDa7dA50sKMIgywyTPB6l9VRbPaXmt28m0MQNEOCgdDbXu/IM17tCO5TaQjveWG1Qi6NT75htWTAOoaeA/4gnhXlF0Wiq7f3NSk1okrGQMO0NzQOdLMziU60usSPw2q7+SVlnWMlE3g1BjG6xZNxFDe1s2OO0Z0JHhxBuMBJlroUSgju682ldUxTH24QaVhDFAvB1Bp4HS+PRO/5ZDP7xtjnaXLJGKlBMtVeGqDuRk2If97z/tl0XVYZg+T3nF0F3tcjN1W2vFWrdNK8gYcgGiQvykFFl7a7oFBvG5o5UfvVRQrRuQu+mjgH5lRu7JjLPISLAtTrJ1pf94dj4U0+mhw4opsEAPU6kiEIZ1XYnZlFgFQKzu8MYtYzKYUs63E7Lnz0ls5iKeVFBrGAGq1A6uj1zZw0XZPzPwuZhqE7biiqm4vzNQP/7JVFmZbgdlxxnKienFBe4/G7YA1kADI7TDilmQJZVlE41cRirBlYdZMzIqB7UnGdseRkohZZmDW+ZhNmfibEHvuzAOcaWTD5XpLuBepdfKtiAxQ1xDPTdnhOdXUH7Nlj7uWKDnAme7bvPlI1a/Hfz4ljp+BfnqPPKD/DzQWIVWNoUiJAAAAAElFTkSuQmCC", + ) + end let(:upload_path) { Discourse.store.upload_path } before do Jobs.run_immediately! stub_request(:get, image_url).to_return(body: png, headers: { "Content-Type" => "image/png" }) - stub_request(:get, encoded_image_url).to_return(body: png, headers: { "Content-Type" => "image/png" }) + stub_request(:get, encoded_image_url).to_return( + body: png, + headers: { + "Content-Type" => "image/png", + }, + ) stub_request(:get, broken_image_url).to_return(status: 404) - stub_request(:get, large_image_url).to_return(body: large_png, headers: { "Content-Type" => "image/png" }) - - stub_request( - :get, - "#{Discourse.base_url}/#{upload_path}/original/1X/f59ea56fe8ebe42048491d43a19d9f34c5d0f8dc.gif" + stub_request(:get, large_image_url).to_return( + body: large_png, + headers: { + "Content-Type" => "image/png", + }, ) stub_request( :get, - "#{Discourse.base_url}/#{upload_path}/original/1X/c530c06cf89c410c0355d7852644a73fc3ec8c04.png" + "#{Discourse.base_url}/#{upload_path}/original/1X/f59ea56fe8ebe42048491d43a19d9f34c5d0f8dc.gif", + ) + + stub_request( + :get, + "#{Discourse.base_url}/#{upload_path}/original/1X/c530c06cf89c410c0355d7852644a73fc3ec8c04.png", ) SiteSetting.download_remote_images_to_local = true @@ -32,22 +50,20 @@ RSpec.describe Jobs::PullHotlinkedImages do SiteSetting.download_remote_images_threshold = 0 end - describe '#execute' do - before do - Jobs.run_immediately! - end + describe "#execute" do + before { Jobs.run_immediately! } - it 'does nothing if topic has been deleted' do + it "does nothing if topic has been deleted" do post = Fabricate(:post, raw: "") post.topic.destroy! - expect do - Jobs::PullHotlinkedImages.new.execute(post_id: post.id) - end.not_to change { Upload.count } + expect do Jobs::PullHotlinkedImages.new.execute(post_id: post.id) end.not_to change { + Upload.count + } end - it 'does nothing if there are no large images to pull' do - post = Fabricate(:post, raw: 'bob bob') + it "does nothing if there are no large images to pull" do + post = Fabricate(:post, raw: "bob bob") orig = post.updated_at freeze_time 1.week.from_now @@ -55,18 +71,18 @@ RSpec.describe Jobs::PullHotlinkedImages do expect(orig).to eq_time(post.reload.updated_at) end - it 'replaces images' do + it "replaces images" do post = Fabricate(:post, raw: "") stub_image_size - expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } - .to change { Upload.count }.by(1) - .and not_change { UserHistory.count } # Should not add to the staff log + expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }.to change { + Upload.count + }.by(1).and not_change { UserHistory.count } # Should not add to the staff log expect(post.reload.raw).to eq("") end - it 'enqueues raw replacement job with a delay' do + it "enqueues raw replacement job with a delay" do Jobs.run_later! post = Fabricate(:post, raw: "") @@ -76,12 +92,16 @@ RSpec.describe Jobs::PullHotlinkedImages do Jobs.expects(:cancel_scheduled_job).with(:update_hotlinked_raw, post_id: post.id).once delay = SiteSetting.editing_grace_period + 1 - expect_enqueued_with(job: :update_hotlinked_raw, args: { post_id: post.id }, at: Time.zone.now + delay.seconds) do - Jobs::PullHotlinkedImages.new.execute(post_id: post.id) - end + expect_enqueued_with( + job: :update_hotlinked_raw, + args: { + post_id: post.id, + }, + at: Time.zone.now + delay.seconds, + ) { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } end - it 'removes downloaded images when they are no longer needed' do + it "removes downloaded images when they are no longer needed" do post = Fabricate(:post, raw: "") stub_image_size post.rebake! @@ -94,43 +114,41 @@ RSpec.describe Jobs::PullHotlinkedImages do expect(post.upload_references.count).to eq(0) end - it 'replaces images again after edit' do + it "replaces images again after edit" do post = Fabricate(:post, raw: "") stub_image_size - expect do - post.rebake! - end.to change { Upload.count }.by(1) + expect do post.rebake! end.to change { Upload.count }.by(1) expect(post.reload.raw).to eq("") # Post raw is updated back to the old value (e.g. by wordpress integration) post.update(raw: "") - expect do - post.rebake! - end.not_to change { Upload.count } # We alread have the upload + expect do post.rebake! end.not_to change { Upload.count } # We alread have the upload expect(post.reload.raw).to eq("") end - it 'replaces encoded image urls' do + it "replaces encoded image urls" do post = Fabricate(:post, raw: "") stub_image_size - expect do - Jobs::PullHotlinkedImages.new.execute(post_id: post.id) - end.to change { Upload.count }.by(1) + expect do Jobs::PullHotlinkedImages.new.execute(post_id: post.id) end.to change { + Upload.count + }.by(1) expect(post.reload.raw).to eq("") end - it 'replaces images in an anchor tag with weird indentation' do + it "replaces images in an anchor tag with weird indentation" do # Skipped pending https://meta.discourse.org/t/152801 # This spec was previously passing, even though the resulting markdown was invalid # Now the spec has been improved, and shows the issue - stub_request(:get, "http://test.localhost/uploads/short-url/z2QSs1KJWoj51uYhDjb6ifCzxH6.gif") - .to_return(status: 200, body: "") + stub_request( + :get, + "http://test.localhost/uploads/short-url/z2QSs1KJWoj51uYhDjb6ifCzxH6.gif", + ).to_return(status: 200, body: "") post = Fabricate(:post, raw: <<~MD)

    @@ -139,9 +157,9 @@ RSpec.describe Jobs::PullHotlinkedImages do MD - expect do - Jobs::PullHotlinkedImages.new.execute(post_id: post.id) - end.to change { Upload.count }.by(1) + expect do Jobs::PullHotlinkedImages.new.execute(post_id: post.id) end.to change { + Upload.count + }.by(1) upload = post.uploads.last @@ -153,55 +171,55 @@ RSpec.describe Jobs::PullHotlinkedImages do MD end - it 'replaces correct image URL' do - url = image_url.sub("/2e/Longcat1.png", '') + it "replaces correct image URL" do + url = image_url.sub("/2e/Longcat1.png", "") post = Fabricate(:post, raw: "[Images](#{url})\n![](#{image_url})") stub_image_size - expect do - Jobs::PullHotlinkedImages.new.execute(post_id: post.id) - end.to change { Upload.count }.by(1) + expect do Jobs::PullHotlinkedImages.new.execute(post_id: post.id) end.to change { + Upload.count + }.by(1) expect(post.reload.raw).to eq("[Images](#{url})\n![](#{Upload.last.short_url})") end - it 'replaces images without protocol' do - url = image_url.sub(/^https?\:/, '') + it "replaces images without protocol" do + url = image_url.sub(/^https?\:/, "") post = Fabricate(:post, raw: "test") stub_image_size - expect do - Jobs::PullHotlinkedImages.new.execute(post_id: post.id) - end.to change { Upload.count }.by(1) + expect do Jobs::PullHotlinkedImages.new.execute(post_id: post.id) end.to change { + Upload.count + }.by(1) expect(post.reload.raw).to eq("\"test\"") end - it 'replaces images without extension' do - url = image_url.sub(/\.[a-zA-Z0-9]+$/, '') + it "replaces images without extension" do + url = image_url.sub(/\.[a-zA-Z0-9]+$/, "") stub_request(:get, url).to_return(body: png, headers: { "Content-Type" => "image/png" }) post = Fabricate(:post, raw: "") stub_image_size - expect do - Jobs::PullHotlinkedImages.new.execute(post_id: post.id) - end.to change { Upload.count }.by(1) + expect do Jobs::PullHotlinkedImages.new.execute(post_id: post.id) end.to change { + Upload.count + }.by(1) expect(post.reload.raw).to eq("") end - it 'replaces optimized images' do + it "replaces optimized images" do optimized_image = Fabricate(:optimized_image) url = "#{Discourse.base_url}#{optimized_image.url}" - stub_request(:get, url) - .to_return(status: 200, body: file_from_fixtures("smallest.png")) + stub_request(:get, url).to_return(status: 200, body: file_from_fixtures("smallest.png")) post = Fabricate(:post, raw: "") stub_image_size - expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } - .to change { Upload.count }.by(1) + expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }.to change { + Upload.count + }.by(1) upload = Upload.last post.reload @@ -233,8 +251,9 @@ RSpec.describe Jobs::PullHotlinkedImages do url = Discourse.base_url + url post = Fabricate(:post, raw: "") upload.update(access_control_post: post) - expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } - .not_to change { Upload.count } + expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }.not_to change { + Upload.count + } end context "when the upload original_sha1 is missing" do @@ -253,8 +272,9 @@ RSpec.describe Jobs::PullHotlinkedImages do # without this we get an infinite hang... Post.any_instance.stubs(:trigger_post_process) - expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } - .to change { Upload.count }.by(1) + expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }.to change { + Upload.count + }.by(1) end end @@ -272,16 +292,18 @@ RSpec.describe Jobs::PullHotlinkedImages do upload.update(access_control_post: Fabricate(:post)) FileStore::S3Store.any_instance.stubs(:store_upload).returns(upload.url) - expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } - .to change { Upload.count }.by(1) + expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }.to change { + Upload.count + }.by(1) - expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } - .not_to change { Upload.count } + expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }.not_to change { + Upload.count + } end end end - it 'replaces markdown image' do + it "replaces markdown image" do post = Fabricate(:post, raw: <<~MD) [![some test](#{image_url})](https://somelink.com) ![some test](#{image_url}) @@ -291,8 +313,9 @@ RSpec.describe Jobs::PullHotlinkedImages do MD stub_image_size - expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } - .to change { Upload.count }.by(1) + expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }.to change { + Upload.count + }.by(1) post.reload @@ -305,18 +328,19 @@ RSpec.describe Jobs::PullHotlinkedImages do MD end - it 'works when invalid url in post' do + it "works when invalid url in post" do post = Fabricate(:post, raw: <<~MD) ![some test](#{image_url}) ![some test 2]("#{image_url}) MD stub_image_size - expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } - .to change { Upload.count }.by(1) + expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }.to change { + Upload.count + }.by(1) end - it 'replaces bbcode images' do + it "replaces bbcode images" do post = Fabricate(:post, raw: <<~MD) [img] #{image_url} @@ -328,8 +352,9 @@ RSpec.describe Jobs::PullHotlinkedImages do MD stub_image_size - expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } - .to change { Upload.count }.by(1) + expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }.to change { + Upload.count + }.by(1) post.reload @@ -340,17 +365,21 @@ RSpec.describe Jobs::PullHotlinkedImages do MD end - describe 'onebox' do + describe "onebox" do let(:media) { "File:Brisbane_May_2013201.jpg" } let(:url) { "https://commons.wikimedia.org/wiki/#{media}" } - let(:api_url) { "https://en.wikipedia.org/w/api.php?action=query&titles=#{media}&prop=imageinfo&iilimit=50&iiprop=timestamp|user|url&iiurlwidth=500&format=json" } + let(:api_url) do + "https://en.wikipedia.org/w/api.php?action=query&titles=#{media}&prop=imageinfo&iilimit=50&iiprop=timestamp|user|url&iiurlwidth=500&format=json" + end before do stub_request(:head, url) - stub_request(:get, url).to_return(body: '') + stub_request(:get, url).to_return(body: "") stub_request(:head, image_url) - stub_request(:get, api_url).to_return(body: "{ + stub_request(:get, api_url).to_return( + body: + "{ \"query\": { \"pages\": { \"-1\": { @@ -363,21 +392,22 @@ RSpec.describe Jobs::PullHotlinkedImages do } } } - }") + }", + ) end - it 'replaces image src' do + it "replaces image src" do post = Fabricate(:post, raw: "#{url}") stub_image_size post.rebake! post.reload - expect(post.cooked).to match(/ #{url} @@ -402,9 +432,7 @@ RSpec.describe Jobs::PullHotlinkedImages do MD stub_image_size - 2.times do - post.rebake! - end + 2.times { post.rebake! } post.reload @@ -416,13 +444,13 @@ RSpec.describe Jobs::PullHotlinkedImages do ![](upload://z2QSs1KJWoj51uYhDjb6ifCzxH6.gif) MD - expect(post.cooked).to match(/

    /) end - it 'rewrites a lone onebox' do + it "rewrites a lone onebox" do post = Fabricate(:post, raw: <<~MD) Onebox here: #{image_url} @@ -438,35 +466,35 @@ RSpec.describe Jobs::PullHotlinkedImages do ![](upload://z2QSs1KJWoj51uYhDjb6ifCzxH6.gif) MD - expect(post.cooked).to match(/", - title: "Some title that is long enough" - ) + it "replaces missing local uploads in lightbox link" do + post = + PostCreator.create!( + user, + raw: "", + title: "Some title that is long enough", + ) expect(post.reload.cooked).to have_tag(:a, with: { class: "lightbox" }) - stub_request(:get, "#{Discourse.base_url}#{upload.url}") - .to_return(status: 200, body: file_from_fixtures("smallest.png")) + stub_request(:get, "#{Discourse.base_url}#{upload.url}").to_return( + status: 200, + body: file_from_fixtures("smallest.png"), + ) upload.delete - expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } - .to change { Upload.count }.by(1) + expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }.to change { + Upload.count + }.by(1) post.reload @@ -572,16 +600,18 @@ RSpec.describe Jobs::PullHotlinkedImages do end it "doesn't remove optimized images from lightboxes" do - post = PostCreator.create!( - user, - raw: "![alt](#{upload.short_url})", - title: "Some title that is long enough" - ) + post = + PostCreator.create!( + user, + raw: "![alt](#{upload.short_url})", + title: "Some title that is long enough", + ) expect(post.reload.cooked).to have_tag(:a, with: { class: "lightbox" }) - expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) } - .not_to change { Upload.count } + expect { Jobs::PullHotlinkedImages.new.execute(post_id: post.id) }.not_to change { + Upload.count + } post.reload @@ -605,12 +635,14 @@ RSpec.describe Jobs::PullHotlinkedImages do end context "when there's not enough disk space" do - before { SiteSetting.download_remote_images_threshold = 75 } it "disables download_remote_images_threshold and send a notification to the admin" do StaffActionLogger.any_instance.expects(:log_site_setting_change).once - SystemMessage.expects(:create_from_system_user).with(Discourse.site_contact_user, :download_remote_images_disabled).once + SystemMessage + .expects(:create_from_system_user) + .with(Discourse.site_contact_user, :download_remote_images_disabled) + .once job.execute({ post_id: post.id }) expect(SiteSetting.download_remote_images_to_local).to eq(false) @@ -622,12 +654,14 @@ RSpec.describe Jobs::PullHotlinkedImages do expect(SiteSetting.download_remote_images_to_local).to eq(true) end - end end def stub_s3(upload) stub_upload(upload) - stub_request(:get, "https:" + upload.url).to_return(status: 200, body: file_from_fixtures("smallest.png")) + stub_request(:get, "https:" + upload.url).to_return( + status: 200, + body: file_from_fixtures("smallest.png"), + ) end end diff --git a/spec/jobs/pull_user_profile_hotlinked_images_spec.rb b/spec/jobs/pull_user_profile_hotlinked_images_spec.rb index 2dbce8437f..de96bf09af 100644 --- a/spec/jobs/pull_user_profile_hotlinked_images_spec.rb +++ b/spec/jobs/pull_user_profile_hotlinked_images_spec.rb @@ -4,26 +4,32 @@ RSpec.describe Jobs::PullUserProfileHotlinkedImages do fab!(:user) { Fabricate(:user) } let(:image_url) { "http://wiki.mozilla.org/images/2/2e/Longcat1.png" } - let(:png) { Base64.decode64("R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==") } + let(:png) do + Base64.decode64( + "R0lGODlhAQABALMAAAAAAIAAAACAAICAAAAAgIAAgACAgMDAwICAgP8AAAD/AP//AAAA//8A/wD//wBiZCH5BAEAAA8ALAAAAAABAAEAAAQC8EUAOw==", + ) + end before do stub_request(:get, image_url).to_return(body: png, headers: { "Content-Type" => "image/png" }) SiteSetting.download_remote_images_to_local = true end - describe '#execute' do - before do - stub_image_size - end + describe "#execute" do + before { stub_image_size } - it 'replaces images' do + it "replaces images" do user.user_profile.update!(bio_raw: "![](#{image_url})") - expect { Jobs::PullUserProfileHotlinkedImages.new.execute(user_id: user.id) }.to change { Upload.count }.by(1) + expect { Jobs::PullUserProfileHotlinkedImages.new.execute(user_id: user.id) }.to change { + Upload.count + }.by(1) expect(user.user_profile.reload.bio_cooked).to include(Upload.last.url) end - it 'handles nil bio' do - expect { Jobs::PullUserProfileHotlinkedImages.new.execute(user_id: user.id) }.not_to change { Upload.count } + it "handles nil bio" do + expect { Jobs::PullUserProfileHotlinkedImages.new.execute(user_id: user.id) }.not_to change { + Upload.count + } expect(user.user_profile.reload.bio_cooked).to eq(nil) end end diff --git a/spec/jobs/rebake_custom_emoji_posts_spec.rb b/spec/jobs/rebake_custom_emoji_posts_spec.rb index 5f64ba824d..49951a4660 100644 --- a/spec/jobs/rebake_custom_emoji_posts_spec.rb +++ b/spec/jobs/rebake_custom_emoji_posts_spec.rb @@ -1,20 +1,20 @@ # frozen_string_literal: true RSpec.describe Jobs::RebakeCustomEmojiPosts do - it 'should rebake posts that are using a given custom emoji' do + it "should rebake posts that are using a given custom emoji" do upload = Fabricate(:upload) - custom_emoji = CustomEmoji.create!(name: 'test', upload: upload) + custom_emoji = CustomEmoji.create!(name: "test", upload: upload) Emoji.clear_cache - post = Fabricate(:post, raw: 'some post with :test: yay') + post = Fabricate(:post, raw: "some post with :test: yay") expect(post.reload.cooked).to eq( - "

    some post with \":test:\" yay

    " + "

    some post with \":test:\" yay

    ", ) custom_emoji.destroy! Emoji.clear_cache - described_class.new.execute(name: 'test') + described_class.new.execute(name: "test") - expect(post.reload.cooked).to eq('

    some post with :test: yay

    ') + expect(post.reload.cooked).to eq("

    some post with :test: yay

    ") end end diff --git a/spec/jobs/refresh_users_reviewable_counts_spec.rb b/spec/jobs/refresh_users_reviewable_counts_spec.rb index 7437996391..667ca6c038 100644 --- a/spec/jobs/refresh_users_reviewable_counts_spec.rb +++ b/spec/jobs/refresh_users_reviewable_counts_spec.rb @@ -7,7 +7,9 @@ RSpec.describe Jobs::RefreshUsersReviewableCounts do fab!(:group) { Fabricate(:group) } fab!(:topic) { Fabricate(:topic) } - fab!(:reviewable1) { Fabricate(:reviewable, reviewable_by_group: group, reviewable_by_moderator: true, topic: topic) } + fab!(:reviewable1) do + Fabricate(:reviewable, reviewable_by_group: group, reviewable_by_moderator: true, topic: topic) + end fab!(:reviewable2) { Fabricate(:reviewable, reviewable_by_moderator: false) } fab!(:reviewable3) { Fabricate(:reviewable, reviewable_by_moderator: true) } @@ -19,11 +21,12 @@ RSpec.describe Jobs::RefreshUsersReviewableCounts do Group.refresh_automatic_groups! end - describe '#execute' do + describe "#execute" do it "publishes reviewable counts for the members of the specified groups" do - messages = MessageBus.track_publish do - described_class.new.execute(group_ids: [Group::AUTO_GROUPS[:staff]]) - end + messages = + MessageBus.track_publish do + described_class.new.execute(group_ids: [Group::AUTO_GROUPS[:staff]]) + end expect(messages.size).to eq(2) moderator_message = messages.find { |m| m.user_ids == [moderator.id] } @@ -32,9 +35,7 @@ RSpec.describe Jobs::RefreshUsersReviewableCounts do admin_message = messages.find { |m| m.user_ids == [admin.id] } expect(moderator_message.channel).to eq("/reviewable_counts/#{moderator.id}") - messages = MessageBus.track_publish do - described_class.new.execute(group_ids: [group.id]) - end + messages = MessageBus.track_publish { described_class.new.execute(group_ids: [group.id]) } expect(messages.size).to eq(1) user_message = messages.find { |m| m.user_ids == [user.id] } @@ -42,9 +43,10 @@ RSpec.describe Jobs::RefreshUsersReviewableCounts do end it "published counts respect reviewables visibility" do - messages = MessageBus.track_publish do - described_class.new.execute(group_ids: [Group::AUTO_GROUPS[:staff], group.id]) - end + messages = + MessageBus.track_publish do + described_class.new.execute(group_ids: [Group::AUTO_GROUPS[:staff], group.id]) + end expect(messages.size).to eq(3) admin_message = messages.find { |m| m.user_ids == [admin.id] } @@ -52,22 +54,13 @@ RSpec.describe Jobs::RefreshUsersReviewableCounts do user_message = messages.find { |m| m.user_ids == [user.id] } expect(admin_message.channel).to eq("/reviewable_counts/#{admin.id}") - expect(admin_message.data).to eq( - reviewable_count: 3, - unseen_reviewable_count: 3 - ) + expect(admin_message.data).to eq(reviewable_count: 3, unseen_reviewable_count: 3) expect(moderator_message.channel).to eq("/reviewable_counts/#{moderator.id}") - expect(moderator_message.data).to eq( - reviewable_count: 2, - unseen_reviewable_count: 2 - ) + expect(moderator_message.data).to eq(reviewable_count: 2, unseen_reviewable_count: 2) expect(user_message.channel).to eq("/reviewable_counts/#{user.id}") - expect(user_message.data).to eq( - reviewable_count: 1, - unseen_reviewable_count: 1 - ) + expect(user_message.data).to eq(reviewable_count: 1, unseen_reviewable_count: 1) end end end diff --git a/spec/jobs/regular/bulk_user_title_update_spec.rb b/spec/jobs/regular/bulk_user_title_update_spec.rb index fa6c9b8da1..ebe66c0e4d 100644 --- a/spec/jobs/regular/bulk_user_title_update_spec.rb +++ b/spec/jobs/regular/bulk_user_title_update_spec.rb @@ -1,33 +1,37 @@ # frozen_string_literal: true RSpec.describe Jobs::BulkUserTitleUpdate do - fab!(:badge) { Fabricate(:badge, name: 'Protector of the Realm', allow_title: true) } + fab!(:badge) { Fabricate(:badge, name: "Protector of the Realm", allow_title: true) } fab!(:user) { Fabricate(:user) } fab!(:other_user) { Fabricate(:user) } - describe 'update action' do + describe "update action" do before do BadgeGranter.grant(badge, user) user.update(title: badge.name) end - it 'updates the title of all users with the attached granted title badge id on their profile' do + it "updates the title of all users with the attached granted title badge id on their profile" do execute_update - expect(user.reload.title).to eq('King of the Forum') + expect(user.reload.title).to eq("King of the Forum") end - it 'does not set the title for any other users' do + it "does not set the title for any other users" do execute_update - expect(other_user.reload.title).not_to eq('King of the Forum') + expect(other_user.reload.title).not_to eq("King of the Forum") end def execute_update - described_class.new.execute(new_title: 'King of the Forum', granted_badge_id: badge.id, action: described_class::UPDATE_ACTION) + described_class.new.execute( + new_title: "King of the Forum", + granted_badge_id: badge.id, + action: described_class::UPDATE_ACTION, + ) end end - describe 'reset action' do - let(:customized_badge_name) { 'Merit Badge' } + describe "reset action" do + let(:customized_badge_name) { "Merit Badge" } before do TranslationOverride.upsert!(I18n.locale, Badge.i18n_key(badge.name), customized_badge_name) @@ -35,14 +39,12 @@ RSpec.describe Jobs::BulkUserTitleUpdate do user.update(title: customized_badge_name) end - it 'updates the title of all users back to the original badge name' do + it "updates the title of all users back to the original badge name" do expect(user.reload.title).to eq(customized_badge_name) described_class.new.execute(granted_badge_id: badge.id, action: described_class::RESET_ACTION) - expect(user.reload.title).to eq('Protector of the Realm') + expect(user.reload.title).to eq("Protector of the Realm") end - after do - TranslationOverride.revert!(I18n.locale, Badge.i18n_key(badge.name)) - end + after { TranslationOverride.revert!(I18n.locale, Badge.i18n_key(badge.name)) } end end diff --git a/spec/jobs/regular/group_smtp_email_spec.rb b/spec/jobs/regular/group_smtp_email_spec.rb index d01ce2c02d..475e939d9b 100644 --- a/spec/jobs/regular/group_smtp_email_spec.rb +++ b/spec/jobs/regular/group_smtp_email_spec.rb @@ -15,7 +15,7 @@ RSpec.describe Jobs::GroupSmtpEmail do group_id: group.id, post_id: post_id, email: "test@test.com", - cc_emails: ["otherguy@test.com", "cormac@lit.com"] + cc_emails: %w[otherguy@test.com cormac@lit.com], } end let(:staged1) { Fabricate(:staged, email: "otherguy@test.com") } @@ -51,14 +51,20 @@ RSpec.describe Jobs::GroupSmtpEmail do it "includes a 'reply above this line' message" do subject.execute(args) - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) - expect(email_log.as_mail_message.html_part.to_s).to include(I18n.t("user_notifications.reply_above_line")) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + expect(email_log.as_mail_message.html_part.to_s).to include( + I18n.t("user_notifications.reply_above_line"), + ) end it "does not include context posts" do subject.execute(args) - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) - expect(email_log.as_mail_message.text_part.to_s).not_to include(I18n.t("user_notifications.previous_discussion")) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + expect(email_log.as_mail_message.text_part.to_s).not_to include( + I18n.t("user_notifications.previous_discussion"), + ) expect(email_log.as_mail_message.text_part.to_s).not_to include("some first post content") end @@ -67,14 +73,20 @@ RSpec.describe Jobs::GroupSmtpEmail do post.update!(reply_to_post_number: 1, reply_to_user: second_post.user) PostReply.create(post: second_post, reply: post) subject.execute(args) - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) - expect(email_log.raw_headers).to include("In-Reply-To: ") - expect(email_log.as_mail_message.html_part.to_s).not_to include(I18n.t("user_notifications.in_reply_to")) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + expect(email_log.raw_headers).to include( + "In-Reply-To: ", + ) + expect(email_log.as_mail_message.html_part.to_s).not_to include( + I18n.t("user_notifications.in_reply_to"), + ) end it "includes the participants in the correct format (but not the recipient user), and does not have links for the staged users" do subject.execute(args) - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) email_text = email_log.as_mail_message.text_part.to_s expect(email_text).to include("Support Group") expect(email_text).to include("otherguy@test.com") @@ -87,7 +99,8 @@ RSpec.describe Jobs::GroupSmtpEmail do it "creates an EmailLog record with the correct details" do subject.execute(args) - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) expect(email_log).not_to eq(nil) expect(email_log.message_id).to eq("discourse/post/#{post.id}@test.localhost") end @@ -96,7 +109,8 @@ RSpec.describe Jobs::GroupSmtpEmail do subject.execute(args) expect(ActionMailer::Base.deliveries.count).to eq(1) expect(ActionMailer::Base.deliveries.last.subject).to eq("Re: Help I need support") - incoming_email = IncomingEmail.find_by(post_id: post.id, topic_id: post.topic_id, user_id: post.user.id) + incoming_email = + IncomingEmail.find_by(post_id: post.id, topic_id: post.topic_id, user_id: post.user.id) expect(incoming_email).not_to eq(nil) expect(incoming_email.message_id).to eq("discourse/post/#{post.id}@test.localhost") expect(incoming_email.created_via).to eq(IncomingEmail.created_via_types[:group_smtp]) @@ -109,7 +123,8 @@ RSpec.describe Jobs::GroupSmtpEmail do subject.execute(args) expect(ActionMailer::Base.deliveries.count).to eq(1) expect(ActionMailer::Base.deliveries.last.subject).to eq("Re: Help I need support") - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) post_reply_key = PostReplyKey.where(user_id: recipient_user, post_id: post.id).first expect(post_reply_key).to eq(nil) expect(email_log.raw_headers).not_to include("Reply-To: Support Group <#{group.email_username}") @@ -120,7 +135,8 @@ RSpec.describe Jobs::GroupSmtpEmail do subject.execute(args) expect(ActionMailer::Base.deliveries.count).to eq(1) expect(ActionMailer::Base.deliveries.last.subject).to eq("Re: Help I need support") - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) expect(email_log).not_to eq(nil) expect(email_log.message_id).to eq("discourse/post/#{post.id}@test.localhost") end @@ -129,7 +145,8 @@ RSpec.describe Jobs::GroupSmtpEmail do subject.execute(args) expect(ActionMailer::Base.deliveries.count).to eq(1) expect(ActionMailer::Base.deliveries.last.subject).to eq("Re: Help I need support") - incoming_email = IncomingEmail.find_by(post_id: post.id, topic_id: post.topic_id, user_id: post.user.id) + incoming_email = + IncomingEmail.find_by(post_id: post.id, topic_id: post.topic_id, user_id: post.user.id) expect(incoming_email).not_to eq(nil) expect(incoming_email.message_id).to eq("discourse/post/#{post.id}@test.localhost") expect(incoming_email.created_via).to eq(IncomingEmail.created_via_types[:group_smtp]) @@ -142,7 +159,8 @@ RSpec.describe Jobs::GroupSmtpEmail do subject.execute(args) expect(ActionMailer::Base.deliveries.count).to eq(1) expect(ActionMailer::Base.deliveries.last.subject).to eq("Re: Help I need support") - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) post_reply_key = PostReplyKey.where(user_id: recipient_user, post_id: post.id).first expect(post_reply_key).to eq(nil) expect(email_log.raw).not_to include("Reply-To: Support Group <#{group.email_username}") @@ -154,7 +172,8 @@ RSpec.describe Jobs::GroupSmtpEmail do subject.execute(args) expect(ActionMailer::Base.deliveries.count).to eq(1) expect(ActionMailer::Base.deliveries.last.subject).to eq("Re: Help I need support") - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) expect(email_log.raw_headers).to include("From: support-group <#{group.email_username}") end @@ -162,7 +181,8 @@ RSpec.describe Jobs::GroupSmtpEmail do subject.execute(args) expect(ActionMailer::Base.deliveries.count).to eq(1) expect(ActionMailer::Base.deliveries.last.subject).to eq("Re: Help I need support") - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) expect(email_log.to_address).to eq("test@test.com") expect(email_log.smtp_group_id).to eq(group.id) end @@ -174,7 +194,7 @@ RSpec.describe Jobs::GroupSmtpEmail do expect(ActionMailer::Base.deliveries.count).to eq(1) last_email = ActionMailer::Base.deliveries.last expect(last_email.subject).to eq("Re: Help I need support") - expect(last_email.cc).to match_array(["otherguy@test.com", "cormac@lit.com"]) + expect(last_email.cc).to match_array(%w[otherguy@test.com cormac@lit.com]) end context "when there are cc_addresses" do @@ -183,8 +203,9 @@ RSpec.describe Jobs::GroupSmtpEmail do expect(ActionMailer::Base.deliveries.count).to eq(1) sent_mail = ActionMailer::Base.deliveries.last expect(sent_mail.subject).to eq("Re: Help I need support") - expect(sent_mail.cc).to eq(["otherguy@test.com", "cormac@lit.com"]) - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + expect(sent_mail.cc).to eq(%w[otherguy@test.com cormac@lit.com]) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) expect(email_log.cc_addresses).to eq("otherguy@test.com;cormac@lit.com") expect(email_log.cc_user_ids).to match_array([staged1.id, staged2.id]) end @@ -197,7 +218,8 @@ RSpec.describe Jobs::GroupSmtpEmail do expect(sent_mail.subject).to eq("Re: Help I need support") expect(sent_mail.cc).to eq(["otherguy@test.com"]) expect(sent_mail.bcc).to eq(["cormac@lit.com"]) - email_log = EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) + email_log = + EmailLog.find_by(post_id: post.id, topic_id: post.topic_id, user_id: recipient_user.id) expect(email_log.cc_addresses).to eq("otherguy@test.com") expect(email_log.bcc_addresses).to eq("cormac@lit.com") expect(email_log.cc_user_ids).to match_array([staged1.id]) @@ -208,9 +230,7 @@ RSpec.describe Jobs::GroupSmtpEmail do let(:post_id) { post.topic.posts.first.id } context "when the group has imap enabled" do - before do - group.update!(imap_enabled: true) - end + before { group.update!(imap_enabled: true) } it "aborts and does not send a group SMTP email; the OP is the one that sent the email in the first place" do expect { subject.execute(args) }.not_to(change { EmailLog.count }) @@ -219,9 +239,7 @@ RSpec.describe Jobs::GroupSmtpEmail do end context "when the group does not have imap enabled" do - before do - group.update!(imap_enabled: false) - end + before { group.update!(imap_enabled: false) } it "sends the email as expected" do subject.execute(args) @@ -235,13 +253,15 @@ RSpec.describe Jobs::GroupSmtpEmail do post.trash! subject.execute(args) expect(ActionMailer::Base.deliveries.count).to eq(0) - expect(SkippedEmailLog.exists?( - email_type: "group_smtp", - user: recipient_user, - post: nil, - to_address: recipient_user.email, - reason_type: SkippedEmailLog.reason_types[:group_smtp_post_deleted] - )).to eq(true) + expect( + SkippedEmailLog.exists?( + email_type: "group_smtp", + user: recipient_user, + post: nil, + to_address: recipient_user.email, + reason_type: SkippedEmailLog.reason_types[:group_smtp_post_deleted], + ), + ).to eq(true) end end @@ -250,13 +270,15 @@ RSpec.describe Jobs::GroupSmtpEmail do post.topic.trash! subject.execute(args) expect(ActionMailer::Base.deliveries.count).to eq(0) - expect(SkippedEmailLog.exists?( - email_type: "group_smtp", - user: recipient_user, - post: post, - to_address: recipient_user.email, - reason_type: SkippedEmailLog.reason_types[:group_smtp_topic_deleted] - )).to eq(true) + expect( + SkippedEmailLog.exists?( + email_type: "group_smtp", + user: recipient_user, + post: post, + to_address: recipient_user.email, + reason_type: SkippedEmailLog.reason_types[:group_smtp_topic_deleted], + ), + ).to eq(true) end end @@ -289,13 +311,15 @@ RSpec.describe Jobs::GroupSmtpEmail do group.update!(smtp_enabled: false) subject.execute(args) expect(ActionMailer::Base.deliveries.count).to eq(0) - expect(SkippedEmailLog.exists?( - email_type: "group_smtp", - user: recipient_user, - post: post, - to_address: recipient_user.email, - reason_type: SkippedEmailLog.reason_types[:group_smtp_disabled_for_group] - )).to eq(true) + expect( + SkippedEmailLog.exists?( + email_type: "group_smtp", + user: recipient_user, + post: post, + to_address: recipient_user.email, + reason_type: SkippedEmailLog.reason_types[:group_smtp_disabled_for_group], + ), + ).to eq(true) end end end diff --git a/spec/jobs/regular/publish_group_membership_updates_spec.rb b/spec/jobs/regular/publish_group_membership_updates_spec.rb index 822c39e711..6f5479b090 100644 --- a/spec/jobs/regular/publish_group_membership_updates_spec.rb +++ b/spec/jobs/regular/publish_group_membership_updates_spec.rb @@ -4,63 +4,54 @@ describe Jobs::PublishGroupMembershipUpdates do fab!(:user) { Fabricate(:user) } fab!(:group) { Fabricate(:group) } - it 'publishes events for added users' do - events = DiscourseEvent.track_events do - subject.execute( - user_ids: [user.id], group_id: group.id, type: 'add' - ) - end + it "publishes events for added users" do + events = + DiscourseEvent.track_events do + subject.execute(user_ids: [user.id], group_id: group.id, type: "add") + end expect(events).to include( event_name: :user_added_to_group, - params: [user, group, { automatic: group.automatic }] + params: [user, group, { automatic: group.automatic }], ) end - it 'publishes events for removed users' do - events = DiscourseEvent.track_events do - subject.execute( - user_ids: [user.id], group_id: group.id, type: 'remove' - ) - end + it "publishes events for removed users" do + events = + DiscourseEvent.track_events do + subject.execute(user_ids: [user.id], group_id: group.id, type: "remove") + end - expect(events).to include( - event_name: :user_removed_from_group, - params: [user, group] - ) + expect(events).to include(event_name: :user_removed_from_group, params: [user, group]) end it "does nothing if the group doesn't exist" do - events = DiscourseEvent.track_events do - subject.execute( - user_ids: [user.id], group_id: nil, type: 'add' - ) - end + events = + DiscourseEvent.track_events do + subject.execute(user_ids: [user.id], group_id: nil, type: "add") + end expect(events).not_to include( event_name: :user_added_to_group, - params: [user, group, { automatic: group.automatic }] + params: [user, group, { automatic: group.automatic }], ) end - it 'fails when the update type is invalid' do - expect { - subject.execute( - user_ids: [user.id], group_id: nil, type: nil - ) - }.to raise_error(Discourse::InvalidParameters) + it "fails when the update type is invalid" do + expect { subject.execute(user_ids: [user.id], group_id: nil, type: nil) }.to raise_error( + Discourse::InvalidParameters, + ) end - it 'does nothing when the user is not human' do - events = DiscourseEvent.track_events do - subject.execute( - user_ids: [Discourse.system_user.id], group_id: nil, type: 'add' - ) - end + it "does nothing when the user is not human" do + events = + DiscourseEvent.track_events do + subject.execute(user_ids: [Discourse.system_user.id], group_id: nil, type: "add") + end expect(events).not_to include( event_name: :user_added_to_group, - params: [user, group, { automatic: group.automatic }] + params: [user, group, { automatic: group.automatic }], ) end end diff --git a/spec/jobs/regular/update_post_uploads_secure_status_spec.rb b/spec/jobs/regular/update_post_uploads_secure_status_spec.rb index 8d728f770d..ea9fbd2c41 100644 --- a/spec/jobs/regular/update_post_uploads_secure_status_spec.rb +++ b/spec/jobs/regular/update_post_uploads_secure_status_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe Jobs::UpdatePostUploadsSecureStatus do fab!(:post) { Fabricate(:post) } diff --git a/spec/jobs/reindex_search_spec.rb b/spec/jobs/reindex_search_spec.rb index e61f9e71a8..ae63f64d05 100644 --- a/spec/jobs/reindex_search_spec.rb +++ b/spec/jobs/reindex_search_spec.rb @@ -6,13 +6,13 @@ RSpec.describe Jobs::ReindexSearch do Jobs.run_immediately! end - let(:locale) { 'fr' } + let(:locale) { "fr" } # This works since test db has a small record less than limit. # Didn't check `topic` because topic doesn't have posts in fabrication # thus no search data - %w(post category user).each do |m| + %w[post category user].each do |m| it "should rebuild `#{m}` when default_locale changed" do - SiteSetting.default_locale = 'en' + SiteSetting.default_locale = "en" model = Fabricate(m.to_sym) SiteSetting.default_locale = locale subject.execute({}) @@ -27,12 +27,13 @@ RSpec.describe Jobs::ReindexSearch do model.reload subject.execute({}) - expect(model.public_send("#{m}_search_data").version) - .to eq("SearchIndexer::#{m.upcase}_INDEX_VERSION".constantize) + expect(model.public_send("#{m}_search_data").version).to eq( + "SearchIndexer::#{m.upcase}_INDEX_VERSION".constantize, + ) end end - describe 'rebuild_posts' do + describe "rebuild_posts" do class FakeIndexer def self.index(post, force:) get_posts.push(post) @@ -53,9 +54,7 @@ RSpec.describe Jobs::ReindexSearch do end end - after do - FakeIndexer.reset - end + after { FakeIndexer.reset } it "should not reindex posts that belong to a deleted topic or have been trashed" do post = Fabricate(:post) @@ -70,7 +69,7 @@ RSpec.describe Jobs::ReindexSearch do expect(FakeIndexer.posts).to contain_exactly(post) end - it 'should not reindex posts with a developmental version' do + it "should not reindex posts with a developmental version" do post = Fabricate(:post, version: SearchIndexer::MIN_POST_REINDEX_VERSION + 1) subject.rebuild_posts(indexer: FakeIndexer) @@ -78,14 +77,11 @@ RSpec.describe Jobs::ReindexSearch do expect(FakeIndexer.posts).to eq([]) end - it 'should not reindex posts with empty raw' do + it "should not reindex posts with empty raw" do post = Fabricate(:post) post.post_search_data.destroy! - post2 = Fabricate.build(:post, - raw: "", - post_type: Post.types[:small_action] - ) + post2 = Fabricate.build(:post, raw: "", post_type: Post.types[:small_action]) post2.save!(validate: false) @@ -95,7 +91,7 @@ RSpec.describe Jobs::ReindexSearch do end end - describe '#execute' do + describe "#execute" do it "should clean up topic_search_data of trashed topics" do topic = Fabricate(:post).topic topic2 = Fabricate(:post).topic @@ -107,9 +103,7 @@ RSpec.describe Jobs::ReindexSearch do expect { subject.execute({}) }.to change { TopicSearchData.count }.by(-1) expect(Topic.pluck(:id)).to contain_exactly(topic2.id) - expect(TopicSearchData.pluck(:topic_id)).to contain_exactly( - topic2.topic_search_data.topic_id - ) + expect(TopicSearchData.pluck(:topic_id)).to contain_exactly(topic2.topic_search_data.topic_id) end it "should clean up post_search_data of posts with empty raw or posts from trashed topics" do @@ -132,13 +126,9 @@ RSpec.describe Jobs::ReindexSearch do expect { subject.execute({}) }.to change { PostSearchData.count }.by(-3) - expect(Post.pluck(:id)).to contain_exactly( - post.id, post2.id, post3.id, post4.id, post5.id - ) + expect(Post.pluck(:id)).to contain_exactly(post.id, post2.id, post3.id, post4.id, post5.id) - expect(PostSearchData.pluck(:post_id)).to contain_exactly( - post.id, post3.id, post5.id - ) + expect(PostSearchData.pluck(:post_id)).to contain_exactly(post.id, post3.id, post5.id) end end end diff --git a/spec/jobs/remove_banner_spec.rb b/spec/jobs/remove_banner_spec.rb index 6456568eb1..fecdcfbd09 100644 --- a/spec/jobs/remove_banner_spec.rb +++ b/spec/jobs/remove_banner_spec.rb @@ -4,44 +4,40 @@ RSpec.describe Jobs::RemoveBanner do fab!(:topic) { Fabricate(:topic) } fab!(:user) { topic.user } - context 'when topic is not bannered until' do - it 'doesn’t enqueue a future job to remove it' do - expect do - topic.make_banner!(user) - end.not_to change { Jobs::RemoveBanner.jobs.size } + context "when topic is not bannered until" do + it "doesn’t enqueue a future job to remove it" do + expect do topic.make_banner!(user) end.not_to change { Jobs::RemoveBanner.jobs.size } end end - context 'when topic is bannered until' do - context 'when bannered_until is a valid date' do - it 'enqueues a future job to remove it' do + context "when topic is bannered until" do + context "when bannered_until is a valid date" do + it "enqueues a future job to remove it" do bannered_until = 5.days.from_now expect(topic.archetype).to eq(Archetype.default) - expect do - topic.make_banner!(user, bannered_until.to_s) - end.to change { Jobs::RemoveBanner.jobs.size }.by(1) + expect do topic.make_banner!(user, bannered_until.to_s) end.to change { + Jobs::RemoveBanner.jobs.size + }.by(1) topic.reload expect(topic.archetype).to eq(Archetype.banner) job = Jobs::RemoveBanner.jobs[0] - expect(Time.at(job['at'])).to be_within_one_minute_of(bannered_until) - expect(job['args'][0]['topic_id']).to eq(topic.id) + expect(Time.at(job["at"])).to be_within_one_minute_of(bannered_until) + expect(job["args"][0]["topic_id"]).to eq(topic.id) - job['class'].constantize.new.perform(*job['args']) + job["class"].constantize.new.perform(*job["args"]) topic.reload expect(topic.archetype).to eq(Archetype.default) end end - context 'when bannered_until is an invalid date' do - it 'doesn’t enqueue a future job to remove it' do + context "when bannered_until is an invalid date" do + it "doesn’t enqueue a future job to remove it" do expect do - expect do - topic.make_banner!(user, 'xxx') - end.to raise_error(Discourse::InvalidParameters) + expect do topic.make_banner!(user, "xxx") end.to raise_error(Discourse::InvalidParameters) end.not_to change { Jobs::RemoveBanner.jobs.size } end end diff --git a/spec/jobs/reviewable_priorities_spec.rb b/spec/jobs/reviewable_priorities_spec.rb index 9b3ea9f030..adad762a6c 100644 --- a/spec/jobs/reviewable_priorities_spec.rb +++ b/spec/jobs/reviewable_priorities_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true RSpec.describe Jobs::ReviewablePriorities do - it "needs returns 0s with no existing reviewables" do Jobs::ReviewablePriorities.new.execute({}) @@ -38,7 +37,7 @@ RSpec.describe Jobs::ReviewablePriorities do expect(Reviewable.score_required_to_hide_post).to eq(8.33) end - context 'when there are enough reviewables' do + context "when there are enough reviewables" do let(:medium_threshold) { 8.0 } let(:high_threshold) { 13.0 } let(:score_to_hide_post) { 8.66 } @@ -54,7 +53,7 @@ RSpec.describe Jobs::ReviewablePriorities do expect(Reviewable.score_required_to_hide_post).to eq(score_to_hide_post) end - it 'ignore negative scores when calculating priorities' do + it "ignore negative scores when calculating priorities" do create_reviewables(Jobs::ReviewablePriorities.min_reviewables) negative_score = -9 10.times { create_with_score(negative_score) } @@ -67,7 +66,7 @@ RSpec.describe Jobs::ReviewablePriorities do expect(Reviewable.score_required_to_hide_post).to eq(score_to_hide_post) end - it 'ignores non-approved reviewables' do + it "ignores non-approved reviewables" do create_reviewables(Jobs::ReviewablePriorities.min_reviewables) low_score = 2 10.times { create_with_score(low_score, status: :pending) } diff --git a/spec/jobs/send_system_message_spec.rb b/spec/jobs/send_system_message_spec.rb index a21942d01f..7bacdd5940 100644 --- a/spec/jobs/send_system_message_spec.rb +++ b/spec/jobs/send_system_message_spec.rb @@ -2,25 +2,37 @@ RSpec.describe Jobs::SendSystemMessage do it "raises an error without a user_id" do - expect { Jobs::SendSystemMessage.new.execute(message_type: 'welcome_invite') }.to raise_error(Discourse::InvalidParameters) + expect { Jobs::SendSystemMessage.new.execute(message_type: "welcome_invite") }.to raise_error( + Discourse::InvalidParameters, + ) end it "raises an error without a message_type" do - expect { Jobs::SendSystemMessage.new.execute(user_id: 1234) }.to raise_error(Discourse::InvalidParameters) + expect { Jobs::SendSystemMessage.new.execute(user_id: 1234) }.to raise_error( + Discourse::InvalidParameters, + ) end - context 'with valid parameters' do + context "with valid parameters" do fab!(:user) { Fabricate(:user) } it "should call SystemMessage.create" do - SystemMessage.any_instance.expects(:create).with('welcome_invite', {}) - Jobs::SendSystemMessage.new.execute(user_id: user.id, message_type: 'welcome_invite') + SystemMessage.any_instance.expects(:create).with("welcome_invite", {}) + Jobs::SendSystemMessage.new.execute(user_id: user.id, message_type: "welcome_invite") end it "can send message parameters" do - options = { url: "/t/no-spammers-please/123", edit_delay: 5, flag_reason: "Flagged by community" } - SystemMessage.any_instance.expects(:create).with('post_hidden', options) - Jobs::SendSystemMessage.new.execute(user_id: user.id, message_type: 'post_hidden', message_options: options) + options = { + url: "/t/no-spammers-please/123", + edit_delay: 5, + flag_reason: "Flagged by community", + } + SystemMessage.any_instance.expects(:create).with("post_hidden", options) + Jobs::SendSystemMessage.new.execute( + user_id: user.id, + message_type: "post_hidden", + message_options: options, + ) end end end diff --git a/spec/jobs/suspicious_login_spec.rb b/spec/jobs/suspicious_login_spec.rb index 5d0ac1aa0a..973aeef20b 100644 --- a/spec/jobs/suspicious_login_spec.rb +++ b/spec/jobs/suspicious_login_spec.rb @@ -1,11 +1,10 @@ # frozen_string_literal: true RSpec.describe Jobs::SuspiciousLogin do - fab!(:user) { Fabricate(:moderator) } let(:zurich) { { latitude: 47.3686498, longitude: 8.5391825 } } # Zurich, Switzerland - let(:bern) { { latitude: 46.947922, longitude: 7.444608 } } # Bern, Switzerland + let(:bern) { { latitude: 46.947922, longitude: 7.444608 } } # Bern, Switzerland let(:london) { { latitude: 51.5073509, longitude: -0.1277583 } } # London, United Kingdom before do @@ -51,8 +50,6 @@ RSpec.describe Jobs::SuspiciousLogin do expect(UserAuthTokenLog.where(action: "suspicious").count).to eq(1) - expect(Jobs::CriticalUserEmail.jobs.first["args"].first["type"]) - .to eq('suspicious_login') + expect(Jobs::CriticalUserEmail.jobs.first["args"].first["type"]).to eq("suspicious_login") end - end diff --git a/spec/jobs/sync_acls_for_uploads_spec.rb b/spec/jobs/sync_acls_for_uploads_spec.rb index a7b43162ca..8f28e27b26 100644 --- a/spec/jobs/sync_acls_for_uploads_spec.rb +++ b/spec/jobs/sync_acls_for_uploads_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" RSpec.describe Jobs::SyncAclsForUploads do let(:upload1) { Fabricate(:upload) } @@ -29,7 +29,13 @@ RSpec.describe Jobs::SyncAclsForUploads do end it "handles updates throwing an exception" do - Discourse.store.expects(:update_upload_ACL).raises(StandardError).then.returns(true, true).times(3) + Discourse + .store + .expects(:update_upload_ACL) + .raises(StandardError) + .then + .returns(true, true) + .times(3) Discourse.expects(:warn_exception).once run_job end diff --git a/spec/jobs/tl3_promotions_spec.rb b/spec/jobs/tl3_promotions_spec.rb index 770bb39c16..d1f4455665 100644 --- a/spec/jobs/tl3_promotions_spec.rb +++ b/spec/jobs/tl3_promotions_spec.rb @@ -8,7 +8,7 @@ RSpec.describe Jobs::Tl3Promotions do topics_entered: 1000, posts_read_count: 1000, likes_given: 1000, - likes_received: 1000 + likes_received: 1000, ) end @@ -51,9 +51,7 @@ RSpec.describe Jobs::Tl3Promotions do user end - before do - SiteSetting.tl3_promotion_min_duration = 3 - end + before { SiteSetting.tl3_promotion_min_duration = 3 } it "demotes if was promoted more than X days ago" do user = nil @@ -82,9 +80,7 @@ RSpec.describe Jobs::Tl3Promotions do it "doesn't demote if user hasn't lost requirements (low water mark)" do user = nil - freeze_time(4.days.ago) do - user = create_leader_user - end + freeze_time(4.days.ago) { user = create_leader_user } TrustLevel3Requirements.any_instance.stubs(:requirements_met?).returns(false) TrustLevel3Requirements.any_instance.stubs(:requirements_lost?).returns(false) @@ -103,7 +99,6 @@ RSpec.describe Jobs::Tl3Promotions do TrustLevel3Requirements.any_instance.stubs(:requirements_lost?).returns(true) run_job expect(user.reload.trust_level).to eq(TrustLevel[2]) - end it "doesn't demote user if their group_granted_trust_level is 3" do @@ -120,11 +115,9 @@ RSpec.describe Jobs::Tl3Promotions do end it "doesn't demote with very high tl3_promotion_min_duration value" do - SiteSetting.stubs(:tl3_promotion_min_duration).returns(2000000000) + SiteSetting.stubs(:tl3_promotion_min_duration).returns(2_000_000_000) user = nil - freeze_time(500.days.ago) do - user = create_leader_user - end + freeze_time(500.days.ago) { user = create_leader_user } expect(user).to be_on_tl3_grace_period TrustLevel3Requirements.any_instance.stubs(:requirements_met?).returns(false) TrustLevel3Requirements.any_instance.stubs(:requirements_lost?).returns(true) diff --git a/spec/jobs/toggle_topic_closed_spec.rb b/spec/jobs/toggle_topic_closed_spec.rb index b7c8eacbc4..406749d897 100644 --- a/spec/jobs/toggle_topic_closed_spec.rb +++ b/spec/jobs/toggle_topic_closed_spec.rb @@ -3,67 +3,45 @@ RSpec.describe Jobs::ToggleTopicClosed do fab!(:admin) { Fabricate(:admin) } - fab!(:topic) do - Fabricate(:topic_timer, user: admin).topic - end + fab!(:topic) { Fabricate(:topic_timer, user: admin).topic } - it 'should be able to close a topic' do + it "should be able to close a topic" do topic freeze_time(61.minutes.from_now) do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: true - ) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: true) expect(topic.reload.closed).to eq(true) - expect(Post.last.raw).to eq(I18n.t( - 'topic_statuses.autoclosed_enabled_minutes', count: 61 - )) + expect(Post.last.raw).to eq(I18n.t("topic_statuses.autoclosed_enabled_minutes", count: 61)) end end - describe 'opening a topic' do - it 'should be work' do + describe "opening a topic" do + it "should be work" do topic.update!(closed: true) freeze_time(61.minutes.from_now) do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: false - ) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: false) expect(topic.reload.closed).to eq(false) - expect(Post.last.raw).to eq(I18n.t( - 'topic_statuses.autoclosed_disabled_minutes', count: 61 - )) + expect(Post.last.raw).to eq(I18n.t("topic_statuses.autoclosed_disabled_minutes", count: 61)) end end - describe 'when category has auto close configured' do + describe "when category has auto close configured" do fab!(:category) do - Fabricate(:category, - auto_close_based_on_last_post: true, - auto_close_hours: 5 - ) + Fabricate(:category, auto_close_based_on_last_post: true, auto_close_hours: 5) end fab!(:topic) { Fabricate(:topic, category: category, closed: true) } it "should restore the category's auto close timer" do - Fabricate(:topic_timer, - status_type: TopicTimer.types[:open], - topic: topic, - user: admin - ) + Fabricate(:topic_timer, status_type: TopicTimer.types[:open], topic: topic, user: admin) freeze_time(61.minutes.from_now) do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: false - ) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: false) expect(topic.reload.closed).to eq(false) @@ -76,61 +54,47 @@ RSpec.describe Jobs::ToggleTopicClosed do end end - describe 'when trying to close a topic that has already been closed' do - it 'should delete the topic timer' do + describe "when trying to close a topic that has already been closed" do + it "should delete the topic timer" do freeze_time(topic.public_topic_timer.execute_at + 1.minute) topic.update!(closed: true) expect do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: true - ) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: true) end.to change { TopicTimer.exists?(topic_id: topic.id) }.from(true).to(false) end end - describe 'when trying to close a topic that has been deleted' do - it 'should delete the topic timer' do + describe "when trying to close a topic that has been deleted" do + it "should delete the topic timer" do freeze_time(topic.public_topic_timer.execute_at + 1.minute) topic.trash! expect do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: true - ) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: true) end.to change { TopicTimer.exists?(topic_id: topic.id) }.from(true).to(false) end end - describe 'when user is no longer authorized to close topics' do + describe "when user is no longer authorized to close topics" do fab!(:user) { Fabricate(:user) } - fab!(:topic) do - Fabricate(:topic_timer, user: user).topic - end + fab!(:topic) { Fabricate(:topic_timer, user: user).topic } - it 'should destroy the topic timer' do + it "should destroy the topic timer" do freeze_time(topic.public_topic_timer.execute_at + 1.minute) expect do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: true - ) + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: true) end.to change { TopicTimer.exists?(topic_id: topic.id) }.from(true).to(false) expect(topic.reload.closed).to eq(false) end it "should reconfigure topic timer if category's topics are set to autoclose" do - category = Fabricate(:category, - auto_close_based_on_last_post: true, - auto_close_hours: 5 - ) + category = Fabricate(:category, auto_close_based_on_last_post: true, auto_close_hours: 5) topic = Fabricate(:topic, category: category) topic.public_topic_timer.update!(user: user) @@ -138,12 +102,10 @@ RSpec.describe Jobs::ToggleTopicClosed do freeze_time(topic.public_topic_timer.execute_at + 1.minute) expect do - described_class.new.execute( - topic_timer_id: topic.public_topic_timer.id, - state: true - ) - end.to change { topic.reload.public_topic_timer.user }.from(user).to(Discourse.system_user) - .and change { topic.public_topic_timer.id } + described_class.new.execute(topic_timer_id: topic.public_topic_timer.id, state: true) + end.to change { topic.reload.public_topic_timer.user }.from(user).to( + Discourse.system_user, + ).and change { topic.public_topic_timer.id } expect(topic.reload.closed).to eq(false) end diff --git a/spec/jobs/topic_timer_enqueuer_spec.rb b/spec/jobs/topic_timer_enqueuer_spec.rb index 1500cb4f4f..95f73fd93e 100644 --- a/spec/jobs/topic_timer_enqueuer_spec.rb +++ b/spec/jobs/topic_timer_enqueuer_spec.rb @@ -4,21 +4,39 @@ RSpec.describe Jobs::TopicTimerEnqueuer do subject { described_class.new } fab!(:timer1) do - Fabricate(:topic_timer, execute_at: 1.minute.ago, created_at: 1.hour.ago, status_type: TopicTimer.types[:close]) + Fabricate( + :topic_timer, + execute_at: 1.minute.ago, + created_at: 1.hour.ago, + status_type: TopicTimer.types[:close], + ) end fab!(:timer2) do - Fabricate(:topic_timer, execute_at: 1.minute.ago, created_at: 1.hour.ago, status_type: TopicTimer.types[:open]) + Fabricate( + :topic_timer, + execute_at: 1.minute.ago, + created_at: 1.hour.ago, + status_type: TopicTimer.types[:open], + ) end fab!(:future_timer) do - Fabricate(:topic_timer, execute_at: 1.hours.from_now, created_at: 1.hour.ago, status_type: TopicTimer.types[:close]) + Fabricate( + :topic_timer, + execute_at: 1.hours.from_now, + created_at: 1.hour.ago, + status_type: TopicTimer.types[:close], + ) end fab!(:deleted_timer) do - Fabricate(:topic_timer, execute_at: 1.minute.ago, created_at: 1.hour.ago, status_type: TopicTimer.types[:close]) + Fabricate( + :topic_timer, + execute_at: 1.minute.ago, + created_at: 1.hour.ago, + status_type: TopicTimer.types[:close], + ) end - before do - deleted_timer.trash! - end + before { deleted_timer.trash! } it "does not enqueue deleted timers" do expect_not_enqueued_with(job: :close_topic, args: { topic_timer_id: deleted_timer.id }) diff --git a/spec/jobs/truncate_user_flag_stats_spec.rb b/spec/jobs/truncate_user_flag_stats_spec.rb index 3c64369ff2..ffd1c0981e 100644 --- a/spec/jobs/truncate_user_flag_stats_spec.rb +++ b/spec/jobs/truncate_user_flag_stats_spec.rb @@ -15,9 +15,7 @@ RSpec.describe Jobs::TruncateUserFlagStats do end it "raises an error without user ids" do - expect { - described_class.new.execute({}) - }.to raise_error(Discourse::InvalidParameters) + expect { described_class.new.execute({}) }.to raise_error(Discourse::InvalidParameters) end it "does nothing if the user doesn't have enough flags" do @@ -79,5 +77,4 @@ RSpec.describe Jobs::TruncateUserFlagStats do expect(other_user.user_stat.flags_disagreed).to eq(1) expect(other_user.user_stat.flags_ignored).to eq(1) end - end diff --git a/spec/jobs/update_gravatar_spec.rb b/spec/jobs/update_gravatar_spec.rb index 4b72fe9527..24637edb01 100644 --- a/spec/jobs/update_gravatar_spec.rb +++ b/spec/jobs/update_gravatar_spec.rb @@ -2,14 +2,18 @@ RSpec.describe Jobs::UpdateGravatar do fab!(:user) { Fabricate(:user) } - let(:temp) { Tempfile.new('test') } + let(:temp) { Tempfile.new("test") } fab!(:upload) { Fabricate(:upload, user: user) } let(:avatar) { user.create_user_avatar! } it "picks gravatar if system avatar is picked and gravatar was just downloaded" do temp.binmode # tiny valid png - temp.write(Base64.decode64("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==")) + temp.write( + Base64.decode64( + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==", + ), + ) temp.rewind FileHelper.expects(:download).returns(temp) @@ -38,8 +42,7 @@ RSpec.describe Jobs::UpdateGravatar do SiteSetting.automatically_download_gravatars = true - expect { user.refresh_avatar } - .not_to change { Jobs::UpdateGravatar.jobs.count } + expect { user.refresh_avatar }.not_to change { Jobs::UpdateGravatar.jobs.count } user.reload expect(user.uploaded_avatar_id).to eq(nil) diff --git a/spec/jobs/update_s3_inventory_spec.rb b/spec/jobs/update_s3_inventory_spec.rb index ff8e032f04..ab43aa4060 100644 --- a/spec/jobs/update_s3_inventory_spec.rb +++ b/spec/jobs/update_s3_inventory_spec.rb @@ -20,7 +20,8 @@ RSpec.describe Jobs::UpdateS3Inventory do @client.expects(:put_bucket_policy).with( bucket: "special-bucket", - policy: %Q|{"Version":"2012-10-17","Statement":[{"Sid":"InventoryAndAnalyticsPolicy","Effect":"Allow","Principal":{"Service":"s3.amazonaws.com"},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::special-bucket/#{path}/*"],"Condition":{"ArnLike":{"aws:SourceArn":"arn:aws:s3:::special-bucket"},"StringEquals":{"s3:x-amz-acl":"bucket-owner-full-control"}}}]}| + policy: + %Q|{"Version":"2012-10-17","Statement":[{"Sid":"InventoryAndAnalyticsPolicy","Effect":"Allow","Principal":{"Service":"s3.amazonaws.com"},"Action":["s3:PutObject"],"Resource":["arn:aws:s3:::special-bucket/#{path}/*"],"Condition":{"ArnLike":{"aws:SourceArn":"arn:aws:s3:::special-bucket"},"StringEquals":{"s3:x-amz-acl":"bucket-owner-full-control"}}}]}|, ) @client.expects(:put_bucket_inventory_configuration) @client.expects(:put_bucket_inventory_configuration).with( @@ -31,19 +32,21 @@ RSpec.describe Jobs::UpdateS3Inventory do s3_bucket_destination: { bucket: "arn:aws:s3:::special-bucket", prefix: path, - format: "CSV" - } + format: "CSV", + }, }, filter: { - prefix: id + prefix: id, }, is_enabled: true, id: id, included_object_versions: "Current", optional_fields: ["ETag"], - schedule: { frequency: "Daily" } + schedule: { + frequency: "Daily", + }, }, - use_accelerate_endpoint: false + use_accelerate_endpoint: false, ) described_class.new.execute(nil) diff --git a/spec/jobs/update_username_spec.rb b/spec/jobs/update_username_spec.rb index fd4a57a92b..f95699a077 100644 --- a/spec/jobs/update_username_spec.rb +++ b/spec/jobs/update_username_spec.rb @@ -3,15 +3,16 @@ RSpec.describe Jobs::UpdateUsername do fab!(:user) { Fabricate(:user) } - it 'does not do anything if user_id is invalid' do - events = DiscourseEvent.track_events do - described_class.new.execute( - user_id: -999, - old_username: user.username, - new_username: 'somenewusername', - avatar_template: user.avatar_template - ) - end + it "does not do anything if user_id is invalid" do + events = + DiscourseEvent.track_events do + described_class.new.execute( + user_id: -999, + old_username: user.username, + new_username: "somenewusername", + avatar_template: user.avatar_template, + ) + end expect(events).to eq([]) end diff --git a/spec/jobs/user_email_spec.rb b/spec/jobs/user_email_spec.rb index efda1132b6..3a52f547be 100644 --- a/spec/jobs/user_email_spec.rb +++ b/spec/jobs/user_email_spec.rb @@ -1,33 +1,44 @@ # frozen_string_literal: true RSpec.describe Jobs::UserEmail do - before do - SiteSetting.email_time_window_mins = 10 - end + before { SiteSetting.email_time_window_mins = 10 } fab!(:user) { Fabricate(:user, last_seen_at: 11.minutes.ago) } fab!(:staged) { Fabricate(:user, staged: true, last_seen_at: 11.minutes.ago) } - fab!(:suspended) { Fabricate(:user, last_seen_at: 10.minutes.ago, suspended_at: 5.minutes.ago, suspended_till: 7.days.from_now) } + fab!(:suspended) do + Fabricate( + :user, + last_seen_at: 10.minutes.ago, + suspended_at: 5.minutes.ago, + suspended_till: 7.days.from_now, + ) + end fab!(:anonymous) { Fabricate(:anonymous, last_seen_at: 11.minutes.ago) } it "raises an error when there is no user" do - expect { Jobs::UserEmail.new.execute(type: :digest) }.to raise_error(Discourse::InvalidParameters) + expect { Jobs::UserEmail.new.execute(type: :digest) }.to raise_error( + Discourse::InvalidParameters, + ) end it "raises an error when there is no type" do - expect { Jobs::UserEmail.new.execute(user_id: user.id) }.to raise_error(Discourse::InvalidParameters) + expect { Jobs::UserEmail.new.execute(user_id: user.id) }.to raise_error( + Discourse::InvalidParameters, + ) end it "raises an error when the type doesn't exist" do - expect { Jobs::UserEmail.new.execute(type: :no_method, user_id: user.id) }.to raise_error(Discourse::InvalidParameters) + expect { Jobs::UserEmail.new.execute(type: :no_method, user_id: user.id) }.to raise_error( + Discourse::InvalidParameters, + ) end - context 'when digest can be generated' do + context "when digest can be generated" do fab!(:user) { Fabricate(:user, last_seen_at: 8.days.ago, last_emailed_at: 8.days.ago) } fab!(:popular_topic) { Fabricate(:topic, user: Fabricate(:admin), created_at: 1.hour.ago) } it "doesn't call the mailer when the user is missing" do - Jobs::UserEmail.new.execute(type: :digest, user_id: User.last.id + 10000) + Jobs::UserEmail.new.execute(type: :digest, user_id: User.last.id + 10_000) expect(ActionMailer::Base.deliveries).to eq([]) end @@ -37,7 +48,7 @@ RSpec.describe Jobs::UserEmail do expect(ActionMailer::Base.deliveries).to eq([]) end - context 'when not emailed recently' do + context "when not emailed recently" do before do freeze_time user.update!(last_emailed_at: 8.days.ago) @@ -50,14 +61,14 @@ RSpec.describe Jobs::UserEmail do end end - context 'when recently emailed' do + context "when recently emailed" do before do freeze_time user.update!(last_emailed_at: 2.hours.ago) user.user_option.update!(digest_after_minutes: 1.day.to_i / 60) end - it 'skips sending digest email' do + it "skips sending digest email" do Jobs::UserEmail.new.execute(type: :digest, user_id: user.id) expect(ActionMailer::Base.deliveries).to eq([]) expect(user.user_stat.reload.digest_attempted_at).to eq_time(Time.zone.now) @@ -70,39 +81,41 @@ RSpec.describe Jobs::UserEmail do email_token = Fabricate(:email_token) user.user_stat.update(bounce_score: SiteSetting.bounce_score_threshold + 1) - Jobs::CriticalUserEmail.new.execute(type: "signup", user_id: user.id, email_token: email_token.token) + Jobs::CriticalUserEmail.new.execute( + type: "signup", + user_id: user.id, + email_token: email_token.token, + ) email_log = EmailLog.where(user_id: user.id).last expect(email_log.email_type).to eq("signup") - expect(ActionMailer::Base.deliveries.first.to).to contain_exactly( - user.email - ) + expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(user.email) end end - context 'with to_address' do - it 'overwrites a to_address when present' do - Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id, to_address: 'jake@adventuretime.ooo') - - expect(ActionMailer::Base.deliveries.first.to).to contain_exactly( - 'jake@adventuretime.ooo' + context "with to_address" do + it "overwrites a to_address when present" do + Jobs::UserEmail.new.execute( + type: :confirm_new_email, + user_id: user.id, + to_address: "jake@adventuretime.ooo", ) + + expect(ActionMailer::Base.deliveries.first.to).to contain_exactly("jake@adventuretime.ooo") end end context "with disable_emails setting" do it "sends when no" do - SiteSetting.disable_emails = 'no' + SiteSetting.disable_emails = "no" Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id) - expect(ActionMailer::Base.deliveries.first.to).to contain_exactly( - user.email - ) + expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(user.email) end it "does not send an email when yes" do - SiteSetting.disable_emails = 'yes' + SiteSetting.disable_emails = "yes" Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id) expect(ActionMailer::Base.deliveries).to eq([]) @@ -111,17 +124,16 @@ RSpec.describe Jobs::UserEmail do context "when recently seen" do fab!(:post) { Fabricate(:post, user: user) } - fab!(:notification) { Fabricate( + fab!(:notification) do + Fabricate( :notification, user: user, topic: post.topic, post_number: post.post_number, - data: { original_post_id: post.id }.to_json + data: { original_post_id: post.id }.to_json, ) - } - before do - user.update_column(:last_seen_at, 9.minutes.ago) end + before { user.update_column(:last_seen_at, 9.minutes.ago) } it "doesn't send an email to a user that's been recently seen" do Jobs::UserEmail.new.execute(type: :user_replied, user_id: user.id, post_id: post.id) @@ -130,30 +142,38 @@ RSpec.describe Jobs::UserEmail do it "does send an email to a user that's been recently seen but has email_level set to always" do user.user_option.update(email_level: UserOption.email_level_types[:always]) - PostTiming.create!(topic_id: post.topic_id, post_number: post.post_number, user_id: user.id, msecs: 100) - - Jobs::UserEmail.new.execute( - type: :user_replied, + PostTiming.create!( + topic_id: post.topic_id, + post_number: post.post_number, user_id: user.id, - post_id: post.id, - notification_id: notification.id + msecs: 100, ) - expect(ActionMailer::Base.deliveries.first.to).to contain_exactly( - user.email - ) - end - - it "doesn't send an email even if email_level is set to always if `force_respect_seen_recently` arg is true" do - user.user_option.update(email_level: UserOption.email_level_types[:always]) - PostTiming.create!(topic_id: post.topic_id, post_number: post.post_number, user_id: user.id, msecs: 100) - Jobs::UserEmail.new.execute( type: :user_replied, user_id: user.id, post_id: post.id, notification_id: notification.id, - force_respect_seen_recently: true + ) + + expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(user.email) + end + + it "doesn't send an email even if email_level is set to always if `force_respect_seen_recently` arg is true" do + user.user_option.update(email_level: UserOption.email_level_types[:always]) + PostTiming.create!( + topic_id: post.topic_id, + post_number: post.post_number, + user_id: user.id, + msecs: 100, + ) + + Jobs::UserEmail.new.execute( + type: :user_replied, + user_id: user.id, + post_id: post.id, + notification_id: notification.id, + force_respect_seen_recently: true, ) expect(ActionMailer::Base.deliveries).to eq([]) end @@ -170,7 +190,7 @@ RSpec.describe Jobs::UserEmail do type: :user_private_message, user_id: user.id, post_id: post.id, - notification_id: notification.id + notification_id: notification.id, ) email = ActionMailer::Base.deliveries.first @@ -178,7 +198,7 @@ RSpec.describe Jobs::UserEmail do expect(email.to).to contain_exactly(user.email) html_part = email.parts.find { |x| x.content_type.include? "html" } - expect(html_part.body.to_s).to_not include('%{email_content}') + expect(html_part.body.to_s).to_not include("%{email_content}") expect(html_part.body.to_s).to include('\0') end @@ -196,7 +216,7 @@ RSpec.describe Jobs::UserEmail do type: :user_private_message, user_id: user.id, post_id: post.id, - notification_id: notification.id + notification_id: notification.id, ) email = ActionMailer::Base.deliveries.first @@ -218,12 +238,10 @@ RSpec.describe Jobs::UserEmail do type: :user_private_message, user_id: user.id, post_id: post.id, - notification_id: notification.id + notification_id: notification.id, ) - expect(ActionMailer::Base.deliveries.first.to).to contain_exactly( - user.email - ) + expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(user.email) end it "doesn't send a PM email to a user that's been recently seen and has email_messages_level set to never" do @@ -246,9 +264,7 @@ RSpec.describe Jobs::UserEmail do context "with email_log" do fab!(:post) { Fabricate(:post, created_at: 30.seconds.ago) } - before do - SiteSetting.editing_grace_period = 0 - end + before { SiteSetting.editing_grace_period = 0 } it "creates an email log when the mail is sent (via Email::Sender)" do freeze_time @@ -257,9 +273,9 @@ RSpec.describe Jobs::UserEmail do user.update!(last_emailed_at: last_emailed_at) Topic.last.update(created_at: 1.minute.ago) - expect do - Jobs::UserEmail.new.execute(type: :digest, user_id: user.id) - end.to change { EmailLog.count }.by(1) + expect do Jobs::UserEmail.new.execute(type: :digest, user_id: user.id) end.to change { + EmailLog.count + }.by(1) email_log = EmailLog.last @@ -273,22 +289,21 @@ RSpec.describe Jobs::UserEmail do freeze_time last_emailed_at = 7.days.ago - user.update!( - last_emailed_at: last_emailed_at, - suspended_till: 1.year.from_now - ) + user.update!(last_emailed_at: last_emailed_at, suspended_till: 1.year.from_now) - expect do - Jobs::UserEmail.new.execute(type: :digest, user_id: user.id) - end.to change { SkippedEmailLog.count }.by(1) + expect do Jobs::UserEmail.new.execute(type: :digest, user_id: user.id) end.to change { + SkippedEmailLog.count + }.by(1) - expect(SkippedEmailLog.exists?( - email_type: "digest", - user: user, - post: nil, - to_address: user.email, - reason_type: SkippedEmailLog.reason_types[:user_email_user_suspended_not_pm] - )).to eq(true) + expect( + SkippedEmailLog.exists?( + email_type: "digest", + user: user, + post: nil, + to_address: user.email, + reason_type: SkippedEmailLog.reason_types[:user_email_user_suspended_not_pm], + ), + ).to eq(true) # last_emailed_at doesn't change expect(user.last_emailed_at).to eq_time(last_emailed_at) @@ -302,21 +317,23 @@ RSpec.describe Jobs::UserEmail do Jobs::UserEmail.new.execute(type: :user_posted, user_id: user.id, post_id: post.id) end.to change { SkippedEmailLog.count }.by(1) - expect(SkippedEmailLog.exists?( - email_type: "user_posted", - user: user, - post: post, - to_address: user.email, - reason_type: SkippedEmailLog.reason_types[:user_email_access_denied] - )).to eq(true) + expect( + SkippedEmailLog.exists?( + email_type: "user_posted", + user: user, + post: post, + to_address: user.email, + reason_type: SkippedEmailLog.reason_types[:user_email_access_denied], + ), + ).to eq(true) expect(ActionMailer::Base.deliveries).to eq([]) end end - context 'with args' do - it 'passes a token as an argument when a token is present' do - Jobs::UserEmail.new.execute(type: :forgot_password, user_id: user.id, email_token: 'asdfasdf') + context "with args" do + it "passes a token as an argument when a token is present" do + Jobs::UserEmail.new.execute(type: :forgot_password, user_id: user.id, email_token: "asdfasdf") mail = ActionMailer::Base.deliveries.first @@ -332,14 +349,18 @@ RSpec.describe Jobs::UserEmail do requested_by: requested_by, new_email_token: email_token, new_email: "testnew@test.com", - change_state: EmailChangeRequest.states[:authorizing_new] + change_state: EmailChangeRequest.states[:authorizing_new], ) end context "when the change was requested by admin" do let(:requested_by) { Fabricate(:admin) } it "passes along true for the requested_by_admin param which changes the wording in the email" do - Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id, email_token: email_token.token) + Jobs::UserEmail.new.execute( + type: :confirm_new_email, + user_id: user.id, + email_token: email_token.token, + ) mail = ActionMailer::Base.deliveries.first expect(mail.body).to include("This email change was requested by a site admin.") end @@ -348,7 +369,11 @@ RSpec.describe Jobs::UserEmail do context "when the change was requested by the user" do let(:requested_by) { user } it "passes along false for the requested_by_admin param which changes the wording in the email" do - Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id, email_token: email_token.token) + Jobs::UserEmail.new.execute( + type: :confirm_new_email, + user_id: user.id, + email_token: email_token.token, + ) mail = ActionMailer::Base.deliveries.first expect(mail.body).not_to include("This email change was requested by a site admin.") end @@ -357,7 +382,11 @@ RSpec.describe Jobs::UserEmail do context "when requested_by record is not present" do let(:requested_by) { nil } it "passes along false for the requested_by_admin param which changes the wording in the email" do - Jobs::UserEmail.new.execute(type: :confirm_new_email, user_id: user.id, email_token: email_token.token) + Jobs::UserEmail.new.execute( + type: :confirm_new_email, + user_id: user.id, + email_token: email_token.token, + ) mail = ActionMailer::Base.deliveries.first expect(mail.body).not_to include("This email change was requested by a site admin.") end @@ -368,7 +397,12 @@ RSpec.describe Jobs::UserEmail do fab!(:post) { Fabricate(:post, user: user) } it "doesn't send the email if you've seen the post" do - PostTiming.record_timing(topic_id: post.topic_id, user_id: user.id, post_number: post.post_number, msecs: 6666) + PostTiming.record_timing( + topic_id: post.topic_id, + user_id: user.id, + post_number: post.post_number, + msecs: 6666, + ) Jobs::UserEmail.new.execute(type: :user_private_message, user_id: user.id, post_id: post.id) expect(ActionMailer::Base.deliveries).to eq([]) @@ -388,9 +422,13 @@ RSpec.describe Jobs::UserEmail do expect(ActionMailer::Base.deliveries).to eq([]) end - context 'when user is suspended' do + context "when user is suspended" do it "doesn't send email for a pm from a regular user" do - Jobs::UserEmail.new.execute(type: :user_private_message, user_id: suspended.id, post_id: post.id) + Jobs::UserEmail.new.execute( + type: :user_private_message, + user_id: suspended.id, + post_id: post.id, + ) expect(ActionMailer::Base.deliveries).to eq([]) end @@ -399,51 +437,57 @@ RSpec.describe Jobs::UserEmail do pm_from_staff = Fabricate(:post, user: Fabricate(:moderator)) pm_from_staff.topic.topic_allowed_users.create!(user_id: suspended.id) - pm_notification = Fabricate(:notification, - user: suspended, - topic: pm_from_staff.topic, - post_number: pm_from_staff.post_number, - data: { original_post_id: pm_from_staff.id }.to_json - ) + pm_notification = + Fabricate( + :notification, + user: suspended, + topic: pm_from_staff.topic, + post_number: pm_from_staff.post_number, + data: { original_post_id: pm_from_staff.id }.to_json, + ) Jobs::UserEmail.new.execute( type: :user_private_message, user_id: suspended.id, post_id: pm_from_staff.id, - notification_id: pm_notification.id + notification_id: pm_notification.id, ) - expect(ActionMailer::Base.deliveries.first.to).to contain_exactly( - suspended.email - ) + expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(suspended.email) end it "doesn't send PM from system user" do pm_from_system = SystemMessage.create(suspended, :unsilenced) - system_pm_notification = Fabricate(:notification, - user: suspended, - topic: pm_from_system.topic, - post_number: pm_from_system.post_number, - data: { original_post_id: pm_from_system.id }.to_json - ) + system_pm_notification = + Fabricate( + :notification, + user: suspended, + topic: pm_from_system.topic, + post_number: pm_from_system.post_number, + data: { original_post_id: pm_from_system.id }.to_json, + ) Jobs::UserEmail.new.execute( type: :user_private_message, user_id: suspended.id, post_id: pm_from_system.id, - notification_id: system_pm_notification.id + notification_id: system_pm_notification.id, ) expect(ActionMailer::Base.deliveries).to eq([]) end end - context 'when user is anonymous' do + context "when user is anonymous" do before { SiteSetting.allow_anonymous_posting = true } it "doesn't send email for a pm from a regular user" do - Jobs::UserEmail.new.execute(type: :user_private_message, user_id: anonymous.id, post_id: post.id) + Jobs::UserEmail.new.execute( + type: :user_private_message, + user_id: anonymous.id, + post_id: post.id, + ) expect(ActionMailer::Base.deliveries).to eq([]) end @@ -451,46 +495,52 @@ RSpec.describe Jobs::UserEmail do it "doesn't send email for a pm from a staff user" do pm_from_staff = Fabricate(:post, user: Fabricate(:moderator)) pm_from_staff.topic.topic_allowed_users.create!(user_id: anonymous.id) - Jobs::UserEmail.new.execute(type: :user_private_message, user_id: anonymous.id, post_id: pm_from_staff.id) + Jobs::UserEmail.new.execute( + type: :user_private_message, + user_id: anonymous.id, + post_id: pm_from_staff.id, + ) expect(ActionMailer::Base.deliveries).to eq([]) end end end - context 'with notification' do + context "with notification" do fab!(:post) { Fabricate(:post, user: user) } - fab!(:notification) { - Fabricate(:notification, - user: user, - topic: post.topic, - post_number: post.post_number, - data: { - original_post_id: post.id - }.to_json - ) - } + fab!(:notification) do + Fabricate( + :notification, + user: user, + topic: post.topic, + post_number: post.post_number, + data: { original_post_id: post.id }.to_json, + ) + end it "doesn't send the email if the notification has been seen" do notification.update_column(:read, true) - message, err = Jobs::UserEmail.new.message_for_email( - user, - post, - :user_mentioned, - notification, - notification_type: notification.notification_type, - notification_data_hash: notification.data_hash - ) + message, err = + Jobs::UserEmail.new.message_for_email( + user, + post, + :user_mentioned, + notification, + notification_type: notification.notification_type, + notification_data_hash: notification.data_hash, + ) expect(message).to eq(nil) - expect(SkippedEmailLog.exists?( - email_type: "user_mentioned", - user: user, - post: post, - to_address: user.email, - reason_type: SkippedEmailLog.reason_types[:user_email_notification_already_read] - )).to eq(true) + expect( + SkippedEmailLog.exists?( + email_type: "user_mentioned", + user: user, + post: post, + to_address: user.email, + reason_type: SkippedEmailLog.reason_types[:user_email_notification_already_read], + ), + ).to eq(true) end it "does send the email if the notification has been seen but user has email_level set to always" do @@ -501,12 +551,10 @@ RSpec.describe Jobs::UserEmail do type: :user_mentioned, user_id: user.id, post_id: post.id, - notification_id: notification.id + notification_id: notification.id, ) - expect(ActionMailer::Base.deliveries.first.to).to contain_exactly( - user.email - ) + expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(user.email) end it "does send the email if the user is using daily mailing list mode" do @@ -516,12 +564,10 @@ RSpec.describe Jobs::UserEmail do type: :user_mentioned, user_id: user.id, post_id: post.id, - notification_id: notification.id + notification_id: notification.id, ) - expect(ActionMailer::Base.deliveries.first.to).to contain_exactly( - user.email - ) + expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(user.email) end it "sends the mail if the user enabled mailing list mode, but mailing list mode is disabled globally" do @@ -531,7 +577,7 @@ RSpec.describe Jobs::UserEmail do type: :user_mentioned, user_id: user.id, post_id: post.id, - notification_id: notification.id + notification_id: notification.id, ) expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(user.email) @@ -545,7 +591,7 @@ RSpec.describe Jobs::UserEmail do type: :user_replied, user_id: user.id, post_id: post.id, - notification_id: notification.id + notification_id: notification.id, ) expect(ActionMailer::Base.deliveries).to eq([]) @@ -559,19 +605,17 @@ RSpec.describe Jobs::UserEmail do type: :user_replied, user_id: user.id, post_id: post.id, - notification_id: notification.id + notification_id: notification.id, ) - expect(ActionMailer::Base.deliveries.first.to).to contain_exactly( - user.email - ) + expect(ActionMailer::Base.deliveries.first.to).to contain_exactly(user.email) end end - context 'when max_emails_per_day_per_user limit is reached' do + context "when max_emails_per_day_per_user limit is reached" do before do SiteSetting.max_emails_per_day_per_user = 2 - 2.times { Fabricate(:email_log, user: user, email_type: 'blah', to_address: user.email) } + 2.times { Fabricate(:email_log, user: user, email_type: "blah", to_address: user.email) } end it "does not send notification if limit is reached" do @@ -581,18 +625,20 @@ RSpec.describe Jobs::UserEmail do type: :user_mentioned, user_id: user.id, notification_id: notification.id, - post_id: post.id + post_id: post.id, ) end end.to change { SkippedEmailLog.count }.by(1) - expect(SkippedEmailLog.exists?( - email_type: "user_mentioned", - user: user, - post: post, - to_address: user.email, - reason_type: SkippedEmailLog.reason_types[:exceeded_emails_limit] - )).to eq(true) + expect( + SkippedEmailLog.exists?( + email_type: "user_mentioned", + user: user, + post: post, + to_address: user.email, + reason_type: SkippedEmailLog.reason_types[:exceeded_emails_limit], + ), + ).to eq(true) freeze_time(Time.zone.now.tomorrow + 1.second) @@ -601,7 +647,7 @@ RSpec.describe Jobs::UserEmail do type: :user_mentioned, user_id: user.id, notification_id: notification.id, - post_id: post.id + post_id: post.id, ) end.not_to change { SkippedEmailLog.count } end @@ -615,10 +661,7 @@ RSpec.describe Jobs::UserEmail do ) end.to change { EmailLog.count }.by(1) - expect(EmailLog.exists?( - email_type: "forgot_password", - user: user, - )).to eq(true) + expect(EmailLog.exists?(email_type: "forgot_password", user: user)).to eq(true) end end @@ -631,7 +674,7 @@ RSpec.describe Jobs::UserEmail do type: :user_mentioned, user_id: user.id, notification_id: notification.id, - post_id: post.id + post_id: post.id, ) user.user_stat.reload @@ -643,7 +686,7 @@ RSpec.describe Jobs::UserEmail do type: :user_mentioned, user_id: user.id, notification_id: notification.id, - post_id: post.id + post_id: post.id, ) user.user_stat.reload @@ -658,17 +701,19 @@ RSpec.describe Jobs::UserEmail do type: :user_mentioned, user_id: user.id, notification_id: notification.id, - post_id: post.id + post_id: post.id, ) end.to change { SkippedEmailLog.count }.by(1) - expect(SkippedEmailLog.exists?( - email_type: "user_mentioned", - user: user, - post: post, - to_address: user.email, - reason_type: SkippedEmailLog.reason_types[:exceeded_bounces_limit] - )).to eq(true) + expect( + SkippedEmailLog.exists?( + email_type: "user_mentioned", + user: user, + post: post, + to_address: user.email, + reason_type: SkippedEmailLog.reason_types[:exceeded_bounces_limit], + ), + ).to eq(true) end it "doesn't send the mail if the user is using individual mailing list mode" do @@ -676,15 +721,34 @@ RSpec.describe Jobs::UserEmail do user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 1) # sometimes, we pass the notification_id - Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id) + Jobs::UserEmail.new.execute( + type: :user_mentioned, + user_id: user.id, + notification_id: notification.id, + post_id: post.id, + ) # other times, we only pass the type of notification - Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id) + Jobs::UserEmail.new.execute( + type: :user_mentioned, + user_id: user.id, + notification_type: "posted", + post_id: post.id, + ) # When post is nil - Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted") + Jobs::UserEmail.new.execute( + type: :user_mentioned, + user_id: user.id, + notification_type: "posted", + ) # When post does not have a topic post = Fabricate(:post) post.topic.destroy - Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id) + Jobs::UserEmail.new.execute( + type: :user_mentioned, + user_id: user.id, + notification_type: "posted", + post_id: post.id, + ) expect(ActionMailer::Base.deliveries).to eq([]) end @@ -694,57 +758,84 @@ RSpec.describe Jobs::UserEmail do user.user_option.update(mailing_list_mode: true, mailing_list_mode_frequency: 2) # sometimes, we pass the notification_id - Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id) + Jobs::UserEmail.new.execute( + type: :user_mentioned, + user_id: user.id, + notification_id: notification.id, + post_id: post.id, + ) # other times, we only pass the type of notification - Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id) + Jobs::UserEmail.new.execute( + type: :user_mentioned, + user_id: user.id, + notification_type: "posted", + post_id: post.id, + ) # When post is nil - Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted") + Jobs::UserEmail.new.execute( + type: :user_mentioned, + user_id: user.id, + notification_type: "posted", + ) # When post does not have a topic post = Fabricate(:post) post.topic.destroy - Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_type: "posted", post_id: post.id) + Jobs::UserEmail.new.execute( + type: :user_mentioned, + user_id: user.id, + notification_type: "posted", + post_id: post.id, + ) expect(ActionMailer::Base.deliveries).to eq([]) end it "doesn't send the email if the post has been user deleted" do post.update_column(:user_deleted, true) - Jobs::UserEmail.new.execute(type: :user_mentioned, user_id: user.id, notification_id: notification.id, post_id: post.id) + Jobs::UserEmail.new.execute( + type: :user_mentioned, + user_id: user.id, + notification_id: notification.id, + post_id: post.id, + ) expect(ActionMailer::Base.deliveries).to eq([]) end - context 'when user is suspended' do + context "when user is suspended" do it "doesn't send email for a pm from a regular user" do - msg, err = Jobs::UserEmail.new.message_for_email( + msg, err = + Jobs::UserEmail.new.message_for_email( suspended, Fabricate.build(:post), :user_private_message, - notification - ) + notification, + ) expect(msg).to eq(nil) expect(err).not_to eq(nil) end - context 'with pm from staff' do + context "with pm from staff" do before do @pm_from_staff = Fabricate(:post, user: Fabricate(:moderator)) @pm_from_staff.topic.topic_allowed_users.create!(user_id: suspended.id) - @pm_notification = Fabricate(:notification, - user: suspended, - topic: @pm_from_staff.topic, - post_number: @pm_from_staff.post_number, - data: { original_post_id: @pm_from_staff.id }.to_json - ) + @pm_notification = + Fabricate( + :notification, + user: suspended, + topic: @pm_from_staff.topic, + post_number: @pm_from_staff.post_number, + data: { original_post_id: @pm_from_staff.id }.to_json, + ) end let :sent_message do Jobs::UserEmail.new.message_for_email( - suspended, - @pm_from_staff, - :user_private_message, - @pm_notification + suspended, + @pm_from_staff, + :user_private_message, + @pm_notification, ) end @@ -764,7 +855,7 @@ RSpec.describe Jobs::UserEmail do end end - context 'when user is anonymous' do + context "when user is anonymous" do before { SiteSetting.allow_anonymous_posting = true } it "doesn't send email for a pm from a regular user" do @@ -772,7 +863,7 @@ RSpec.describe Jobs::UserEmail do type: :user_private_message, user_id: anonymous.id, post_id: post.id, - notification_id: notification.id + notification_id: notification.id, ) expect(ActionMailer::Base.deliveries).to eq([]) @@ -781,17 +872,19 @@ RSpec.describe Jobs::UserEmail do it "doesn't send email for a pm from staff" do pm_from_staff = Fabricate(:post, user: Fabricate(:moderator)) pm_from_staff.topic.topic_allowed_users.create!(user_id: anonymous.id) - pm_notification = Fabricate(:notification, - user: anonymous, - topic: pm_from_staff.topic, - post_number: pm_from_staff.post_number, - data: { original_post_id: pm_from_staff.id }.to_json - ) + pm_notification = + Fabricate( + :notification, + user: anonymous, + topic: pm_from_staff.topic, + post_number: pm_from_staff.post_number, + data: { original_post_id: pm_from_staff.id }.to_json, + ) Jobs::UserEmail.new.execute( type: :user_private_message, user_id: anonymous.id, post_id: pm_from_staff.id, - notification_id: pm_notification.id + notification_id: pm_notification.id, ) expect(ActionMailer::Base.deliveries).to eq([]) diff --git a/spec/lib/admin_confirmation_spec.rb b/spec/lib/admin_confirmation_spec.rb index 10e065c603..e9d345939c 100644 --- a/spec/lib/admin_confirmation_spec.rb +++ b/spec/lib/admin_confirmation_spec.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true -require 'admin_confirmation' +require "admin_confirmation" RSpec.describe AdminConfirmation do - fab!(:admin) { Fabricate(:admin) } fab!(:user) { Fabricate(:user) } @@ -32,25 +31,27 @@ RSpec.describe AdminConfirmation do expect(ac.target_user).to eq(user) expect(ac.token).to eq(@token) - expect_enqueued_with(job: :send_system_message, args: { user_id: user.id, message_type: 'welcome_staff', message_options: { role: :admin } }) do - ac.email_confirmed! - end + expect_enqueued_with( + job: :send_system_message, + args: { + user_id: user.id, + message_type: "welcome_staff", + message_options: { + role: :admin, + }, + }, + ) { ac.email_confirmed! } user.reload expect(user.admin?).to eq(true) # It creates a staff log - logs = UserHistory.where( - action: UserHistory.actions[:grant_admin], - target_user_id: user.id - ) + logs = UserHistory.where(action: UserHistory.actions[:grant_admin], target_user_id: user.id) expect(logs).to be_present # It removes the redis keys for another user expect(AdminConfirmation.find_by_code(ac.token)).to eq(nil) expect(AdminConfirmation.exists_for?(user.id)).to eq(false) end - end - end diff --git a/spec/lib/admin_user_index_query_spec.rb b/spec/lib/admin_user_index_query_spec.rb index 71fe1856d8..d603d3eaeb 100644 --- a/spec/lib/admin_user_index_query_spec.rb +++ b/spec/lib/admin_user_index_query_spec.rb @@ -2,7 +2,7 @@ RSpec.describe AdminUserIndexQuery do def real_users(query) - query.find_users_query.where('users.id > 0') + query.find_users_query.where("users.id > 0") end describe "sql order" do @@ -65,18 +65,15 @@ RSpec.describe AdminUserIndexQuery do end describe "no users with trust level" do - TrustLevel.levels.each do |key, value| it "#{key} returns no records" do query = ::AdminUserIndexQuery.new(query: key.to_s) expect(real_users(query)).to eq([]) end end - end describe "users with trust level" do - TrustLevel.levels.each do |key, value| it "finds user with trust #{key}" do user = Fabricate(:user, trust_level: value) @@ -88,107 +85,99 @@ RSpec.describe AdminUserIndexQuery do expect(real_users(query).to_a).to eq([user]) end end - end describe "with a pending user" do - fab!(:user) { Fabricate(:user, active: true, approved: false) } fab!(:inactive_user) { Fabricate(:user, approved: false, active: false) } it "finds the unapproved user" do - query = ::AdminUserIndexQuery.new(query: 'pending') + query = ::AdminUserIndexQuery.new(query: "pending") expect(query.find_users).to include(user) expect(query.find_users).not_to include(inactive_user) end - context 'with a suspended pending user' do - fab!(:suspended_user) { Fabricate(:user, approved: false, suspended_at: 1.hour.ago, suspended_till: 20.years.from_now) } + context "with a suspended pending user" do + fab!(:suspended_user) do + Fabricate( + :user, + approved: false, + suspended_at: 1.hour.ago, + suspended_till: 20.years.from_now, + ) + end it "doesn't return the suspended user" do - query = ::AdminUserIndexQuery.new(query: 'pending') + query = ::AdminUserIndexQuery.new(query: "pending") expect(query.find_users).not_to include(suspended_user) end end - end describe "correct order with nil values" do - before(:each) do - Fabricate(:user, email: "test2@example.com", last_emailed_at: 1.hour.ago) - end + before(:each) { Fabricate(:user, email: "test2@example.com", last_emailed_at: 1.hour.ago) } it "shows nil values first with asc" do users = ::AdminUserIndexQuery.new(order: "last_emailed", asc: true).find_users - expect(users.where('users.id > -2').count).to eq(2) - expect(users.where('users.id > -2').order('users.id asc').first.username).to eq("system") + expect(users.where("users.id > -2").count).to eq(2) + expect(users.where("users.id > -2").order("users.id asc").first.username).to eq("system") expect(users.first.last_emailed_at).to eq(nil) end it "shows nil values last with desc" do users = ::AdminUserIndexQuery.new(order: "last_emailed").find_users - expect(users.where('users.id > -2').count).to eq(2) + expect(users.where("users.id > -2").count).to eq(2) expect(users.first.last_emailed_at).to_not eq(nil) end - end describe "with an admin user" do - fab!(:user) { Fabricate(:user, admin: true) } fab!(:user2) { Fabricate(:user, admin: false) } it "finds the admin" do - query = ::AdminUserIndexQuery.new(query: 'admins') + query = ::AdminUserIndexQuery.new(query: "admins") expect(real_users(query)).to eq([user]) end - end describe "with a moderator" do - fab!(:user) { Fabricate(:user, moderator: true) } fab!(:user2) { Fabricate(:user, moderator: false) } it "finds the moderator" do - query = ::AdminUserIndexQuery.new(query: 'moderators') + query = ::AdminUserIndexQuery.new(query: "moderators") expect(real_users(query)).to eq([user]) end - end describe "with a silenced user" do - fab!(:user) { Fabricate(:user, silenced_till: 1.year.from_now) } fab!(:user2) { Fabricate(:user) } it "finds the silenced user" do - query = ::AdminUserIndexQuery.new(query: 'silenced') + query = ::AdminUserIndexQuery.new(query: "silenced") expect(real_users(query)).to eq([user]) end - end describe "with a staged user" do - fab!(:user) { Fabricate(:user, staged: true) } fab!(:user2) { Fabricate(:user, staged: false) } it "finds the staged user" do - query = ::AdminUserIndexQuery.new(query: 'staged') + query = ::AdminUserIndexQuery.new(query: "staged") expect(real_users(query)).to eq([user]) end - end describe "filtering" do - context "with exact email bypass" do it "can correctly bypass expensive ilike query" do - user = Fabricate(:user, email: 'sam@Sam.com') + user = Fabricate(:user, email: "sam@Sam.com") - query = AdminUserIndexQuery.new(filter: 'Sam@sam.com').find_users_query + query = AdminUserIndexQuery.new(filter: "Sam@sam.com").find_users_query expect(query.count).to eq(1) expect(query.first.id).to eq(user.id) @@ -196,22 +185,20 @@ RSpec.describe AdminUserIndexQuery do end it "can correctly bypass expensive ilike query" do - user = Fabricate(:user, email: 'sam2@Sam.com') + user = Fabricate(:user, email: "sam2@Sam.com") - query = AdminUserIndexQuery.new(email: 'Sam@sam.com').find_users_query + query = AdminUserIndexQuery.new(email: "Sam@sam.com").find_users_query expect(query.count).to eq(0) expect(query.to_sql.downcase).not_to include("ilike") - query = AdminUserIndexQuery.new(email: 'Sam2@sam.com').find_users_query + query = AdminUserIndexQuery.new(email: "Sam2@sam.com").find_users_query expect(query.first.id).to eq(user.id) expect(query.count).to eq(1) expect(query.to_sql.downcase).not_to include("ilike") - end end context "with email fragment" do - before(:each) { Fabricate(:user, email: "test1@example.com") } it "matches the email" do @@ -223,11 +210,9 @@ RSpec.describe AdminUserIndexQuery do query = ::AdminUserIndexQuery.new(filter: "Test1\t") expect(query.find_users.count()).to eq(1) end - end context "with username fragment" do - before(:each) { Fabricate(:user, username: "test_user_1") } it "matches the username" do @@ -242,15 +227,12 @@ RSpec.describe AdminUserIndexQuery do end context "with ip address fragment" do - fab!(:user) { Fabricate(:user, ip_address: "117.207.94.9") } it "matches the ip address" do query = ::AdminUserIndexQuery.new(filter: " 117.207.94.9 ") expect(query.find_users.count()).to eq(1) end - end - end end diff --git a/spec/lib/archetype_spec.rb b/spec/lib/archetype_spec.rb index 9b5a6b6945..9843012072 100644 --- a/spec/lib/archetype_spec.rb +++ b/spec/lib/archetype_spec.rb @@ -1,36 +1,36 @@ # encoding: utf-8 # frozen_string_literal: true -require 'archetype' +require "archetype" RSpec.describe Archetype do - describe 'default archetype' do - it 'has an Archetype by default' do + describe "default archetype" do + it "has an Archetype by default" do expect(Archetype.list).to be_present end - it 'has an id of default' do + it "has an id of default" do expect(Archetype.list.first.id).to eq(Archetype.default) end - context 'with duplicate' do + context "with duplicate" do before do @old_size = Archetype.list.size Archetype.register(Archetype.default) end - it 'does not add the same archetype twice' do + it "does not add the same archetype twice" do expect(Archetype.list.size).to eq(@old_size) end end end - describe 'register an archetype' do - it 'has one more element' do + describe "register an archetype" do + it "has one more element" do @list = Archetype.list.dup - Archetype.register('glados') + Archetype.register("glados") expect(Archetype.list.size).to eq(@list.size + 1) - expect(Archetype.list.find { |a| a.id == 'glados' }).to be_present + expect(Archetype.list.find { |a| a.id == "glados" }).to be_present end end end diff --git a/spec/lib/auth/default_current_user_provider_spec.rb b/spec/lib/auth/default_current_user_provider_spec.rb index 9c542e7965..39b84153f3 100644 --- a/spec/lib/auth/default_current_user_provider_spec.rb +++ b/spec/lib/auth/default_current_user_provider_spec.rb @@ -54,17 +54,13 @@ RSpec.describe Auth::DefaultCurrentUserProvider do it "raises for a revoked key" do api_key = ApiKey.create! params = { "HTTP_API_USERNAME" => user.username.downcase, "HTTP_API_KEY" => api_key.key } - expect( - provider("/", params).current_user.id - ).to eq(user.id) + expect(provider("/", params).current_user.id).to eq(user.id) api_key.reload.update(revoked_at: Time.zone.now, last_used_at: nil) expect(api_key.reload.last_used_at).to eq(nil) params = { "HTTP_API_USERNAME" => user.username.downcase, "HTTP_API_KEY" => api_key.key } - expect { - provider("/", params).current_user - }.to raise_error(Discourse::InvalidAccess) + expect { provider("/", params).current_user }.to raise_error(Discourse::InvalidAccess) api_key.reload expect(api_key.last_used_at).to eq(nil) @@ -72,9 +68,10 @@ RSpec.describe Auth::DefaultCurrentUserProvider do it "raises errors for incorrect api_key" do params = { "HTTP_API_KEY" => "INCORRECT" } - expect { - provider("/", params).current_user - }.to raise_error(Discourse::InvalidAccess, /API username or key is invalid/) + expect { provider("/", params).current_user }.to raise_error( + Discourse::InvalidAccess, + /API username or key is invalid/, + ) end it "finds a user for a correct per-user api key" do @@ -83,9 +80,9 @@ RSpec.describe Auth::DefaultCurrentUserProvider do good_provider = provider("/", params) - expect do - expect(good_provider.current_user.id).to eq(user.id) - end.to change { api_key.reload.last_used_at } + expect do expect(good_provider.current_user.id).to eq(user.id) end.to change { + api_key.reload.last_used_at + } expect(good_provider.is_api?).to eq(true) expect(good_provider.is_user_api?).to eq(false) @@ -93,15 +90,11 @@ RSpec.describe Auth::DefaultCurrentUserProvider do user.update_columns(active: false) - expect { - provider("/", params).current_user - }.to raise_error(Discourse::InvalidAccess) + expect { provider("/", params).current_user }.to raise_error(Discourse::InvalidAccess) user.update_columns(active: true, suspended_till: 1.day.from_now) - expect { - provider("/", params).current_user - }.to raise_error(Discourse::InvalidAccess) + expect { provider("/", params).current_user }.to raise_error(Discourse::InvalidAccess) end it "raises for a user pretending" do @@ -109,26 +102,22 @@ RSpec.describe Auth::DefaultCurrentUserProvider do api_key = ApiKey.create!(user_id: user.id, created_by_id: -1) params = { "HTTP_API_KEY" => api_key.key, "HTTP_API_USERNAME" => user2.username.downcase } - expect { - provider("/", params).current_user - }.to raise_error(Discourse::InvalidAccess) + expect { provider("/", params).current_user }.to raise_error(Discourse::InvalidAccess) end it "raises for a user with a mismatching ip" do - api_key = ApiKey.create!(user_id: user.id, created_by_id: -1, allowed_ips: ['10.0.0.0/24']) + api_key = ApiKey.create!(user_id: user.id, created_by_id: -1, allowed_ips: ["10.0.0.0/24"]) params = { "HTTP_API_KEY" => api_key.key, "HTTP_API_USERNAME" => user.username.downcase, - "REMOTE_ADDR" => "10.1.0.1" + "REMOTE_ADDR" => "10.1.0.1", } - expect { - provider("/", params).current_user - }.to raise_error(Discourse::InvalidAccess) + expect { provider("/", params).current_user }.to raise_error(Discourse::InvalidAccess) end it "allows a user with a matching ip" do - api_key = ApiKey.create!(user_id: user.id, created_by_id: -1, allowed_ips: ['100.0.0.0/24']) + api_key = ApiKey.create!(user_id: user.id, created_by_id: -1, allowed_ips: ["100.0.0.0/24"]) params = { "HTTP_API_KEY" => api_key.key, "HTTP_API_USERNAME" => user.username.downcase, @@ -142,7 +131,7 @@ RSpec.describe Auth::DefaultCurrentUserProvider do params = { "HTTP_API_KEY" => api_key.key, "HTTP_API_USERNAME" => user.username.downcase, - "HTTP_X_FORWARDED_FOR" => "10.1.1.1, 100.0.0.22" + "HTTP_X_FORWARDED_FOR" => "10.1.1.1, 100.0.0.22", } found_user = provider("/", params).current_user @@ -165,18 +154,18 @@ RSpec.describe Auth::DefaultCurrentUserProvider do it "finds a user for a correct system api key with external id" do api_key = ApiKey.create!(created_by_id: -1) - SingleSignOnRecord.create(user_id: user.id, external_id: "abc", last_payload: '') + SingleSignOnRecord.create(user_id: user.id, external_id: "abc", last_payload: "") params = { "HTTP_API_KEY" => api_key.key, "HTTP_API_USER_EXTERNAL_ID" => "abc" } expect(provider("/", params).current_user.id).to eq(user.id) end it "raises for a mismatched api_key header and param external id" do api_key = ApiKey.create!(created_by_id: -1) - SingleSignOnRecord.create(user_id: user.id, external_id: "abc", last_payload: '') + SingleSignOnRecord.create(user_id: user.id, external_id: "abc", last_payload: "") params = { "HTTP_API_KEY" => api_key.key } - expect { - provider("/?api_user_external_id=abc", params).current_user - }.to raise_error(Discourse::InvalidAccess) + expect { provider("/?api_user_external_id=abc", params).current_user }.to raise_error( + Discourse::InvalidAccess, + ) end it "finds a user for a correct system api key with id" do @@ -188,19 +177,15 @@ RSpec.describe Auth::DefaultCurrentUserProvider do it "raises for a mismatched api_key header and param user id" do api_key = ApiKey.create!(created_by_id: -1) params = { "HTTP_API_KEY" => api_key.key } - expect { - provider("/?api_user_id=#{user.id}", params).current_user - }.to raise_error(Discourse::InvalidAccess) + expect { provider("/?api_user_id=#{user.id}", params).current_user }.to raise_error( + Discourse::InvalidAccess, + ) end describe "when readonly mode is enabled due to postgres" do - before do - Discourse.enable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) - end + before { Discourse.enable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) } - after do - Discourse.disable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) - end + after { Discourse.disable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) } it "should not update ApiKey#last_used_at" do api_key = ApiKey.create!(user_id: user.id, created_by_id: -1) @@ -208,16 +193,14 @@ RSpec.describe Auth::DefaultCurrentUserProvider do good_provider = provider("/", params) - expect do - expect(good_provider.current_user.id).to eq(user.id) - end.to_not change { api_key.reload.last_used_at } + expect do expect(good_provider.current_user.id).to eq(user.id) end.to_not change { + api_key.reload.last_used_at + } end end context "with rate limiting" do - before do - RateLimiter.enable - end + before { RateLimiter.enable } it "rate limits admin api requests" do global_setting :max_admin_api_reqs_per_minute, 3 @@ -233,15 +216,15 @@ RSpec.describe Auth::DefaultCurrentUserProvider do provider("/", system_params).current_user provider("/", params).current_user - expect do - provider("/", system_params).current_user - end.to raise_error(RateLimiter::LimitExceeded) + expect do provider("/", system_params).current_user end.to raise_error( + RateLimiter::LimitExceeded, + ) freeze_time 59.seconds.from_now - expect do - provider("/", system_params).current_user - end.to raise_error(RateLimiter::LimitExceeded) + expect do provider("/", system_params).current_user end.to raise_error( + RateLimiter::LimitExceeded, + ) freeze_time 2.seconds.from_now @@ -259,7 +242,7 @@ RSpec.describe Auth::DefaultCurrentUserProvider do describe "#current_user" do let(:cookie) do - new_provider = provider('/') + new_provider = provider("/") new_provider.log_on_user(user, {}, new_provider.cookie_jar) CGI.escape(new_provider.cookie_jar["_t"]) end @@ -269,9 +252,7 @@ RSpec.describe Auth::DefaultCurrentUserProvider do user.clear_last_seen_cache!(@orig) end - after do - user.clear_last_seen_cache!(@orig) - end + after { user.clear_last_seen_cache!(@orig) } it "should not update last seen for suspended users" do provider2 = provider("/", "HTTP_COOKIE" => "_t=#{cookie}") @@ -295,13 +276,9 @@ RSpec.describe Auth::DefaultCurrentUserProvider do end describe "when readonly mode is enabled due to postgres" do - before do - Discourse.enable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) - end + before { Discourse.enable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) } - after do - Discourse.disable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) - end + after { Discourse.disable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) } it "should not update User#last_seen_at" do provider2 = provider("/", "HTTP_COOKIE" => "_t=#{cookie}") @@ -338,35 +315,47 @@ RSpec.describe Auth::DefaultCurrentUserProvider do end it "should update ajax reqs with discourse visible" do - expect(provider("/topic/anything/goes", - :method => "POST", - "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", - "HTTP_DISCOURSE_PRESENT" => "true" - ).should_update_last_seen?).to eq(true) + expect( + provider( + "/topic/anything/goes", + :method => "POST", + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + "HTTP_DISCOURSE_PRESENT" => "true", + ).should_update_last_seen?, + ).to eq(true) end it "should not update last seen for ajax calls without Discourse-Present header" do - expect(provider("/topic/anything/goes", - :method => "POST", - "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" - ).should_update_last_seen?).to eq(false) + expect( + provider( + "/topic/anything/goes", + :method => "POST", + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + ).should_update_last_seen?, + ).to eq(false) end it "should update last seen for API calls with Discourse-Present header" do api_key = ApiKey.create!(user_id: user.id, created_by_id: -1) - params = { :method => "POST", - "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", - "HTTP_API_KEY" => api_key.key - } + params = { + :method => "POST", + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + "HTTP_API_KEY" => api_key.key, + } expect(provider("/topic/anything/goes", params).should_update_last_seen?).to eq(false) - expect(provider("/topic/anything/goes", params.merge("HTTP_DISCOURSE_PRESENT" => "true")).should_update_last_seen?).to eq(true) + expect( + provider( + "/topic/anything/goes", + params.merge("HTTP_DISCOURSE_PRESENT" => "true"), + ).should_update_last_seen?, + ).to eq(true) end it "supports non persistent sessions" do SiteSetting.persistent_sessions = false - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) cookie_info = get_cookie_info(@provider.cookie_jar, "_t") @@ -377,12 +366,12 @@ RSpec.describe Auth::DefaultCurrentUserProvider do token = UserAuthToken.generate!(user_id: user.id).unhashed_auth_token ip = "10.0.0.1" env = { "HTTP_COOKIE" => "_t=#{token}", "REMOTE_ADDR" => ip } - expect(provider('/', env).current_user.id).to eq(user.id) + expect(provider("/", env).current_user.id).to eq(user.id) end it "correctly rotates tokens" do SiteSetting.maximum_session_age = 3 - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) cookie = @provider.cookie_jar["_t"] @@ -405,12 +394,8 @@ RSpec.describe Auth::DefaultCurrentUserProvider do expect(token.auth_token_seen).to eq(true) provider2.refresh_session(user, {}, provider2.cookie_jar) - expect( - decrypt_auth_cookie(provider2.cookie_jar["_t"])[:token] - ).not_to eq(unhashed_token) - expect( - decrypt_auth_cookie(provider2.cookie_jar["_t"])[:token].size - ).to eq(32) + expect(decrypt_auth_cookie(provider2.cookie_jar["_t"])[:token]).not_to eq(unhashed_token) + expect(decrypt_auth_cookie(provider2.cookie_jar["_t"])[:token].size).to eq(32) token.reload expect(token.auth_token_seen).to eq(false) @@ -432,23 +417,20 @@ RSpec.describe Auth::DefaultCurrentUserProvider do # assume it never reached the client expect(token.prev_auth_token).to eq(old_token) expect(token.auth_token).not_to eq(unverified_token) - end describe "events" do before do @refreshes = 0 - @increase_refreshes = -> (user) { @refreshes += 1 } + @increase_refreshes = ->(user) { @refreshes += 1 } DiscourseEvent.on(:user_session_refreshed, &@increase_refreshes) end - after do - DiscourseEvent.off(:user_session_refreshed, &@increase_refreshes) - end + after { DiscourseEvent.off(:user_session_refreshed, &@increase_refreshes) } it "fires event when updating last seen" do - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) cookie = @provider.cookie_jar["_t"] unhashed_token = decrypt_auth_cookie(cookie)[:token] @@ -460,7 +442,7 @@ RSpec.describe Auth::DefaultCurrentUserProvider do end it "does not fire an event when last seen does not update" do - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) cookie = @provider.cookie_jar["_t"] unhashed_token = decrypt_auth_cookie(cookie)[:token] @@ -473,42 +455,38 @@ RSpec.describe Auth::DefaultCurrentUserProvider do end describe "rate limiting" do - before do - RateLimiter.enable - end + before { RateLimiter.enable } it "can only try 10 bad cookies a minute" do token = UserAuthToken.generate!(user_id: user.id) - cookie = create_auth_cookie( - token: token.unhashed_auth_token, - user_id: user.id, - trust_level: user.trust_level, - issued_at: 5.minutes.ago - ) + cookie = + create_auth_cookie( + token: token.unhashed_auth_token, + user_id: user.id, + trust_level: user.trust_level, + issued_at: 5.minutes.ago, + ) - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) RateLimiter.new(nil, "cookie_auth_10.0.0.1", 10, 60).clear! RateLimiter.new(nil, "cookie_auth_10.0.0.2", 10, 60).clear! ip = "10.0.0.1" - bad_cookie = create_auth_cookie( - token: SecureRandom.hex, - user_id: user.id, - trust_level: user.trust_level, - issued_at: 5.minutes.ago, - ) + bad_cookie = + create_auth_cookie( + token: SecureRandom.hex, + user_id: user.id, + trust_level: user.trust_level, + issued_at: 5.minutes.ago, + ) env = { "HTTP_COOKIE" => "_t=#{bad_cookie}", "REMOTE_ADDR" => ip } - 10.times do - provider('/', env).current_user - end + 10.times { provider("/", env).current_user } - expect { - provider('/', env).current_user - }.to raise_error(Discourse::InvalidAccess) + expect { provider("/", env).current_user }.to raise_error(Discourse::InvalidAccess) expect { env["HTTP_COOKIE"] = "_t=#{cookie}" @@ -517,29 +495,28 @@ RSpec.describe Auth::DefaultCurrentUserProvider do env["REMOTE_ADDR"] = "10.0.0.2" - expect { - provider('/', env).current_user - }.not_to raise_error + expect { provider("/", env).current_user }.not_to raise_error end end it "correctly removes invalid cookies" do - bad_cookie = create_auth_cookie( - token: SecureRandom.hex, - user_id: 1, - trust_level: 4, - issued_at: 5.minutes.ago, - ) - @provider = provider('/') + bad_cookie = + create_auth_cookie( + token: SecureRandom.hex, + user_id: 1, + trust_level: 4, + issued_at: 5.minutes.ago, + ) + @provider = provider("/") @provider.cookie_jar["_t"] = bad_cookie @provider.refresh_session(nil, {}, @provider.cookie_jar) expect(@provider.cookie_jar.key?("_t")).to eq(false) end it "logging on user always creates a new token" do - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) - @provider2 = provider('/') + @provider2 = provider("/") @provider2.log_on_user(user, {}, @provider2.cookie_jar) expect(UserAuthToken.where(user_id: user.id).count).to eq(2) @@ -548,22 +525,24 @@ RSpec.describe Auth::DefaultCurrentUserProvider do it "cleans up old sessions when a user logs in" do yesterday = 1.day.ago - UserAuthToken.insert_all((1..(UserAuthToken::MAX_SESSION_COUNT + 2)).to_a.map do |i| - { - user_id: user.id, - created_at: yesterday + i.seconds, - updated_at: yesterday + i.seconds, - rotated_at: yesterday + i.seconds, - prev_auth_token: "abc#{i}", - auth_token: "abc#{i}" - } - end) + UserAuthToken.insert_all( + (1..(UserAuthToken::MAX_SESSION_COUNT + 2)).to_a.map do |i| + { + user_id: user.id, + created_at: yesterday + i.seconds, + updated_at: yesterday + i.seconds, + rotated_at: yesterday + i.seconds, + prev_auth_token: "abc#{i}", + auth_token: "abc#{i}", + } + end, + ) # Check the oldest 3 still exist expect(UserAuthToken.where(auth_token: (1..3).map { |i| "abc#{i}" }).count).to eq(3) # On next login, gets fixed - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) expect(UserAuthToken.where(user_id: user.id).count).to eq(UserAuthToken::MAX_SESSION_COUNT) @@ -575,7 +554,7 @@ RSpec.describe Auth::DefaultCurrentUserProvider do SiteSetting.force_https = false SiteSetting.same_site_cookies = "Lax" - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) cookie_info = get_cookie_info(@provider.cookie_jar, "_t") @@ -586,7 +565,7 @@ RSpec.describe Auth::DefaultCurrentUserProvider do SiteSetting.force_https = true SiteSetting.same_site_cookies = "Disabled" - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) cookie_info = get_cookie_info(@provider.cookie_jar, "_t") @@ -597,14 +576,15 @@ RSpec.describe Auth::DefaultCurrentUserProvider do it "correctly expires session" do SiteSetting.maximum_session_age = 2 token = UserAuthToken.generate!(user_id: user.id) - cookie = create_auth_cookie( - token: token.unhashed_auth_token, - user_id: user.id, - trust_level: user.trust_level, - issued_at: 5.minutes.ago - ) + cookie = + create_auth_cookie( + token: token.unhashed_auth_token, + user_id: user.id, + trust_level: user.trust_level, + issued_at: 5.minutes.ago, + ) - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) expect(provider("/", "HTTP_COOKIE" => "_t=#{cookie}").current_user.id).to eq(user.id) @@ -628,20 +608,21 @@ RSpec.describe Auth::DefaultCurrentUserProvider do let :api_key do UserApiKey.create!( - application_name: 'my app', - client_id: '1234', - scopes: ['read'].map { |name| UserApiKeyScope.new(name: name) }, - user_id: user.id + application_name: "my app", + client_id: "1234", + scopes: ["read"].map { |name| UserApiKeyScope.new(name: name) }, + user_id: user.id, ) end it "can clear old duplicate keys correctly" do - dupe = UserApiKey.create!( - application_name: 'my app', - client_id: '12345', - scopes: ['read'].map { |name| UserApiKeyScope.new(name: name) }, - user_id: user.id - ) + dupe = + UserApiKey.create!( + application_name: "my app", + client_id: "12345", + scopes: ["read"].map { |name| UserApiKeyScope.new(name: name) }, + user_id: user.id, + ) params = { "REQUEST_METHOD" => "GET", @@ -655,16 +636,13 @@ RSpec.describe Auth::DefaultCurrentUserProvider do end it "allows user API access correctly" do - params = { - "REQUEST_METHOD" => "GET", - "HTTP_USER_API_KEY" => api_key.key, - } + params = { "REQUEST_METHOD" => "GET", "HTTP_USER_API_KEY" => api_key.key } good_provider = provider("/", params) - expect do - expect(good_provider.current_user.id).to eq(user.id) - end.to change { api_key.reload.last_used_at } + expect do expect(good_provider.current_user.id).to eq(user.id) end.to change { + api_key.reload.last_used_at + } expect(good_provider.is_api?).to eq(false) expect(good_provider.is_user_api?).to eq(true) @@ -676,38 +654,27 @@ RSpec.describe Auth::DefaultCurrentUserProvider do user.update_columns(suspended_till: 1.year.from_now) - expect { - provider("/", params).current_user - }.to raise_error(Discourse::InvalidAccess) + expect { provider("/", params).current_user }.to raise_error(Discourse::InvalidAccess) end describe "when readonly mode is enabled due to postgres" do - before do - Discourse.enable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) - end + before { Discourse.enable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) } - after do - Discourse.disable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) - end + after { Discourse.disable_readonly_mode(Discourse::PG_READONLY_MODE_KEY) } - it 'should not update ApiKey#last_used_at' do - params = { - "REQUEST_METHOD" => "GET", - "HTTP_USER_API_KEY" => api_key.key, - } + it "should not update ApiKey#last_used_at" do + params = { "REQUEST_METHOD" => "GET", "HTTP_USER_API_KEY" => api_key.key } good_provider = provider("/", params) - expect do - expect(good_provider.current_user.id).to eq(user.id) - end.to_not change { api_key.reload.last_used_at } + expect do expect(good_provider.current_user.id).to eq(user.id) end.to_not change { + api_key.reload.last_used_at + } end end context "with rate limiting" do - before do - RateLimiter.enable - end + before { RateLimiter.enable } it "rate limits api usage" do limiter1 = RateLimiter.new(nil, "user_api_day_#{ApiKey.hash_key(api_key.key)}", 10, 60) @@ -718,18 +685,11 @@ RSpec.describe Auth::DefaultCurrentUserProvider do global_setting :max_user_api_reqs_per_day, 3 global_setting :max_user_api_reqs_per_minute, 4 - params = { - "REQUEST_METHOD" => "GET", - "HTTP_USER_API_KEY" => api_key.key, - } + params = { "REQUEST_METHOD" => "GET", "HTTP_USER_API_KEY" => api_key.key } - 3.times do - provider("/", params).current_user - end + 3.times { provider("/", params).current_user } - expect { - provider("/", params).current_user - }.to raise_error(RateLimiter::LimitExceeded) + expect { provider("/", params).current_user }.to raise_error(RateLimiter::LimitExceeded) global_setting :max_user_api_reqs_per_day, 4 global_setting :max_user_api_reqs_per_minute, 3 @@ -737,19 +697,15 @@ RSpec.describe Auth::DefaultCurrentUserProvider do limiter1.clear! limiter2.clear! - 3.times do - provider("/", params).current_user - end + 3.times { provider("/", params).current_user } - expect { - provider("/", params).current_user - }.to raise_error(RateLimiter::LimitExceeded) + expect { provider("/", params).current_user }.to raise_error(RateLimiter::LimitExceeded) end end end it "ignores a valid auth cookie that has been tampered with" do - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) cookie = @provider.cookie_jar["_t"] @@ -757,33 +713,38 @@ RSpec.describe Auth::DefaultCurrentUserProvider do ip = "10.0.0.1" env = { "HTTP_COOKIE" => "_t=#{cookie}", "REMOTE_ADDR" => ip } - expect(provider('/', env).current_user).to eq(nil) + expect(provider("/", env).current_user).to eq(nil) end it "copes with json-serialized auth cookies" do # We're switching to :json during the Rails 7 upgrade, but we want a clean revert path # back to Rails 6 if needed - @provider = provider('/', { # The upcoming default - ActionDispatch::Cookies::COOKIES_SERIALIZER => :json, - method: "GET", - }) + @provider = + provider( + "/", + { # The upcoming default + ActionDispatch::Cookies::COOKIES_SERIALIZER => :json, + :method => "GET", + }, + ) @provider.log_on_user(user, {}, @provider.cookie_jar) cookie = CGI.escape(@provider.cookie_jar["_t"]) ip = "10.0.0.1" env = { "HTTP_COOKIE" => "_t=#{cookie}", "REMOTE_ADDR" => ip } - provider2 = provider('/', env) + provider2 = provider("/", env) expect(provider2.current_user).to eq(user) expect(provider2.cookie_jar.encrypted["_t"].keys).to include("user_id", "token") # (strings) end describe "#log_off_user" do it "should work when the current user was cached by a different provider instance" do - user_provider = provider('/') + user_provider = provider("/") user_provider.log_on_user(user, {}, user_provider.cookie_jar) cookie = CGI.escape(user_provider.cookie_jar["_t"]) - env = create_request_env(path: "/").merge({ method: "GET", "HTTP_COOKIE" => "_t=#{cookie}" }) + env = + create_request_env(path: "/").merge({ :method => "GET", "HTTP_COOKIE" => "_t=#{cookie}" }) user_provider = TestProvider.new(env) expect(user_provider.current_user).to eq(user) @@ -802,7 +763,7 @@ RSpec.describe Auth::DefaultCurrentUserProvider do end it "makes the user into an admin if their email is in DISCOURSE_DEVELOPER_EMAILS" do - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) expect(user.reload.admin).to eq(true) user2 = Fabricate(:user) @@ -811,7 +772,7 @@ RSpec.describe Auth::DefaultCurrentUserProvider do end it "adds the user to the correct staff/admin auto groups" do - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) user.reload expect(user.in_any_groups?([Group::AUTO_GROUPS[:staff]])).to eq(true) @@ -819,7 +780,7 @@ RSpec.describe Auth::DefaultCurrentUserProvider do end it "runs the job to enable bootstrap mode" do - @provider = provider('/') + @provider = provider("/") @provider.log_on_user(user, {}, @provider.cookie_jar) expect_job_enqueued(job: :enable_bootstrap_mode, args: { user_id: user.id }) end diff --git a/spec/lib/auth/discord_authenticator_spec.rb b/spec/lib/auth/discord_authenticator_spec.rb index cff7c662f4..7aea3e5d09 100644 --- a/spec/lib/auth/discord_authenticator_spec.rb +++ b/spec/lib/auth/discord_authenticator_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe Auth::DiscordAuthenticator do - let(:hash) { + let(:hash) do OmniAuth::AuthHash.new( provider: "facebook", extra: { @@ -10,27 +10,27 @@ RSpec.describe Auth::DiscordAuthenticator do username: "bobbob", guilds: [ { - "id": "80351110224678912", - "name": "1337 Krew", - "icon": "8342729096ea3675442027381ff50dfe", - "owner": true, - "permissions": 36953089 - } - ] - } + id: "80351110224678912", + name: "1337 Krew", + icon: "8342729096ea3675442027381ff50dfe", + owner: true, + permissions: 36_953_089, + }, + ], + }, }, info: { email: "bob@bob.com", - name: "bobbob" + name: "bobbob", }, - uid: "100" + uid: "100", ) - } + end let(:authenticator) { described_class.new } - describe 'after_authenticate' do - it 'works normally' do + describe "after_authenticate" do + it "works normally" do result = authenticator.after_authenticate(hash) expect(result.user).to eq(nil) expect(result.failed).to eq(false) @@ -38,16 +38,16 @@ RSpec.describe Auth::DiscordAuthenticator do expect(result.email).to eq("bob@bob.com") end - it 'denies access when guilds are restricted' do - SiteSetting.discord_trusted_guilds = ["someguildid", "someotherguildid"].join("|") + it "denies access when guilds are restricted" do + SiteSetting.discord_trusted_guilds = %w[someguildid someotherguildid].join("|") result = authenticator.after_authenticate(hash) expect(result.user).to eq(nil) expect(result.failed).to eq(true) expect(result.failed_reason).to eq(I18n.t("discord.not_in_allowed_guild")) end - it 'allows access when in an allowed guild' do - SiteSetting.discord_trusted_guilds = ["80351110224678912", "anothertrustedguild"].join("|") + it "allows access when in an allowed guild" do + SiteSetting.discord_trusted_guilds = %w[80351110224678912 anothertrustedguild].join("|") result = authenticator.after_authenticate(hash) expect(result.user).to eq(nil) expect(result.failed).to eq(false) diff --git a/spec/lib/auth/facebook_authenticator_spec.rb b/spec/lib/auth/facebook_authenticator_spec.rb index 9033506af3..d19319d3a1 100644 --- a/spec/lib/auth/facebook_authenticator_spec.rb +++ b/spec/lib/auth/facebook_authenticator_spec.rb @@ -1,31 +1,32 @@ # frozen_string_literal: true RSpec.describe Auth::FacebookAuthenticator do - let(:hash) { + let(:hash) do { provider: "facebook", extra: { - raw_info: {} + raw_info: { + }, }, info: { email: "bob@bob.com", first_name: "Bob", - last_name: "Smith" + last_name: "Smith", }, - uid: "100" + uid: "100", } - } + end let(:authenticator) { Auth::FacebookAuthenticator.new } - describe 'after_authenticate' do - it 'can authenticate and create a user record for already existing users' do + describe "after_authenticate" do + it "can authenticate and create a user record for already existing users" do user = Fabricate(:user) result = authenticator.after_authenticate(hash.deep_merge(info: { email: user.email })) expect(result.user.id).to eq(user.id) end - it 'can connect to a different existing user account' do + it "can connect to a different existing user account" do user1 = Fabricate(:user) user2 = Fabricate(:user) @@ -34,46 +35,64 @@ RSpec.describe Auth::FacebookAuthenticator do result = authenticator.after_authenticate(hash, existing_account: user2) expect(result.user.id).to eq(user2.id) - expect(UserAssociatedAccount.exists?(provider_name: "facebook", user_id: user1.id)).to eq(false) - expect(UserAssociatedAccount.exists?(provider_name: "facebook", user_id: user2.id)).to eq(true) + expect(UserAssociatedAccount.exists?(provider_name: "facebook", user_id: user1.id)).to eq( + false, + ) + expect(UserAssociatedAccount.exists?(provider_name: "facebook", user_id: user2.id)).to eq( + true, + ) end - it 'can create a proper result for non existing users' do + it "can create a proper result for non existing users" do result = authenticator.after_authenticate(hash) expect(result.user).to eq(nil) expect(result.name).to eq("Bob Smith") end end - describe 'description_for_user' do + describe "description_for_user" do fab!(:user) { Fabricate(:user) } - it 'returns empty string if no entry for user' do + it "returns empty string if no entry for user" do expect(authenticator.description_for_user(user)).to eq("") end - it 'returns correct information' do - UserAssociatedAccount.create!(provider_name: "facebook", user_id: user.id, provider_uid: 100, info: { email: "someuser@somedomain.tld" }) - expect(authenticator.description_for_user(user)).to eq('someuser@somedomain.tld') + it "returns correct information" do + UserAssociatedAccount.create!( + provider_name: "facebook", + user_id: user.id, + provider_uid: 100, + info: { + email: "someuser@somedomain.tld", + }, + ) + expect(authenticator.description_for_user(user)).to eq("someuser@somedomain.tld") end end - describe 'revoke' do + describe "revoke" do fab!(:user) { Fabricate(:user) } let(:authenticator) { Auth::FacebookAuthenticator.new } - it 'raises exception if no entry for user' do + it "raises exception if no entry for user" do expect { authenticator.revoke(user) }.to raise_error(Discourse::NotFound) end context "with valid record" do before do - SiteSetting.facebook_app_id = '123' - SiteSetting.facebook_app_secret = 'abcde' - UserAssociatedAccount.create!(provider_name: "facebook", user_id: user.id, provider_uid: 100, info: { email: "someuser@somedomain.tld" }) + SiteSetting.facebook_app_id = "123" + SiteSetting.facebook_app_secret = "abcde" + UserAssociatedAccount.create!( + provider_name: "facebook", + user_id: user.id, + provider_uid: 100, + info: { + email: "someuser@somedomain.tld", + }, + ) end - it 'revokes correctly' do + it "revokes correctly" do expect(authenticator.description_for_user(user)).to eq("someuser@somedomain.tld") expect(authenticator.can_revoke?).to eq(true) expect(authenticator.revoke(user)).to eq(true) diff --git a/spec/lib/auth/github_authenticator_spec.rb b/spec/lib/auth/github_authenticator_spec.rb index 15020a9636..b5ec3162fa 100644 --- a/spec/lib/auth/github_authenticator_spec.rb +++ b/spec/lib/auth/github_authenticator_spec.rb @@ -4,11 +4,7 @@ def auth_token_for(user) { provider: "github", extra: { - all_emails: [{ - email: user.email, - primary: true, - verified: true, - }] + all_emails: [{ email: user.email, primary: true, verified: true }], }, info: { email: user.email, @@ -16,7 +12,7 @@ def auth_token_for(user) name: user.name, image: "https://avatars3.githubusercontent.com/u/#{user.username}", }, - uid: '100' + uid: "100", } end @@ -24,10 +20,10 @@ RSpec.describe Auth::GithubAuthenticator do let(:authenticator) { described_class.new } fab!(:user) { Fabricate(:user) } - describe 'after_authenticate' do + describe "after_authenticate" do let(:data) { auth_token_for(user) } - it 'can authenticate and create a user record for already existing users' do + it "can authenticate and create a user record for already existing users" do result = authenticator.after_authenticate(data) expect(result.user.id).to eq(user.id) @@ -43,37 +39,41 @@ RSpec.describe Auth::GithubAuthenticator do expect(result.email_valid).to eq(true) end - it 'can authenticate and update GitHub screen_name for existing user' do - UserAssociatedAccount.create!(user_id: user.id, provider_name: "github", provider_uid: 100, info: { nickname: "boris" }) + it "can authenticate and update GitHub screen_name for existing user" do + UserAssociatedAccount.create!( + user_id: user.id, + provider_name: "github", + provider_uid: 100, + info: { + nickname: "boris", + }, + ) result = authenticator.after_authenticate(data) expect(result.user.id).to eq(user.id) expect(result.email).to eq(user.email) expect(result.email_valid).to eq(true) - expect(UserAssociatedAccount.find_by(provider_name: "github", user_id: user.id).info["nickname"]).to eq(user.username) + expect( + UserAssociatedAccount.find_by(provider_name: "github", user_id: user.id).info["nickname"], + ).to eq(user.username) end - it 'should use primary email for new user creation over other available emails' do + it "should use primary email for new user creation over other available emails" do hash = { provider: "github", extra: { - all_emails: [{ - email: "bob@example.com", - primary: false, - verified: true, - }, { - email: "john@example.com", - primary: true, - verified: true, - }] + all_emails: [ + { email: "bob@example.com", primary: false, verified: true }, + { email: "john@example.com", primary: true, verified: true }, + ], }, info: { email: "john@example.com", nickname: "john", name: "John Bob", }, - uid: "100" + uid: "100", } result = authenticator.after_authenticate(hash) @@ -81,28 +81,30 @@ RSpec.describe Auth::GithubAuthenticator do expect(result.email).to eq("john@example.com") end - it 'should not error out if user already has a different old github account attached' do - + it "should not error out if user already has a different old github account attached" do # There is a rare case where an end user had # 2 different github accounts and moved emails between the 2 - UserAssociatedAccount.create!(user_id: user.id, info: { nickname: 'bob' }, provider_uid: 100, provider_name: "github") + UserAssociatedAccount.create!( + user_id: user.id, + info: { + nickname: "bob", + }, + provider_uid: 100, + provider_name: "github", + ) hash = { provider: "github", extra: { - all_emails: [{ - email: user.email, - primary: false, - verified: true, - }] + all_emails: [{ email: user.email, primary: false, verified: true }], }, info: { email: "john@example.com", nickname: "john", name: "John Bob", }, - uid: "1001" + uid: "1001", } result = authenticator.after_authenticate(hash) @@ -111,22 +113,18 @@ RSpec.describe Auth::GithubAuthenticator do expect(UserAssociatedAccount.where(user_id: user.id).pluck(:provider_uid)).to eq(["1001"]) end - it 'will not authenticate for already existing users with an unverified email' do + it "will not authenticate for already existing users with an unverified email" do hash = { provider: "github", extra: { - all_emails: [{ - email: user.email, - primary: true, - verified: false, - }] + all_emails: [{ email: user.email, primary: true, verified: false }], }, info: { email: user.email, nickname: user.username, name: user.name, }, - uid: "100" + uid: "100", } result = authenticator.after_authenticate(hash) @@ -138,22 +136,18 @@ RSpec.describe Auth::GithubAuthenticator do expect(result.email_valid).to eq(false) end - it 'can create a proper result for non existing users' do + it "can create a proper result for non existing users" do hash = { provider: "github", extra: { - all_emails: [{ - email: "person@example.com", - primary: true, - verified: true, - }] + all_emails: [{ email: "person@example.com", primary: true, verified: true }], }, info: { email: "person@example.com", nickname: "person", name: "Person Lastname", }, - uid: "100" + uid: "100", } result = authenticator.after_authenticate(hash) @@ -165,26 +159,21 @@ RSpec.describe Auth::GithubAuthenticator do expect(result.email_valid).to eq(hash[:info][:email].present?) end - it 'will skip blocklisted domains for non existing users' do + it "will skip blocklisted domains for non existing users" do hash = { provider: "github", extra: { - all_emails: [{ - email: "not_allowed@blocklist.com", - primary: true, - verified: true, - }, { - email: "allowed@allowlist.com", - primary: false, - verified: true, - }] + all_emails: [ + { email: "not_allowed@blocklist.com", primary: true, verified: true }, + { email: "allowed@allowlist.com", primary: false, verified: true }, + ], }, info: { email: "not_allowed@blocklist.com", nickname: "person", name: "Person Lastname", }, - uid: "100" + uid: "100", } SiteSetting.blocked_email_domains = "blocklist.com" @@ -197,30 +186,22 @@ RSpec.describe Auth::GithubAuthenticator do expect(result.email_valid).to eq(true) end - it 'will find allowlisted domains for non existing users' do + it "will find allowlisted domains for non existing users" do hash = { provider: "github", extra: { - all_emails: [{ - email: "person@example.com", - primary: true, - verified: true, - }, { - email: "not_allowed@blocklist.com", - primary: false, - verified: true, - }, { - email: "allowed@allowlist.com", - primary: false, - verified: true, - }] + all_emails: [ + { email: "person@example.com", primary: true, verified: true }, + { email: "not_allowed@blocklist.com", primary: false, verified: true }, + { email: "allowed@allowlist.com", primary: false, verified: true }, + ], }, info: { email: "person@example.com", nickname: "person", name: "Person Lastname", }, - uid: "100" + uid: "100", } SiteSetting.allowed_email_domains = "allowlist.com" @@ -233,13 +214,20 @@ RSpec.describe Auth::GithubAuthenticator do expect(result.email_valid).to eq(true) end - it 'can connect to a different existing user account' do + it "can connect to a different existing user account" do user1 = Fabricate(:user) user2 = Fabricate(:user) expect(authenticator.can_connect_existing_user?).to eq(true) - UserAssociatedAccount.create!(provider_name: "github", user_id: user1.id, provider_uid: 100, info: { nickname: "boris" }) + UserAssociatedAccount.create!( + provider_name: "github", + user_id: user1.id, + provider_uid: 100, + info: { + nickname: "boris", + }, + ) result = authenticator.after_authenticate(data, existing_account: user2) @@ -249,47 +237,55 @@ RSpec.describe Auth::GithubAuthenticator do end end - describe 'revoke' do + describe "revoke" do fab!(:user) { Fabricate(:user) } let(:authenticator) { Auth::GithubAuthenticator.new } - it 'raises exception if no entry for user' do + it "raises exception if no entry for user" do expect { authenticator.revoke(user) }.to raise_error(Discourse::NotFound) end - it 'revokes correctly' do - UserAssociatedAccount.create!(provider_name: "github", user_id: user.id, provider_uid: 100, info: { nickname: "boris" }) + it "revokes correctly" do + UserAssociatedAccount.create!( + provider_name: "github", + user_id: user.id, + provider_uid: 100, + info: { + nickname: "boris", + }, + ) expect(authenticator.can_revoke?).to eq(true) expect(authenticator.revoke(user)).to eq(true) expect(authenticator.description_for_user(user)).to eq("") end end - describe 'avatar retrieval' do + describe "avatar retrieval" do let(:job_klass) { Jobs::DownloadAvatarFromUrl } - context 'when user has a custom avatar' do + context "when user has a custom avatar" do fab!(:user_avatar) { Fabricate(:user_avatar, custom_upload: Fabricate(:upload)) } fab!(:user_with_custom_avatar) { Fabricate(:user, user_avatar: user_avatar) } - it 'does not enqueue a download_avatar_from_url job' do + it "does not enqueue a download_avatar_from_url job" do expect { authenticator.after_authenticate(auth_token_for(user_with_custom_avatar)) }.to_not change(job_klass.jobs, :size) end end - context 'when user does not have a custom avatar' do - it 'enqueues a download_avatar_from_url job' do - expect { - authenticator.after_authenticate(auth_token_for(user)) - }.to change(job_klass.jobs, :size).by(1) + context "when user does not have a custom avatar" do + it "enqueues a download_avatar_from_url job" do + expect { authenticator.after_authenticate(auth_token_for(user)) }.to change( + job_klass.jobs, + :size, + ).by(1) - job_args = job_klass.jobs.last['args'].first + job_args = job_klass.jobs.last["args"].first - expect(job_args['url']).to eq("https://avatars3.githubusercontent.com/u/#{user.username}") - expect(job_args['user_id']).to eq(user.id) - expect(job_args['override_gravatar']).to eq(false) + expect(job_args["url"]).to eq("https://avatars3.githubusercontent.com/u/#{user.username}") + expect(job_args["user_id"]).to eq(user.id) + expect(job_args["override_gravatar"]).to eq(false) end end end diff --git a/spec/lib/auth/google_oauth2_authenticator_spec.rb b/spec/lib/auth/google_oauth2_authenticator_spec.rb index f610763ed3..d42a08d1d1 100644 --- a/spec/lib/auth/google_oauth2_authenticator_spec.rb +++ b/spec/lib/auth/google_oauth2_authenticator_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true RSpec.describe Auth::GoogleOAuth2Authenticator do - it 'does not look up user unless email is verified' do + it "does not look up user unless email is verified" do # note, emails that come back from google via omniauth are always valid # this protects against future regressions @@ -12,16 +12,16 @@ RSpec.describe Auth::GoogleOAuth2Authenticator do provider: "google_oauth2", uid: "123456789", info: { - name: "John Doe", - email: user.email + name: "John Doe", + email: user.email, }, extra: { raw_info: { email: user.email, email_verified: false, - name: "John Doe" - } - } + name: "John Doe", + }, + }, } result = authenticator.after_authenticate(hash) @@ -29,8 +29,8 @@ RSpec.describe Auth::GoogleOAuth2Authenticator do expect(result.user).to eq(nil) end - describe 'after_authenticate' do - it 'can authenticate and create a user record for already existing users' do + describe "after_authenticate" do + it "can authenticate and create a user record for already existing users" do authenticator = Auth::GoogleOAuth2Authenticator.new user = Fabricate(:user) @@ -38,16 +38,16 @@ RSpec.describe Auth::GoogleOAuth2Authenticator do provider: "google_oauth2", uid: "123456789", info: { - name: "John Doe", - email: user.email + name: "John Doe", + email: user.email, }, extra: { raw_info: { email: user.email, email_verified: true, - name: "John Doe" - } - } + name: "John Doe", + }, + }, } result = authenticator.after_authenticate(hash) @@ -55,27 +55,31 @@ RSpec.describe Auth::GoogleOAuth2Authenticator do expect(result.user.id).to eq(user.id) end - it 'can connect to a different existing user account' do + it "can connect to a different existing user account" do authenticator = Auth::GoogleOAuth2Authenticator.new user1 = Fabricate(:user) user2 = Fabricate(:user) - UserAssociatedAccount.create!(provider_name: "google_oauth2", user_id: user1.id, provider_uid: 100) + UserAssociatedAccount.create!( + provider_name: "google_oauth2", + user_id: user1.id, + provider_uid: 100, + ) hash = { provider: "google_oauth2", uid: "100", info: { - name: "John Doe", - email: user1.email + name: "John Doe", + email: user1.email, }, extra: { raw_info: { email: user1.email, email_verified: true, - name: "John Doe" - } - } + name: "John Doe", + }, + }, } result = authenticator.after_authenticate(hash, existing_account: user2) @@ -85,23 +89,23 @@ RSpec.describe Auth::GoogleOAuth2Authenticator do expect(UserAssociatedAccount.exists?(user_id: user2.id)).to eq(true) end - it 'can create a proper result for non existing users' do + it "can create a proper result for non existing users" do hash = { provider: "google_oauth2", uid: "123456789", info: { - first_name: "Jane", - last_name: "Doe", - name: "Jane Doe", - email: "jane.doe@the.google.com" + first_name: "Jane", + last_name: "Doe", + name: "Jane Doe", + email: "jane.doe@the.google.com", }, extra: { raw_info: { email: "jane.doe@the.google.com", email_verified: true, - name: "Jane Doe" - } - } + name: "Jane Doe", + }, + }, } authenticator = described_class.new @@ -117,48 +121,38 @@ RSpec.describe Auth::GoogleOAuth2Authenticator do group1 = OmniAuth::AuthHash.new(id: "12345", name: "group1") group2 = OmniAuth::AuthHash.new(id: "67890", name: "group2") @groups = [group1, group2] - @auth_hash = OmniAuth::AuthHash.new( - provider: "google_oauth2", - uid: "123456789", - info: { - first_name: "Jane", - last_name: "Doe", - name: "Jane Doe", - email: "jane.doe@the.google.com" - }, - extra: { - raw_info: { + @auth_hash = + OmniAuth::AuthHash.new( + provider: "google_oauth2", + uid: "123456789", + info: { + first_name: "Jane", + last_name: "Doe", + name: "Jane Doe", email: "jane.doe@the.google.com", - email_verified: true, - name: "Jane Doe" }, - } - ) + extra: { + raw_info: { + email: "jane.doe@the.google.com", + email_verified: true, + name: "Jane Doe", + }, + }, + ) end context "when enabled" do let(:private_key) { OpenSSL::PKey::RSA.generate(2048) } - let(:group_response) { - { - groups: [ - { - id: "12345", - name: "group1" - }, - { - id: "67890", - name: "group2" - } - ] - } - } + let(:group_response) do + { groups: [{ id: "12345", name: "group1" }, { id: "67890", name: "group2" }] } + end before do SiteSetting.google_oauth2_hd_groups_service_account_admin_email = "admin@example.com" SiteSetting.google_oauth2_hd_groups_service_account_json = { "private_key" => private_key.to_s, - "client_email": "discourse-group-sync@example.iam.gserviceaccount.com", + :"client_email" => "discourse-group-sync@example.iam.gserviceaccount.com", }.to_json SiteSetting.google_oauth2_hd_groups = true @@ -166,28 +160,30 @@ RSpec.describe Auth::GoogleOAuth2Authenticator do stub_request(:post, "https://oauth2.googleapis.com/token").to_return do |request| jwt = Rack::Utils.parse_query(request.body)["assertion"] - decoded_token = JWT.decode(jwt, private_key.public_key, true, { algorithm: 'RS256' }) + decoded_token = JWT.decode(jwt, private_key.public_key, true, { algorithm: "RS256" }) { status: 200, body: { "access_token" => token, "type" => "bearer" }.to_json, - headers: { "Content-Type" => "application/json" } + headers: { + "Content-Type" => "application/json", + }, } rescue JWT::VerificationError - { - status: 403, - body: "Invalid JWT" - } + { status: 403, body: "Invalid JWT" } end - stub_request(:get, "https://admin.googleapis.com/admin/directory/v1/groups?userKey=#{@auth_hash.uid}"). - with(headers: { "Authorization" => "Bearer #{token}" }). - to_return do + stub_request( + :get, + "https://admin.googleapis.com/admin/directory/v1/groups?userKey=#{@auth_hash.uid}", + ) + .with(headers: { "Authorization" => "Bearer #{token}" }) + .to_return do { status: 200, body: group_response.to_json, headers: { - "Content-Type" => "application/json" - } + "Content-Type" => "application/json", + }, } end end @@ -206,7 +202,7 @@ RSpec.describe Auth::GoogleOAuth2Authenticator do it "doesn't explode with invalid credentials" do SiteSetting.google_oauth2_hd_groups_service_account_json = { "private_key" => OpenSSL::PKey::RSA.generate(2048).to_s, - "client_email": "discourse-group-sync@example.iam.gserviceaccount.com", + :"client_email" => "discourse-group-sync@example.iam.gserviceaccount.com", }.to_json result = described_class.new.after_authenticate(@auth_hash) @@ -215,9 +211,7 @@ RSpec.describe Auth::GoogleOAuth2Authenticator do end context "when disabled" do - before do - SiteSetting.google_oauth2_hd_groups = false - end + before { SiteSetting.google_oauth2_hd_groups = false } it "doesnt add associated groups" do result = described_class.new.after_authenticate(@auth_hash) @@ -227,16 +221,20 @@ RSpec.describe Auth::GoogleOAuth2Authenticator do end end - describe 'revoke' do + describe "revoke" do fab!(:user) { Fabricate(:user) } let(:authenticator) { Auth::GoogleOAuth2Authenticator.new } - it 'raises exception if no entry for user' do + it "raises exception if no entry for user" do expect { authenticator.revoke(user) }.to raise_error(Discourse::NotFound) end - it 'revokes correctly' do - UserAssociatedAccount.create!(provider_name: "google_oauth2", user_id: user.id, provider_uid: 12345) + it "revokes correctly" do + UserAssociatedAccount.create!( + provider_name: "google_oauth2", + user_id: user.id, + provider_uid: 12_345, + ) expect(authenticator.can_revoke?).to eq(true) expect(authenticator.revoke(user)).to eq(true) expect(authenticator.description_for_user(user)).to eq("") diff --git a/spec/lib/auth/managed_authenticator_spec.rb b/spec/lib/auth/managed_authenticator_spec.rb index a91cae9933..940be5776b 100644 --- a/spec/lib/auth/managed_authenticator_spec.rb +++ b/spec/lib/auth/managed_authenticator_spec.rb @@ -1,19 +1,21 @@ # frozen_string_literal: true RSpec.describe Auth::ManagedAuthenticator do - let(:authenticator) { - Class.new(described_class) do - def name - "myauth" - end + let(:authenticator) do + Class + .new(described_class) do + def name + "myauth" + end - def primary_email_verified?(auth_token) - auth_token[:info][:email_verified] + def primary_email_verified?(auth_token) + auth_token[:info][:email_verified] + end end - end.new - } + .new + end - let(:hash) { + let(:hash) do OmniAuth::AuthHash.new( provider: "myauth", uid: "1234", @@ -21,25 +23,20 @@ RSpec.describe Auth::ManagedAuthenticator do name: "Best Display Name", email: "awesome@example.com", nickname: "IAmGroot", - email_verified: true + email_verified: true, }, credentials: { - token: "supersecrettoken" + token: "supersecrettoken", }, extra: { raw_info: { - randominfo: "some info" - } - } + randominfo: "some info", + }, + }, ) - } + end - let(:create_hash) { - OmniAuth::AuthHash.new( - provider: "myauth", - uid: "1234" - ) - } + let(:create_hash) { OmniAuth::AuthHash.new(provider: "myauth", uid: "1234") } def create_auth_result(attrs) auth_result = Auth::Result.new @@ -47,10 +44,16 @@ RSpec.describe Auth::ManagedAuthenticator do auth_result end - describe 'after_authenticate' do - it 'can match account from an existing association' do + describe "after_authenticate" do + it "can match account from an existing association" do user = Fabricate(:user) - associated = UserAssociatedAccount.create!(user: user, provider_name: 'myauth', provider_uid: "1234", last_used: 1.year.ago) + associated = + UserAssociatedAccount.create!( + user: user, + provider_name: "myauth", + provider_uid: "1234", + last_used: 1.year.ago, + ) result = authenticator.after_authenticate(hash) expect(result.user.id).to eq(user.id) @@ -62,36 +65,50 @@ RSpec.describe Auth::ManagedAuthenticator do expect(associated.extra["raw_info"]["randominfo"]).to eq("some info") end - it 'only sets email valid for present strings' do + it "only sets email valid for present strings" do # (Twitter sometimes sends empty email strings) - result = authenticator.after_authenticate(create_hash.merge(info: { email: "email@example.com", email_verified: true })) + result = + authenticator.after_authenticate( + create_hash.merge(info: { email: "email@example.com", email_verified: true }), + ) expect(result.email_valid).to eq(true) - result = authenticator.after_authenticate(create_hash.merge(info: { email: "", email_verified: true })) + result = + authenticator.after_authenticate( + create_hash.merge(info: { email: "", email_verified: true }), + ) expect(result.email_valid).to be_falsey - result = authenticator.after_authenticate(create_hash.merge(info: { email: nil, email_verified: true })) + result = + authenticator.after_authenticate( + create_hash.merge(info: { email: nil, email_verified: true }), + ) expect(result.email_valid).to be_falsey end - it 'does not set email valid if email_verified is false' do - result = authenticator.after_authenticate(create_hash.merge(info: { email: "email@example.com", email_verified: false })) + it "does not set email valid if email_verified is false" do + result = + authenticator.after_authenticate( + create_hash.merge(info: { email: "email@example.com", email_verified: false }), + ) expect(result.email_valid).to eq(false) end - describe 'connecting to another user account' do + describe "connecting to another user account" do fab!(:user1) { Fabricate(:user) } fab!(:user2) { Fabricate(:user) } - before { UserAssociatedAccount.create!(user: user1, provider_name: 'myauth', provider_uid: "1234") } + before do + UserAssociatedAccount.create!(user: user1, provider_name: "myauth", provider_uid: "1234") + end - it 'works by default' do + it "works by default" do result = authenticator.after_authenticate(hash, existing_account: user2) expect(result.user.id).to eq(user2.id) expect(UserAssociatedAccount.exists?(user_id: user1.id)).to eq(false) expect(UserAssociatedAccount.exists?(user_id: user2.id)).to eq(true) end - it 'still works if another user has a matching email' do + it "still works if another user has a matching email" do Fabricate(:user, email: hash.dig(:info, :email)) result = authenticator.after_authenticate(hash, existing_account: user2) expect(result.user.id).to eq(user2.id) @@ -99,15 +116,18 @@ RSpec.describe Auth::ManagedAuthenticator do expect(UserAssociatedAccount.exists?(user_id: user2.id)).to eq(true) end - it 'does not work when disabled' do - authenticator = Class.new(described_class) do - def name - "myauth" - end - def can_connect_existing_user? - false - end - end.new + it "does not work when disabled" do + authenticator = + Class + .new(described_class) do + def name + "myauth" + end + def can_connect_existing_user? + false + end + end + .new result = authenticator.after_authenticate(hash, existing_account: user2) expect(result.user.id).to eq(user1.id) expect(UserAssociatedAccount.exists?(user_id: user1.id)).to eq(true) @@ -115,42 +135,48 @@ RSpec.describe Auth::ManagedAuthenticator do end end - describe 'match by email' do - it 'downcases the email address from the authprovider' do - result = authenticator.after_authenticate(hash.deep_merge(info: { email: "HELLO@example.com" })) - expect(result.email).to eq('hello@example.com') + describe "match by email" do + it "downcases the email address from the authprovider" do + result = + authenticator.after_authenticate(hash.deep_merge(info: { email: "HELLO@example.com" })) + expect(result.email).to eq("hello@example.com") end - it 'works normally' do + it "works normally" do user = Fabricate(:user) result = authenticator.after_authenticate(hash.deep_merge(info: { email: user.email })) expect(result.user.id).to eq(user.id) - expect(UserAssociatedAccount.find_by(provider_name: 'myauth', provider_uid: "1234").user_id).to eq(user.id) + expect( + UserAssociatedAccount.find_by(provider_name: "myauth", provider_uid: "1234").user_id, + ).to eq(user.id) end - it 'works if there is already an association with the target account' do + it "works if there is already an association with the target account" do user = Fabricate(:user, email: "awesome@example.com") result = authenticator.after_authenticate(hash) expect(result.user.id).to eq(user.id) end - it 'does not match if match_by_email is false' do - authenticator = Class.new(described_class) do - def name - "myauth" - end - def match_by_email - false - end - end.new + it "does not match if match_by_email is false" do + authenticator = + Class + .new(described_class) do + def name + "myauth" + end + def match_by_email + false + end + end + .new user = Fabricate(:user, email: "awesome@example.com") result = authenticator.after_authenticate(hash) expect(result.user).to eq(nil) end end - context 'when no matching user' do - it 'returns the correct information' do + context "when no matching user" do + it "returns the correct information" do expect { result = authenticator.after_authenticate(hash) expect(result.user).to eq(nil) @@ -161,13 +187,13 @@ RSpec.describe Auth::ManagedAuthenticator do expect(UserAssociatedAccount.last.info["nickname"]).to eq("IAmGroot") end - it 'works if there is already an association with the target account' do + it "works if there is already an association with the target account" do user = Fabricate(:user, email: "awesome@example.com") result = authenticator.after_authenticate(hash) expect(result.user.id).to eq(user.id) end - it 'works if there is no email' do + it "works if there is no email" do expect { result = authenticator.after_authenticate(hash.deep_merge(info: { email: nil })) expect(result.user).to eq(nil) @@ -178,7 +204,7 @@ RSpec.describe Auth::ManagedAuthenticator do expect(UserAssociatedAccount.last.info["nickname"]).to eq("IAmGroot") end - it 'will ignore name when equal to email' do + it "will ignore name when equal to email" do result = authenticator.after_authenticate(hash.deep_merge(info: { name: hash.info.email })) expect(result.email).to eq(hash.info.email) expect(result.name).to eq(nil) @@ -187,39 +213,61 @@ RSpec.describe Auth::ManagedAuthenticator do describe "avatar on update" do fab!(:user) { Fabricate(:user) } - let!(:associated) { UserAssociatedAccount.create!(user: user, provider_name: 'myauth', provider_uid: "1234") } + let!(:associated) do + UserAssociatedAccount.create!(user: user, provider_name: "myauth", provider_uid: "1234") + end it "schedules the job upon update correctly" do # No image supplied, do not schedule - expect { result = authenticator.after_authenticate(hash) } - .not_to change { Jobs::DownloadAvatarFromUrl.jobs.count } + expect { result = authenticator.after_authenticate(hash) }.not_to change { + Jobs::DownloadAvatarFromUrl.jobs.count + } # Image supplied, schedule - expect { result = authenticator.after_authenticate(hash.deep_merge(info: { image: "https://some.domain/image.jpg" })) } - .to change { Jobs::DownloadAvatarFromUrl.jobs.count }.by(1) + expect { + result = + authenticator.after_authenticate( + hash.deep_merge(info: { image: "https://some.domain/image.jpg" }), + ) + }.to change { Jobs::DownloadAvatarFromUrl.jobs.count }.by(1) # User already has profile picture, don't schedule user.user_avatar = Fabricate(:user_avatar, custom_upload: Fabricate(:upload)) user.save! - expect { result = authenticator.after_authenticate(hash.deep_merge(info: { image: "https://some.domain/image.jpg" })) } - .not_to change { Jobs::DownloadAvatarFromUrl.jobs.count } + expect { + result = + authenticator.after_authenticate( + hash.deep_merge(info: { image: "https://some.domain/image.jpg" }), + ) + }.not_to change { Jobs::DownloadAvatarFromUrl.jobs.count } end end describe "profile on update" do fab!(:user) { Fabricate(:user) } - let!(:associated) { UserAssociatedAccount.create!(user: user, provider_name: 'myauth', provider_uid: "1234") } + let!(:associated) do + UserAssociatedAccount.create!(user: user, provider_name: "myauth", provider_uid: "1234") + end it "updates the user's location and bio, unless already set" do { description: :bio_raw, location: :location }.each do |auth_hash_key, profile_key| user.user_profile.update(profile_key => "Initial Value") # No value supplied, do not overwrite - expect { result = authenticator.after_authenticate(hash) } - .not_to change { user.user_profile.reload; user.user_profile[profile_key] } + expect { result = authenticator.after_authenticate(hash) }.not_to change { + user.user_profile.reload + user.user_profile[profile_key] + } # Value supplied, still do not overwrite - expect { result = authenticator.after_authenticate(hash.deep_merge(info: { auth_hash_key => "New Value" })) } - .not_to change { user.user_profile.reload; user.user_profile[profile_key] } + expect { + result = + authenticator.after_authenticate( + hash.deep_merge(info: { auth_hash_key => "New Value" }), + ) + }.not_to change { + user.user_profile.reload + user.user_profile[profile_key] + } # User has not set a value, so overwrite user.user_profile.update(profile_key => "") @@ -232,24 +280,32 @@ RSpec.describe Auth::ManagedAuthenticator do describe "avatar on create" do fab!(:user) { Fabricate(:user) } - let!(:association) { UserAssociatedAccount.create!(provider_name: 'myauth', provider_uid: "1234") } + let!(:association) do + UserAssociatedAccount.create!(provider_name: "myauth", provider_uid: "1234") + end it "doesn't schedule with no image" do - expect { result = authenticator.after_create_account(user, create_auth_result(extra_data: create_hash)) } - .not_to change { Jobs::DownloadAvatarFromUrl.jobs.count } + expect { + result = + authenticator.after_create_account(user, create_auth_result(extra_data: create_hash)) + }.not_to change { Jobs::DownloadAvatarFromUrl.jobs.count } end it "schedules with image" do association.info["image"] = "https://some.domain/image.jpg" association.save! - expect { result = authenticator.after_create_account(user, create_auth_result(extra_data: create_hash)) } - .to change { Jobs::DownloadAvatarFromUrl.jobs.count }.by(1) + expect { + result = + authenticator.after_create_account(user, create_auth_result(extra_data: create_hash)) + }.to change { Jobs::DownloadAvatarFromUrl.jobs.count }.by(1) end end describe "profile on create" do fab!(:user) { Fabricate(:user) } - let!(:association) { UserAssociatedAccount.create!(provider_name: 'myauth', provider_uid: "1234") } + let!(:association) do + UserAssociatedAccount.create!(provider_name: "myauth", provider_uid: "1234") + end it "doesn't explode without profile" do authenticator.after_create_account(user, create_auth_result(extra_data: create_hash)) @@ -265,37 +321,44 @@ RSpec.describe Auth::ManagedAuthenticator do end end - describe 'match by username' do - let(:user_match_authenticator) { - Class.new(described_class) do - def name - "myauth" + describe "match by username" do + let(:user_match_authenticator) do + Class + .new(described_class) do + def name + "myauth" + end + def match_by_email + false + end + def match_by_username + true + end end - def match_by_email - false - end - def match_by_username - true - end - end.new - } - - it 'works normally' do - SiteSetting.username_change_period = 0 - user = Fabricate(:user) - result = user_match_authenticator.after_authenticate(hash.deep_merge(info: { nickname: user.username })) - expect(result.user.id).to eq(user.id) - expect(UserAssociatedAccount.find_by(provider_name: 'myauth', provider_uid: "1234").user_id).to eq(user.id) + .new end - it 'works if there is already an association with the target account' do + it "works normally" do + SiteSetting.username_change_period = 0 + user = Fabricate(:user) + result = + user_match_authenticator.after_authenticate( + hash.deep_merge(info: { nickname: user.username }), + ) + expect(result.user.id).to eq(user.id) + expect( + UserAssociatedAccount.find_by(provider_name: "myauth", provider_uid: "1234").user_id, + ).to eq(user.id) + end + + it "works if there is already an association with the target account" do SiteSetting.username_change_period = 0 user = Fabricate(:user, username: "IAmGroot") result = user_match_authenticator.after_authenticate(hash) expect(result.user.id).to eq(user.id) end - it 'works if the username is different case' do + it "works if the username is different case" do SiteSetting.username_change_period = 0 user = Fabricate(:user, username: "IAMGROOT") result = user_match_authenticator.after_authenticate(hash) @@ -309,28 +372,34 @@ RSpec.describe Auth::ManagedAuthenticator do expect(result.user).to eq(nil) end - it 'does not match if default match_by_username not overriden' do + it "does not match if default match_by_username not overriden" do SiteSetting.username_change_period = 0 - authenticator = Class.new(described_class) do - def name - "myauth" - end - end.new + authenticator = + Class + .new(described_class) do + def name + "myauth" + end + end + .new user = Fabricate(:user, username: "IAmGroot") result = authenticator.after_authenticate(hash) expect(result.user).to eq(nil) end - it 'does not match if match_by_username is false' do + it "does not match if match_by_username is false" do SiteSetting.username_change_period = 0 - authenticator = Class.new(described_class) do - def name - "myauth" - end - def match_by_username - false - end - end.new + authenticator = + Class + .new(described_class) do + def name + "myauth" + end + def match_by_username + false + end + end + .new user = Fabricate(:user, username: "IAmGroot") result = authenticator.after_authenticate(hash) expect(result.user).to eq(nil) @@ -338,38 +407,57 @@ RSpec.describe Auth::ManagedAuthenticator do end end - describe 'description_for_user' do + describe "description_for_user" do fab!(:user) { Fabricate(:user) } - it 'returns empty string if no entry for user' do + it "returns empty string if no entry for user" do expect(authenticator.description_for_user(user)).to eq("") end - it 'returns correct information' do - association = UserAssociatedAccount.create!(user: user, provider_name: 'myauth', provider_uid: "1234", info: { nickname: "somenickname", email: "test@domain.tld", name: "bestname" }) - expect(authenticator.description_for_user(user)).to eq('test@domain.tld') + it "returns correct information" do + association = + UserAssociatedAccount.create!( + user: user, + provider_name: "myauth", + provider_uid: "1234", + info: { + nickname: "somenickname", + email: "test@domain.tld", + name: "bestname", + }, + ) + expect(authenticator.description_for_user(user)).to eq("test@domain.tld") association.update(info: { nickname: "somenickname", name: "bestname" }) - expect(authenticator.description_for_user(user)).to eq('somenickname') + expect(authenticator.description_for_user(user)).to eq("somenickname") association.update(info: { nickname: "bestname" }) - expect(authenticator.description_for_user(user)).to eq('bestname') + expect(authenticator.description_for_user(user)).to eq("bestname") association.update(info: {}) - expect(authenticator.description_for_user(user)).to eq(I18n.t("associated_accounts.connected")) + expect(authenticator.description_for_user(user)).to eq( + I18n.t("associated_accounts.connected"), + ) end end - describe 'revoke' do + describe "revoke" do fab!(:user) { Fabricate(:user) } - it 'raises exception if no entry for user' do + it "raises exception if no entry for user" do expect { authenticator.revoke(user) }.to raise_error(Discourse::NotFound) end context "with valid record" do before do - UserAssociatedAccount.create!(user: user, provider_name: 'myauth', provider_uid: "1234", info: { name: "somename" }) + UserAssociatedAccount.create!( + user: user, + provider_name: "myauth", + provider_uid: "1234", + info: { + name: "somename", + }, + ) end - it 'revokes correctly' do + it "revokes correctly" do expect(authenticator.description_for_user(user)).to eq("somename") expect(authenticator.can_revoke?).to eq(true) expect(authenticator.revoke(user)).to eq(true) @@ -378,5 +466,4 @@ RSpec.describe Auth::ManagedAuthenticator do end end end - end diff --git a/spec/lib/auth/result_spec.rb b/spec/lib/auth/result_spec.rb index 2c9a83e057..ebc5830ce7 100644 --- a/spec/lib/auth/result_spec.rb +++ b/spec/lib/auth/result_spec.rb @@ -3,7 +3,9 @@ RSpec.describe Auth::Result do fab!(:initial_email) { "initialemail@example.org" } fab!(:initial_username) { "initialusername" } fab!(:initial_name) { "Initial Name" } - fab!(:user) { Fabricate(:user, email: initial_email, username: initial_username, name: initial_name) } + fab!(:user) do + Fabricate(:user, email: initial_email, username: initial_username, name: initial_name) + end let(:new_email) { "newemail@example.org" } let(:new_username) { "newusername" } diff --git a/spec/lib/auth/twitter_authenticator_spec.rb b/spec/lib/auth/twitter_authenticator_spec.rb index 14f5c834a6..c6e1bdfd69 100644 --- a/spec/lib/auth/twitter_authenticator_spec.rb +++ b/spec/lib/auth/twitter_authenticator_spec.rb @@ -14,7 +14,7 @@ RSpec.describe Auth::TwitterAuthenticator do nickname: "minion", }, uid: "123", - provider: "twitter" + provider: "twitter", } result = auth.after_authenticate(auth_token) @@ -25,7 +25,7 @@ RSpec.describe Auth::TwitterAuthenticator do expect(info.info["email"]).to eq(user.email) end - it 'can connect to a different existing user account' do + it "can connect to a different existing user account" do authenticator = Auth::TwitterAuthenticator.new user1 = Fabricate(:user) user2 = Fabricate(:user) @@ -40,7 +40,7 @@ RSpec.describe Auth::TwitterAuthenticator do nickname: "minion", }, uid: "100", - provider: "twitter" + provider: "twitter", } result = authenticator.after_authenticate(hash, existing_account: user2) @@ -50,19 +50,19 @@ RSpec.describe Auth::TwitterAuthenticator do expect(UserAssociatedAccount.exists?(provider_name: "twitter", user_id: user2.id)).to eq(true) end - describe 'revoke' do + describe "revoke" do fab!(:user) { Fabricate(:user) } let(:authenticator) { Auth::TwitterAuthenticator.new } - it 'raises exception if no entry for user' do + it "raises exception if no entry for user" do expect { authenticator.revoke(user) }.to raise_error(Discourse::NotFound) end - it 'revokes correctly' do - UserAssociatedAccount.create!(provider_name: "twitter", user_id: user.id, provider_uid: 100) - expect(authenticator.can_revoke?).to eq(true) - expect(authenticator.revoke(user)).to eq(true) - expect(authenticator.description_for_user(user)).to eq("") - end + it "revokes correctly" do + UserAssociatedAccount.create!(provider_name: "twitter", user_id: user.id, provider_uid: 100) + expect(authenticator.can_revoke?).to eq(true) + expect(authenticator.revoke(user)).to eq(true) + expect(authenticator.description_for_user(user)).to eq("") + end end end diff --git a/spec/lib/backup_restore/backup_file_handler_multisite_spec.rb b/spec/lib/backup_restore/backup_file_handler_multisite_spec.rb index 5a0b1f6de1..40a01e26bc 100644 --- a/spec/lib/backup_restore/backup_file_handler_multisite_spec.rb +++ b/spec/lib/backup_restore/backup_file_handler_multisite_spec.rb @@ -10,7 +10,7 @@ RSpec.describe BackupRestore::BackupFileHandler, type: :multisite do expect_decompress_and_clean_up_to_work( backup_filename: "backup_till_v1.5.tar.gz", require_metadata_file: true, - require_uploads: true + require_uploads: true, ) end end diff --git a/spec/lib/backup_restore/backup_file_handler_spec.rb b/spec/lib/backup_restore/backup_file_handler_spec.rb index 8803de7890..c7f017692e 100644 --- a/spec/lib/backup_restore/backup_file_handler_spec.rb +++ b/spec/lib/backup_restore/backup_file_handler_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'shared_context_for_backup_restore' +require_relative "shared_context_for_backup_restore" RSpec.describe BackupRestore::BackupFileHandler do include_context "with shared stuff" @@ -9,7 +9,7 @@ RSpec.describe BackupRestore::BackupFileHandler do expect_decompress_and_clean_up_to_work( backup_filename: "backup_since_v1.6.tar.gz", require_metadata_file: false, - require_uploads: true + require_uploads: true, ) end @@ -18,7 +18,7 @@ RSpec.describe BackupRestore::BackupFileHandler do backup_filename: "sql_only_backup.sql.gz", expected_dump_filename: "sql_only_backup.sql", require_metadata_file: false, - require_uploads: false + require_uploads: false, ) end @@ -27,11 +27,11 @@ RSpec.describe BackupRestore::BackupFileHandler do backup_filename: "backup_with_wrong_upload_path.tar.gz", require_metadata_file: false, require_uploads: true, - expected_upload_paths: [ - "uploads/default/original/1X/both.txt", - "uploads/default/original/1X/only-uploads.txt", - "uploads/default/original/1X/only-var.txt" - ] + expected_upload_paths: %w[ + uploads/default/original/1X/both.txt + uploads/default/original/1X/only-uploads.txt + uploads/default/original/1X/only-var.txt + ], ) do |upload_path| content = File.read(upload_path).chomp @@ -54,7 +54,7 @@ RSpec.describe BackupRestore::BackupFileHandler do backup_filename: "backup_since_v1.6.tar.gz", require_metadata_file: false, require_uploads: true, - location: BackupLocationSiteSetting::LOCAL + location: BackupLocationSiteSetting::LOCAL, ) end end diff --git a/spec/lib/backup_restore/backuper_spec.rb b/spec/lib/backup_restore/backuper_spec.rb index c66006d631..1578a44f44 100644 --- a/spec/lib/backup_restore/backuper_spec.rb +++ b/spec/lib/backup_restore/backuper_spec.rb @@ -1,55 +1,55 @@ # frozen_string_literal: true RSpec.describe BackupRestore::Backuper do - it 'returns a non-empty parameterized title when site title contains unicode' do - SiteSetting.title = 'Ɣ' + it "returns a non-empty parameterized title when site title contains unicode" do + SiteSetting.title = "Ɣ" backuper = BackupRestore::Backuper.new(Discourse.system_user.id) expect(backuper.send(:get_parameterized_title)).to eq("discourse") end - it 'returns a valid parameterized site title' do + it "returns a valid parameterized site title" do SiteSetting.title = "Coding Horror" backuper = BackupRestore::Backuper.new(Discourse.system_user.id) expect(backuper.send(:get_parameterized_title)).to eq("coding-horror") end - describe '#notify_user' do - before do - freeze_time Time.zone.parse('2010-01-01 12:00') - end + describe "#notify_user" do + before { freeze_time Time.zone.parse("2010-01-01 12:00") } - it 'includes logs if short' do + it "includes logs if short" do SiteSetting.max_export_file_size_kb = 1 SiteSetting.export_authorized_extensions = "tar.gz" silence_stdout do backuper = BackupRestore::Backuper.new(Discourse.system_user.id) - expect { backuper.send(:notify_user) } - .to change { Topic.private_messages.count }.by(1) - .and not_change { Upload.count } + expect { backuper.send(:notify_user) }.to change { Topic.private_messages.count }.by( + 1, + ).and not_change { Upload.count } end - expect(Topic.last.first_post.raw).to include("```text\n[2010-01-01 12:00:00] Notifying 'system' of the end of the backup...\n```") + expect(Topic.last.first_post.raw).to include( + "```text\n[2010-01-01 12:00:00] Notifying 'system' of the end of the backup...\n```", + ) end - it 'include upload if log is long' do + it "include upload if log is long" do SiteSetting.max_post_length = 250 silence_stdout do backuper = BackupRestore::Backuper.new(Discourse.system_user.id) - expect { backuper.send(:notify_user) } - .to change { Topic.private_messages.count }.by(1) - .and change { Upload.where(original_filename: "log.txt.zip").count }.by(1) + expect { backuper.send(:notify_user) }.to change { Topic.private_messages.count }.by( + 1, + ).and change { Upload.where(original_filename: "log.txt.zip").count }.by(1) end expect(Topic.last.first_post.raw).to include("[log.txt.zip|attachment]") end - it 'includes trimmed logs if log is long and upload cannot be saved' do + it "includes trimmed logs if log is long and upload cannot be saved" do SiteSetting.max_post_length = 348 SiteSetting.max_export_file_size_kb = 1 SiteSetting.export_authorized_extensions = "tar.gz" @@ -57,16 +57,16 @@ RSpec.describe BackupRestore::Backuper do silence_stdout do backuper = BackupRestore::Backuper.new(Discourse.system_user.id) - 1.upto(10).each do |i| - backuper.send(:log, "Line #{i}") - end + 1.upto(10).each { |i| backuper.send(:log, "Line #{i}") } - expect { backuper.send(:notify_user) } - .to change { Topic.private_messages.count }.by(1) - .and not_change { Upload.count } + expect { backuper.send(:notify_user) }.to change { Topic.private_messages.count }.by( + 1, + ).and not_change { Upload.count } end - expect(Topic.last.first_post.raw).to include("```text\n...\n[2010-01-01 12:00:00] Line 10\n[2010-01-01 12:00:00] Notifying 'system' of the end of the backup...\n```") + expect(Topic.last.first_post.raw).to include( + "```text\n...\n[2010-01-01 12:00:00] Line 10\n[2010-01-01 12:00:00] Notifying 'system' of the end of the backup...\n```", + ) end end end diff --git a/spec/lib/backup_restore/database_restorer_spec.rb b/spec/lib/backup_restore/database_restorer_spec.rb index e8d69c0723..afa4bea4f3 100644 --- a/spec/lib/backup_restore/database_restorer_spec.rb +++ b/spec/lib/backup_restore/database_restorer_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'shared_context_for_backup_restore' +require_relative "shared_context_for_backup_restore" RSpec.describe BackupRestore::DatabaseRestorer do include_context "with shared stuff" @@ -32,7 +32,7 @@ RSpec.describe BackupRestore::DatabaseRestorer do context "with real psql" do after do psql = BackupRestore::DatabaseRestorer.psql_command - system("#{psql} -c 'DROP TABLE IF EXISTS foo'", [:out, :err] => File::NULL) + system("#{psql} -c 'DROP TABLE IF EXISTS foo'", %i[out err] => File::NULL) end def restore(filename, stub_migrate: true) @@ -74,24 +74,27 @@ RSpec.describe BackupRestore::DatabaseRestorer do end it "detects error during restore" do - expect { restore("error.sql", stub_migrate: false) } - .to raise_error(BackupRestore::DatabaseRestoreError) + expect { restore("error.sql", stub_migrate: false) }.to raise_error( + BackupRestore::DatabaseRestoreError, + ) end end describe "rewrites database dump" do let(:logger) do - Class.new do - attr_reader :log_messages + Class + .new do + attr_reader :log_messages - def initialize - @log_messages = [] - end + def initialize + @log_messages = [] + end - def log(message, ex = nil) - @log_messages << message if message + def log(message, ex = nil) + @log_messages << message if message + end end - end.new + .new end def restore_and_log_output(filename) @@ -108,8 +111,12 @@ RSpec.describe BackupRestore::DatabaseRestorer do expect(log).not_to be_blank expect(log).not_to match(/CREATE SCHEMA public/) expect(log).not_to match(/EXECUTE FUNCTION/) - expect(log).to match(/^CREATE TRIGGER foo_topic_id_readonly .+? EXECUTE PROCEDURE discourse_functions.raise_foo_topic_id_readonly/) - expect(log).to match(/^CREATE TRIGGER foo_user_id_readonly .+? EXECUTE PROCEDURE discourse_functions.raise_foo_user_id_readonly/) + expect(log).to match( + /^CREATE TRIGGER foo_topic_id_readonly .+? EXECUTE PROCEDURE discourse_functions.raise_foo_topic_id_readonly/, + ) + expect(log).to match( + /^CREATE TRIGGER foo_user_id_readonly .+? EXECUTE PROCEDURE discourse_functions.raise_foo_user_id_readonly/, + ) end it "does not replace `EXECUTE FUNCTION` when restoring on PostgreSQL >= 11" do @@ -119,13 +126,17 @@ RSpec.describe BackupRestore::DatabaseRestorer do expect(log).not_to be_blank expect(log).not_to match(/CREATE SCHEMA public/) expect(log).not_to match(/EXECUTE PROCEDURE/) - expect(log).to match(/^CREATE TRIGGER foo_topic_id_readonly .+? EXECUTE FUNCTION discourse_functions.raise_foo_topic_id_readonly/) - expect(log).to match(/^CREATE TRIGGER foo_user_id_readonly .+? EXECUTE FUNCTION discourse_functions.raise_foo_user_id_readonly/) + expect(log).to match( + /^CREATE TRIGGER foo_topic_id_readonly .+? EXECUTE FUNCTION discourse_functions.raise_foo_topic_id_readonly/, + ) + expect(log).to match( + /^CREATE TRIGGER foo_user_id_readonly .+? EXECUTE FUNCTION discourse_functions.raise_foo_user_id_readonly/, + ) end end describe "database connection" do - it 'it is not erroring for non-multisite' do + it "it is not erroring for non-multisite" do expect { execute_stubbed_restore }.not_to raise_error end end @@ -147,7 +158,7 @@ RSpec.describe BackupRestore::DatabaseRestorer do describe "readonly functions" do before do BackupRestore::DatabaseRestorer.stubs(:core_migration_files).returns( - Dir[Rails.root.join("spec/fixtures/db/post_migrate/drop_column/**/*.rb")] + Dir[Rails.root.join("spec/fixtures/db/post_migrate/drop_column/**/*.rb")], ) end @@ -167,8 +178,9 @@ RSpec.describe BackupRestore::DatabaseRestorer do end it "creates and drops only missing functions during restore" do - Migration::BaseDropper.stubs(:existing_discourse_function_names) - .returns(%w(raise_email_logs_readonly raise_posts_raw_email_readonly)) + Migration::BaseDropper.stubs(:existing_discourse_function_names).returns( + %w[raise_email_logs_readonly raise_posts_raw_email_readonly], + ) Migration::BaseDropper.expects(:create_readonly_function).with(:posts, :via_email) execute_stubbed_restore(stub_readonly_functions: false) @@ -191,9 +203,7 @@ RSpec.describe BackupRestore::DatabaseRestorer do end context "when a backup schema exists" do - before do - ActiveRecord::Base.connection.expects(:schema_exists?).with("backup").returns(true) - end + before { ActiveRecord::Base.connection.expects(:schema_exists?).with("backup").returns(true) } it "drops the schema when the last restore was long ago" do ActiveRecord::Base.connection.expects(:drop_schema).with("backup") diff --git a/spec/lib/backup_restore/local_backup_store_spec.rb b/spec/lib/backup_restore/local_backup_store_spec.rb index faa01c4f16..345fb4e4d1 100644 --- a/spec/lib/backup_restore/local_backup_store_spec.rb +++ b/spec/lib/backup_restore/local_backup_store_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'backup_restore/local_backup_store' -require_relative 'shared_examples_for_backup_store' +require "backup_restore/local_backup_store" +require_relative "shared_examples_for_backup_store" RSpec.describe BackupRestore::LocalBackupStore do before do @@ -10,9 +10,7 @@ RSpec.describe BackupRestore::LocalBackupStore do SiteSetting.backup_location = BackupLocationSiteSetting::LOCAL end - after do - FileUtils.remove_dir(@root_directory, true) - end + after { FileUtils.remove_dir(@root_directory, true) } subject(:store) { BackupRestore::BackupStore.create(root_directory: @root_directory) } let(:expected_type) { BackupRestore::LocalBackupStore } @@ -24,14 +22,49 @@ RSpec.describe BackupRestore::LocalBackupStore do end def create_backups - create_file(db_name: "default", filename: "b.tar.gz", last_modified: "2018-09-13T15:10:00Z", size_in_bytes: 17) - create_file(db_name: "default", filename: "a.tgz", last_modified: "2018-02-11T09:27:00Z", size_in_bytes: 29) - create_file(db_name: "default", filename: "r.sql.gz", last_modified: "2017-12-20T03:48:00Z", size_in_bytes: 11) - create_file(db_name: "default", filename: "no-backup.txt", last_modified: "2018-09-05T14:27:00Z", size_in_bytes: 12) - create_file(db_name: "default/subfolder", filename: "c.tar.gz", last_modified: "2019-01-24T18:44:00Z", size_in_bytes: 23) + create_file( + db_name: "default", + filename: "b.tar.gz", + last_modified: "2018-09-13T15:10:00Z", + size_in_bytes: 17, + ) + create_file( + db_name: "default", + filename: "a.tgz", + last_modified: "2018-02-11T09:27:00Z", + size_in_bytes: 29, + ) + create_file( + db_name: "default", + filename: "r.sql.gz", + last_modified: "2017-12-20T03:48:00Z", + size_in_bytes: 11, + ) + create_file( + db_name: "default", + filename: "no-backup.txt", + last_modified: "2018-09-05T14:27:00Z", + size_in_bytes: 12, + ) + create_file( + db_name: "default/subfolder", + filename: "c.tar.gz", + last_modified: "2019-01-24T18:44:00Z", + size_in_bytes: 23, + ) - create_file(db_name: "second", filename: "multi-2.tar.gz", last_modified: "2018-11-27T03:16:54Z", size_in_bytes: 19) - create_file(db_name: "second", filename: "multi-1.tar.gz", last_modified: "2018-11-26T03:17:09Z", size_in_bytes: 22) + create_file( + db_name: "second", + filename: "multi-2.tar.gz", + last_modified: "2018-11-27T03:16:54Z", + size_in_bytes: 19, + ) + create_file( + db_name: "second", + filename: "multi-1.tar.gz", + last_modified: "2018-11-26T03:17:09Z", + size_in_bytes: 22, + ) end def remove_backups diff --git a/spec/lib/backup_restore/meta_data_handler_spec.rb b/spec/lib/backup_restore/meta_data_handler_spec.rb index 9dd08f0e82..70f062e89d 100644 --- a/spec/lib/backup_restore/meta_data_handler_spec.rb +++ b/spec/lib/backup_restore/meta_data_handler_spec.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require_relative 'shared_context_for_backup_restore' +require_relative "shared_context_for_backup_restore" RSpec.describe BackupRestore::MetaDataHandler do include_context "with shared stuff" - let!(:backup_filename) { 'discourse-2019-11-18-143242-v20191108000414.tar.gz' } + let!(:backup_filename) { "discourse-2019-11-18-143242-v20191108000414.tar.gz" } def with_metadata_file(content) Dir.mktmpdir do |directory| @@ -27,8 +27,7 @@ RSpec.describe BackupRestore::MetaDataHandler do metadata = '{"source":"discourse","version":20160329101122}' with_metadata_file(metadata) do |dir| - expect(validate_metadata(backup_filename, dir)) - .to include(version: 20160329101122) + expect(validate_metadata(backup_filename, dir)).to include(version: 20_160_329_101_122) end end @@ -36,15 +35,17 @@ RSpec.describe BackupRestore::MetaDataHandler do corrupt_metadata = '{"version":20160329101122' with_metadata_file(corrupt_metadata) do |dir| - expect { validate_metadata(backup_filename, dir) } - .to raise_error(BackupRestore::MetaDataError) + expect { validate_metadata(backup_filename, dir) }.to raise_error( + BackupRestore::MetaDataError, + ) end end it "raises an exception when the metadata file is empty" do - with_metadata_file('') do |dir| - expect { validate_metadata(backup_filename, dir) } - .to raise_error(BackupRestore::MetaDataError) + with_metadata_file("") do |dir| + expect { validate_metadata(backup_filename, dir) }.to raise_error( + BackupRestore::MetaDataError, + ) end end @@ -52,8 +53,9 @@ RSpec.describe BackupRestore::MetaDataHandler do metadata = '{"source":"discourse","version":"1abcdefghijklm"}' with_metadata_file(metadata) do |dir| - expect { validate_metadata(backup_filename, dir) } - .to raise_error(BackupRestore::MetaDataError) + expect { validate_metadata(backup_filename, dir) }.to raise_error( + BackupRestore::MetaDataError, + ) end end @@ -61,8 +63,9 @@ RSpec.describe BackupRestore::MetaDataHandler do metadata = '{"source":"discourse","version":""}' with_metadata_file(metadata) do |dir| - expect { validate_metadata(backup_filename, dir) } - .to raise_error(BackupRestore::MetaDataError) + expect { validate_metadata(backup_filename, dir) }.to raise_error( + BackupRestore::MetaDataError, + ) end end end @@ -70,36 +73,32 @@ RSpec.describe BackupRestore::MetaDataHandler do describe "filename" do it "extracts metadata from filename when metadata file does not exist" do with_metadata_file(nil) do |dir| - expect(validate_metadata(backup_filename, dir)) - .to include(version: 20191108000414) + expect(validate_metadata(backup_filename, dir)).to include(version: 20_191_108_000_414) end end it "raises an exception when the filename contains no version number" do - filename = 'discourse-2019-11-18-143242.tar.gz' + filename = "discourse-2019-11-18-143242.tar.gz" - expect { validate_metadata(filename, nil) } - .to raise_error(BackupRestore::MetaDataError) + expect { validate_metadata(filename, nil) }.to raise_error(BackupRestore::MetaDataError) end it "raises an exception when the filename contains an invalid version number" do - filename = 'discourse-2019-11-18-143242-v123456789.tar.gz' - expect { validate_metadata(filename, nil) } - .to raise_error(BackupRestore::MetaDataError) + filename = "discourse-2019-11-18-143242-v123456789.tar.gz" + expect { validate_metadata(filename, nil) }.to raise_error(BackupRestore::MetaDataError) - filename = 'discourse-2019-11-18-143242-v1abcdefghijklm.tar.gz' - expect { validate_metadata(filename, nil) } - .to raise_error(BackupRestore::MetaDataError) + filename = "discourse-2019-11-18-143242-v1abcdefghijklm.tar.gz" + expect { validate_metadata(filename, nil) }.to raise_error(BackupRestore::MetaDataError) end end it "raises an exception when the backup's version is newer than the current version" do - new_backup_filename = 'discourse-2019-11-18-143242-v20191113193141.sql.gz' + new_backup_filename = "discourse-2019-11-18-143242-v20191113193141.sql.gz" - BackupRestore.expects(:current_version) - .returns(20191025005204).once + BackupRestore.expects(:current_version).returns(20_191_025_005_204).once - expect { validate_metadata(new_backup_filename, nil) } - .to raise_error(BackupRestore::MigrationRequiredError) + expect { validate_metadata(new_backup_filename, nil) }.to raise_error( + BackupRestore::MigrationRequiredError, + ) end end diff --git a/spec/lib/backup_restore/s3_backup_store_spec.rb b/spec/lib/backup_restore/s3_backup_store_spec.rb index 4aad803cfd..7989fbaa4d 100644 --- a/spec/lib/backup_restore/s3_backup_store_spec.rb +++ b/spec/lib/backup_restore/s3_backup_store_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 's3_helper' -require 'backup_restore/s3_backup_store' -require_relative 'shared_examples_for_backup_store' +require "s3_helper" +require "backup_restore/s3_backup_store" +require_relative "shared_examples_for_backup_store" RSpec.describe BackupRestore::S3BackupStore do before do @@ -21,49 +21,64 @@ RSpec.describe BackupRestore::S3BackupStore do expect(context.params[:prefix]).to eq(expected_prefix) if context.params.key?(:prefix) end - @s3_client.stub_responses(:list_objects_v2, -> (context) do - check_context(context) + @s3_client.stub_responses( + :list_objects_v2, + ->(context) { + check_context(context) - { contents: objects_with_prefix(context) } - end) + { contents: objects_with_prefix(context) } + }, + ) - @s3_client.stub_responses(:delete_object, -> (context) do - check_context(context) + @s3_client.stub_responses( + :delete_object, + ->(context) { + check_context(context) - expect do - @objects.delete_if { |obj| obj[:key] == context.params[:key] } - end.to change { @objects } - end) + expect do @objects.delete_if { |obj| obj[:key] == context.params[:key] } end.to change { + @objects + } + }, + ) - @s3_client.stub_responses(:head_object, -> (context) do - check_context(context) + @s3_client.stub_responses( + :head_object, + ->(context) { + check_context(context) - if object = @objects.find { |obj| obj[:key] == context.params[:key] } - { content_length: object[:size], last_modified: object[:last_modified] } - else - { status_code: 404, headers: {}, body: "", } - end - end) + if object = @objects.find { |obj| obj[:key] == context.params[:key] } + { content_length: object[:size], last_modified: object[:last_modified] } + else + { status_code: 404, headers: {}, body: "" } + end + }, + ) - @s3_client.stub_responses(:get_object, -> (context) do - check_context(context) + @s3_client.stub_responses( + :get_object, + ->(context) { + check_context(context) - if object = @objects.find { |obj| obj[:key] == context.params[:key] } - { content_length: object[:size], body: "A" * object[:size] } - else - { status_code: 404, headers: {}, body: "", } - end - end) + if object = @objects.find { |obj| obj[:key] == context.params[:key] } + { content_length: object[:size], body: "A" * object[:size] } + else + { status_code: 404, headers: {}, body: "" } + end + }, + ) - @s3_client.stub_responses(:put_object, -> (context) do - check_context(context) + @s3_client.stub_responses( + :put_object, + ->(context) { + check_context(context) - @objects << { - key: context.params[:key], - size: context.params[:body].size, - last_modified: Time.zone.now - } - end) + @objects << { + key: context.params[:key], + size: context.params[:body].size, + last_modified: Time.zone.now, + } + }, + ) SiteSetting.s3_backup_bucket = "s3-backup-bucket" SiteSetting.s3_access_key_id = "s3-access-key-id" @@ -105,15 +120,47 @@ RSpec.describe BackupRestore::S3BackupStore do def create_backups @objects.clear - @objects << { key: "default/b.tar.gz", size: 17, last_modified: Time.parse("2018-09-13T15:10:00Z") } - @objects << { key: "default/a.tgz", size: 29, last_modified: Time.parse("2018-02-11T09:27:00Z") } - @objects << { key: "default/r.sql.gz", size: 11, last_modified: Time.parse("2017-12-20T03:48:00Z") } - @objects << { key: "default/no-backup.txt", size: 12, last_modified: Time.parse("2018-09-05T14:27:00Z") } - @objects << { key: "default/subfolder/c.tar.gz", size: 23, last_modified: Time.parse("2019-01-24T18:44:00Z") } + @objects << { + key: "default/b.tar.gz", + size: 17, + last_modified: Time.parse("2018-09-13T15:10:00Z"), + } + @objects << { + key: "default/a.tgz", + size: 29, + last_modified: Time.parse("2018-02-11T09:27:00Z"), + } + @objects << { + key: "default/r.sql.gz", + size: 11, + last_modified: Time.parse("2017-12-20T03:48:00Z"), + } + @objects << { + key: "default/no-backup.txt", + size: 12, + last_modified: Time.parse("2018-09-05T14:27:00Z"), + } + @objects << { + key: "default/subfolder/c.tar.gz", + size: 23, + last_modified: Time.parse("2019-01-24T18:44:00Z"), + } - @objects << { key: "second/multi-2.tar.gz", size: 19, last_modified: Time.parse("2018-11-27T03:16:54Z") } - @objects << { key: "second/multi-1.tar.gz", size: 22, last_modified: Time.parse("2018-11-26T03:17:09Z") } - @objects << { key: "second/subfolder/multi-3.tar.gz", size: 23, last_modified: Time.parse("2019-01-24T18:44:00Z") } + @objects << { + key: "second/multi-2.tar.gz", + size: 19, + last_modified: Time.parse("2018-11-27T03:16:54Z"), + } + @objects << { + key: "second/multi-1.tar.gz", + size: 22, + last_modified: Time.parse("2018-11-26T03:17:09Z"), + } + @objects << { + key: "second/subfolder/multi-3.tar.gz", + size: 23, + last_modified: Time.parse("2019-01-24T18:44:00Z"), + } end def remove_backups @@ -126,7 +173,7 @@ RSpec.describe BackupRestore::S3BackupStore do filename = Regexp.escape(filename) expires = SiteSetting.s3_presigned_get_url_expires_after_seconds - /\Ahttps:\/\/#{bucket}.*#{prefix}\/#{filename}\?.*X-Amz-Expires=#{expires}.*X-Amz-Signature=.*\z/ + %r{\Ahttps://#{bucket}.*#{prefix}/#{filename}\?.*X-Amz-Expires=#{expires}.*X-Amz-Signature=.*\z} end def upload_url_regex(db_name, filename, multisite:) @@ -135,7 +182,7 @@ RSpec.describe BackupRestore::S3BackupStore do filename = Regexp.escape(filename) expires = BackupRestore::S3BackupStore::UPLOAD_URL_EXPIRES_AFTER_SECONDS - /\Ahttps:\/\/#{bucket}.*#{prefix}\/#{filename}\?.*X-Amz-Expires=#{expires}.*X-Amz-Signature=.*\z/ + %r{\Ahttps://#{bucket}.*#{prefix}/#{filename}\?.*X-Amz-Expires=#{expires}.*X-Amz-Signature=.*\z} end def file_prefix(db_name, multisite) diff --git a/spec/lib/backup_restore/shared_context_for_backup_restore.rb b/spec/lib/backup_restore/shared_context_for_backup_restore.rb index 5ad59e604f..402a774993 100644 --- a/spec/lib/backup_restore/shared_context_for_backup_restore.rb +++ b/spec/lib/backup_restore/shared_context_for_backup_restore.rb @@ -2,9 +2,12 @@ RSpec.shared_context "with shared stuff" do let!(:logger) do - Class.new do - def log(message, ex = nil); end - end.new + Class + .new do + def log(message, ex = nil) + end + end + .new end def expect_create_readonly_functions @@ -33,13 +36,14 @@ RSpec.shared_context "with shared stuff" do end def expect_db_migrate - Discourse::Utils.expects(:execute_command).with do |env, *command, options| - env["SKIP_POST_DEPLOYMENT_MIGRATIONS"] == "0" && - env["SKIP_OPTIMIZE_ICONS"] == "1" && - env["DISABLE_TRANSLATION_OVERRIDES"] == "1" && - command == ["rake", "db:migrate"] && - options[:chdir] == Rails.root - end.once + Discourse::Utils + .expects(:execute_command) + .with do |env, *command, options| + env["SKIP_POST_DEPLOYMENT_MIGRATIONS"] == "0" && env["SKIP_OPTIMIZE_ICONS"] == "1" && + env["DISABLE_TRANSLATION_OVERRIDES"] == "1" && command == %w[rake db:migrate] && + options[:chdir] == Rails.root + end + .once end def expect_db_reconnect @@ -76,11 +80,14 @@ RSpec.shared_context "with shared stuff" do Dir.mktmpdir do |root_directory| current_db = RailsMultisite::ConnectionManagement.current_db - file_handler = BackupRestore::BackupFileHandler.new( - logger, backup_filename, current_db, - root_tmp_directory: root_directory, - location: location - ) + file_handler = + BackupRestore::BackupFileHandler.new( + logger, + backup_filename, + current_db, + root_tmp_directory: root_directory, + location: location, + ) tmp_directory, db_dump_path = file_handler.decompress expected_tmp_path = File.join(root_directory, "tmp/restores", current_db, "2019-12-24-143148") @@ -93,11 +100,14 @@ RSpec.shared_context "with shared stuff" do expect(File.exist?(File.join(tmp_directory, "meta.json"))).to eq(require_metadata_file) if require_uploads - expected_upload_paths ||= ["uploads/default/original/3X/b/d/bd269860bb508aebcb6f08fe7289d5f117830383.png"] + expected_upload_paths ||= [ + "uploads/default/original/3X/b/d/bd269860bb508aebcb6f08fe7289d5f117830383.png", + ] expected_upload_paths.each do |upload_path| absolute_upload_path = File.join(tmp_directory, upload_path) - expect(File.exist?(absolute_upload_path)).to eq(true), "expected file #{upload_path} does not exist" + expect(File.exist?(absolute_upload_path)).to eq(true), + "expected file #{upload_path} does not exist" yield(absolute_upload_path) if block_given? end else @@ -112,6 +122,10 @@ RSpec.shared_context "with shared stuff" do # We don't want to delete the directory unless it is empty, otherwise this could be annoying # when tests run for the "default" database in a development environment. - FileUtils.rmdir(target_directory) rescue nil + begin + FileUtils.rmdir(target_directory) + rescue StandardError + nil + end end end diff --git a/spec/lib/backup_restore/shared_examples_for_backup_store.rb b/spec/lib/backup_restore/shared_examples_for_backup_store.rb index eeb8ee2e05..a35456a83c 100644 --- a/spec/lib/backup_restore/shared_examples_for_backup_store.rb +++ b/spec/lib/backup_restore/shared_examples_for_backup_store.rb @@ -6,13 +6,39 @@ RSpec.shared_context "with backups" do after { remove_backups } # default backup files - let(:backup1) { BackupFile.new(filename: "b.tar.gz", size: 17, last_modified: Time.parse("2018-09-13T15:10:00Z")) } - let(:backup2) { BackupFile.new(filename: "a.tgz", size: 29, last_modified: Time.parse("2018-02-11T09:27:00Z")) } - let(:backup3) { BackupFile.new(filename: "r.sql.gz", size: 11, last_modified: Time.parse("2017-12-20T03:48:00Z")) } + let(:backup1) do + BackupFile.new( + filename: "b.tar.gz", + size: 17, + last_modified: Time.parse("2018-09-13T15:10:00Z"), + ) + end + let(:backup2) do + BackupFile.new(filename: "a.tgz", size: 29, last_modified: Time.parse("2018-02-11T09:27:00Z")) + end + let(:backup3) do + BackupFile.new( + filename: "r.sql.gz", + size: 11, + last_modified: Time.parse("2017-12-20T03:48:00Z"), + ) + end # backup files on another multisite - let(:backup4) { BackupFile.new(filename: "multi-1.tar.gz", size: 22, last_modified: Time.parse("2018-11-26T03:17:09Z")) } - let(:backup5) { BackupFile.new(filename: "multi-2.tar.gz", size: 19, last_modified: Time.parse("2018-11-27T03:16:54Z")) } + let(:backup4) do + BackupFile.new( + filename: "multi-1.tar.gz", + size: 22, + last_modified: Time.parse("2018-11-26T03:17:09Z"), + ) + end + let(:backup5) do + BackupFile.new( + filename: "multi-2.tar.gz", + size: 19, + last_modified: Time.parse("2018-11-27T03:16:54Z"), + ) + end end RSpec.shared_examples "backup store" do @@ -56,13 +82,15 @@ RSpec.shared_examples "backup store" do it "returns only *.gz and *.tgz files" do files = store.files expect(files).to_not be_empty - expect(files.map(&:filename)).to contain_exactly(backup1.filename, backup2.filename, backup3.filename) + expect(files.map(&:filename)).to contain_exactly( + backup1.filename, + backup2.filename, + backup3.filename, + ) end it "works with multisite", type: :multisite do - test_multisite_connection("second") do - expect(store.files).to eq([backup5, backup4]) - end + test_multisite_connection("second") { expect(store.files).to eq([backup5, backup4]) } end end @@ -77,9 +105,7 @@ RSpec.shared_examples "backup store" do end it "works with multisite", type: :multisite do - test_multisite_connection("second") do - expect(store.latest_file).to eq(backup5) - end + test_multisite_connection("second") { expect(store.latest_file).to eq(backup5) } end end @@ -222,11 +248,7 @@ RSpec.shared_examples "remote backup store" do # to that freeze_time(Time.now.to_s) - backup = BackupFile.new( - filename: "foo.tar.gz", - size: 33, - last_modified: Time.zone.now - ) + backup = BackupFile.new(filename: "foo.tar.gz", size: 33, last_modified: Time.zone.now) expect(store.files).to_not include(backup) @@ -246,15 +268,14 @@ RSpec.shared_examples "remote backup store" do end it "works with multisite", type: :multisite do - test_multisite_connection("second") do - upload_file - end + test_multisite_connection("second") { upload_file } end it "raises an exception when a file with same filename exists" do Tempfile.create(backup1.filename) do |file| - expect { store.upload_file(backup1.filename, file.path, "application/gzip") } - .to raise_exception(BackupRestore::BackupStore::BackupFileExists) + expect { + store.upload_file(backup1.filename, file.path, "application/gzip") + }.to raise_exception(BackupRestore::BackupStore::BackupFileExists) end end end @@ -268,8 +289,9 @@ RSpec.shared_examples "remote backup store" do end it "raises an exception when a file with same filename exists" do - expect { store.generate_upload_url(backup1.filename) } - .to raise_exception(BackupRestore::BackupStore::BackupFileExists) + expect { store.generate_upload_url(backup1.filename) }.to raise_exception( + BackupRestore::BackupStore::BackupFileExists, + ) end it "works with multisite", type: :multisite do diff --git a/spec/lib/backup_restore/system_interface_spec.rb b/spec/lib/backup_restore/system_interface_spec.rb index 20a701d8d3..8f1461cc17 100644 --- a/spec/lib/backup_restore/system_interface_spec.rb +++ b/spec/lib/backup_restore/system_interface_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative 'shared_context_for_backup_restore' +require_relative "shared_context_for_backup_restore" RSpec.describe BackupRestore::SystemInterface do include_context "with shared stuff" @@ -8,9 +8,7 @@ RSpec.describe BackupRestore::SystemInterface do subject { BackupRestore::SystemInterface.new(logger) } describe "readonly mode" do - after do - Discourse::READONLY_KEYS.each { |key| Discourse.redis.del(key) } - end + after { Discourse::READONLY_KEYS.each { |key| Discourse.redis.del(key) } } describe "#enable_readonly_mode" do it "enables readonly mode" do @@ -107,45 +105,43 @@ RSpec.describe BackupRestore::SystemInterface do end context "with Sidekiq workers" do - after do - flush_sidekiq_redis_namespace - end + after { flush_sidekiq_redis_namespace } def flush_sidekiq_redis_namespace - Sidekiq.redis do |redis| - redis.scan_each { |key| redis.del(key) } - end + Sidekiq.redis { |redis| redis.scan_each { |key| redis.del(key) } } end def create_workers(site_id: nil, all_sites: false) - payload = Sidekiq::Testing.fake! do - data = { post_id: 1 } + payload = + Sidekiq::Testing.fake! do + data = { post_id: 1 } - if all_sites - data[:all_sites] = true - else - data[:current_site_id] = site_id || RailsMultisite::ConnectionManagement.current_db + if all_sites + data[:all_sites] = true + else + data[:current_site_id] = site_id || RailsMultisite::ConnectionManagement.current_db + end + + Jobs.enqueue(:process_post, data) + Jobs::ProcessPost.jobs.last end - Jobs.enqueue(:process_post, data) - Jobs::ProcessPost.jobs.last - end - Sidekiq.redis do |conn| hostname = "localhost" pid = 7890 key = "#{hostname}:#{pid}" process = { pid: pid, hostname: hostname } - conn.sadd('processes', key) - conn.hmset(key, 'info', Sidekiq.dump_json(process)) + conn.sadd("processes", key) + conn.hmset(key, "info", Sidekiq.dump_json(process)) - data = Sidekiq.dump_json( - queue: 'default', - run_at: Time.now.to_i, - payload: Sidekiq.dump_json(payload) - ) - conn.hmset("#{key}:work", '444', data) + data = + Sidekiq.dump_json( + queue: "default", + run_at: Time.now.to_i, + payload: Sidekiq.dump_json(payload), + ) + conn.hmset("#{key}:work", "444", data) end end diff --git a/spec/lib/backup_restore/uploads_restorer_spec.rb b/spec/lib/backup_restore/uploads_restorer_spec.rb index 3314358e9e..ff00811c9a 100644 --- a/spec/lib/backup_restore/uploads_restorer_spec.rb +++ b/spec/lib/backup_restore/uploads_restorer_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true # rubocop:disable Discourse/OnlyTopLevelMultisiteSpecs -require_relative 'shared_context_for_backup_restore' +require_relative "shared_context_for_backup_restore" RSpec.describe BackupRestore::UploadsRestorer do include_context "with shared stuff" @@ -21,11 +21,19 @@ RSpec.describe BackupRestore::UploadsRestorer do expect_remaps( source_site_name: source_site_name, target_site_name: target_site_name, - metadata: metadata + metadata: metadata, ) end - def expect_remap(source_site_name: nil, target_site_name:, metadata: [], from:, to:, regex: false, &block) + def expect_remap( + source_site_name: nil, + target_site_name:, + metadata: [], + from:, + to:, + regex: false, + &block + ) expect_remaps( source_site_name: source_site_name, target_site_name: target_site_name, @@ -54,19 +62,25 @@ RSpec.describe BackupRestore::UploadsRestorer do if remaps.blank? DbHelper.expects(:remap).never else - DbHelper.expects(:remap).with do |from, to, args| - args[:excluded_tables]&.include?("backup_metadata") - remaps.shift == { from: from, to: to } - end.times(remaps.size) + DbHelper + .expects(:remap) + .with do |from, to, args| + args[:excluded_tables]&.include?("backup_metadata") + remaps.shift == { from: from, to: to } + end + .times(remaps.size) end if regex_remaps.blank? DbHelper.expects(:regexp_replace).never else - DbHelper.expects(:regexp_replace).with do |from, to, args| - args[:excluded_tables]&.include?("backup_metadata") - regex_remaps.shift == { from: from, to: to } - end.times(regex_remaps.size) + DbHelper + .expects(:regexp_replace) + .with do |from, to, args| + args[:excluded_tables]&.include?("backup_metadata") + regex_remaps.shift == { from: from, to: to } + end + .times(regex_remaps.size) end if target_site_name == "default" @@ -85,13 +99,14 @@ RSpec.describe BackupRestore::UploadsRestorer do def uploads_path(database) path = File.join("uploads", database) - path = File.join(path, "test_#{ENV['TEST_ENV_NUMBER'].presence || '0'}") + path = File.join(path, "test_#{ENV["TEST_ENV_NUMBER"].presence || "0"}") "/#{path}/" end def s3_url_regex(bucket, path) - Regexp.escape("//#{bucket}") + %q*\.s3(?:\.dualstack\.[a-z0-9\-]+?|[.\-][a-z0-9\-]+?)?\.amazonaws\.com* + Regexp.escape(path) + Regexp.escape("//#{bucket}") + + %q*\.s3(?:\.dualstack\.[a-z0-9\-]+?|[.\-][a-z0-9\-]+?)?\.amazonaws\.com* + Regexp.escape(path) end describe "uploads" do @@ -99,8 +114,8 @@ RSpec.describe BackupRestore::UploadsRestorer do let!(:no_multisite) { { name: "multisite", value: false } } let!(:source_db_name) { { name: "db_name", value: "foo" } } let!(:base_url) { { name: "base_url", value: "https://test.localhost/forum" } } - let!(:no_cdn_url) { { name: "cdn_url", value: nil } } - let!(:cdn_url) { { name: "cdn_url", value: "https://some-cdn.example.com" } } + let!(:no_cdn_url) { { name: "cdn_url", value: nil } } + let!(:cdn_url) { { name: "cdn_url", value: "https://some-cdn.example.com" } } let(:target_site_name) { target_site_type == multisite ? "second" : "default" } let(:target_hostname) { target_site_type == multisite ? "test2.localhost" : "test.localhost" } @@ -127,7 +142,7 @@ RSpec.describe BackupRestore::UploadsRestorer do source_site_name: "foo", target_site_name: "default", from: "/uploads/foo/", - to: uploads_path("default") + to: uploads_path("default"), ) end end @@ -142,13 +157,16 @@ RSpec.describe BackupRestore::UploadsRestorer do post.link_post_uploads FileHelper.stubs(:download).returns(file_from_fixtures("logo.png")) - FileStore::S3Store.any_instance.stubs(:store_upload).returns do - File.join( - "//s3-upload-bucket.s3.dualstack.us-east-1.amazonaws.com", - target_site_type == multisite ? "/uploads/#{target_site_name}" : "", - "original/1X/bc975735dfc6409c1c2aa5ebf2239949bcbdbd65.png" - ) - end + FileStore::S3Store + .any_instance + .stubs(:store_upload) + .returns do + File.join( + "//s3-upload-bucket.s3.dualstack.us-east-1.amazonaws.com", + target_site_type == multisite ? "/uploads/#{target_site_name}" : "", + "original/1X/bc975735dfc6409c1c2aa5ebf2239949bcbdbd65.png", + ) + end UserAvatar.import_url_for_user("logo.png", Fabricate(:user)) end @@ -158,10 +176,11 @@ RSpec.describe BackupRestore::UploadsRestorer do with_temp_uploads_directory do |directory, path| store_class.any_instance.expects(:copy_from).with(path).once - expect { subject.restore(directory) } - .to change { OptimizedImage.count }.by_at_most(-1) - .and change { Jobs::CreateAvatarThumbnails.jobs.size }.by(1) - .and change { Post.where(baked_version: nil).count }.by(1) + expect { subject.restore(directory) }.to change { OptimizedImage.count }.by_at_most( + -1, + ).and change { Jobs::CreateAvatarThumbnails.jobs.size }.by(1).and change { + Post.where(baked_version: nil).count + }.by(1) end end @@ -171,10 +190,11 @@ RSpec.describe BackupRestore::UploadsRestorer do with_temp_uploads_directory(with_optimized: true) do |directory, path| store_class.any_instance.expects(:copy_from).with(path).once - expect { subject.restore(directory) } - .to not_change { OptimizedImage.count } - .and not_change { Jobs::CreateAvatarThumbnails.jobs.size } - .and change { Post.where(baked_version: nil).count }.by(1) + expect { subject.restore(directory) }.to not_change { + OptimizedImage.count + }.and not_change { Jobs::CreateAvatarThumbnails.jobs.size }.and change { + Post.where(baked_version: nil).count + }.by(1) end end end @@ -187,14 +207,14 @@ RSpec.describe BackupRestore::UploadsRestorer do target_site_name: target_site_name, metadata: [source_site_type, base_url], from: "https://test.localhost/forum", - to: "http://localhost" + to: "http://localhost", ) end it "doesn't remap when `cdn_url` in `backup_metadata` is empty" do expect_no_remap( target_site_name: target_site_name, - metadata: [source_site_type, no_cdn_url] + metadata: [source_site_type, no_cdn_url], ) end @@ -206,8 +226,8 @@ RSpec.describe BackupRestore::UploadsRestorer do metadata: [source_site_type, cdn_url], remaps: [ { from: "https://some-cdn.example.com/", to: "https://new-cdn.example.com/" }, - { from: "some-cdn.example.com", to: "new-cdn.example.com" } - ] + { from: "some-cdn.example.com", to: "new-cdn.example.com" }, + ], ) end @@ -220,8 +240,8 @@ RSpec.describe BackupRestore::UploadsRestorer do metadata: [source_site_type, cdn_url], remaps: [ { from: "https://some-cdn.example.com/", to: "//example.com/discourse/" }, - { from: "some-cdn.example.com", to: "example.com" } - ] + { from: "some-cdn.example.com", to: "example.com" }, + ], ) end end @@ -230,22 +250,20 @@ RSpec.describe BackupRestore::UploadsRestorer do it "doesn't remap when `s3_base_url` in `backup_metadata` is empty" do expect_no_remap( target_site_name: target_site_name, - metadata: [source_site_type, s3_base_url] + metadata: [source_site_type, s3_base_url], ) end it "doesn't remap when `s3_cdn_url` in `backup_metadata` is empty" do expect_no_remap( target_site_name: target_site_name, - metadata: [source_site_type, s3_cdn_url] + metadata: [source_site_type, s3_cdn_url], ) end end context "when currently stored locally" do - before do - SiteSetting.enable_s3_uploads = false - end + before { SiteSetting.enable_s3_uploads = false } let!(:store_class) { FileStore::LocalStore } @@ -297,7 +315,9 @@ RSpec.describe BackupRestore::UploadsRestorer do end context "with uploads previously stored on S3" do - let!(:s3_base_url) { { name: "s3_base_url", value: "//old-bucket.s3-us-east-1.amazonaws.com" } } + let!(:s3_base_url) do + { name: "s3_base_url", value: "//old-bucket.s3-us-east-1.amazonaws.com" } + end let!(:s3_cdn_url) { { name: "s3_cdn_url", value: "https://s3-cdn.example.com" } } shared_examples "regular site remaps from S3" do @@ -307,7 +327,7 @@ RSpec.describe BackupRestore::UploadsRestorer do metadata: [no_multisite, s3_base_url], from: s3_url_regex("old-bucket", "/"), to: uploads_path(target_site_name), - regex: true + regex: true, ) end @@ -316,9 +336,12 @@ RSpec.describe BackupRestore::UploadsRestorer do target_site_name: target_site_name, metadata: [no_multisite, s3_cdn_url], remaps: [ - { from: "https://s3-cdn.example.com/", to: "//#{target_hostname}#{uploads_path(target_site_name)}" }, - { from: "s3-cdn.example.com", to: target_hostname } - ] + { + from: "https://s3-cdn.example.com/", + to: "//#{target_hostname}#{uploads_path(target_site_name)}", + }, + { from: "s3-cdn.example.com", to: target_hostname }, + ], ) end end @@ -330,7 +353,7 @@ RSpec.describe BackupRestore::UploadsRestorer do metadata: [source_db_name, multisite, s3_base_url], from: s3_url_regex("old-bucket", "/"), to: "/", - regex: true + regex: true, ) end @@ -340,8 +363,8 @@ RSpec.describe BackupRestore::UploadsRestorer do metadata: [source_db_name, multisite, s3_cdn_url], remaps: [ { from: "https://s3-cdn.example.com/", to: "//#{target_hostname}/" }, - { from: "s3-cdn.example.com", to: target_hostname } - ] + { from: "s3-cdn.example.com", to: target_hostname }, + ], ) end end @@ -386,9 +409,7 @@ RSpec.describe BackupRestore::UploadsRestorer do end context "when currently stored on S3" do - before do - setup_s3 - end + before { setup_s3 } let!(:store_class) { FileStore::S3Store } @@ -440,7 +461,9 @@ RSpec.describe BackupRestore::UploadsRestorer do end context "with uploads previously stored on S3" do - let!(:s3_base_url) { { name: "s3_base_url", value: "//old-bucket.s3-us-east-1.amazonaws.com" } } + let!(:s3_base_url) do + { name: "s3_base_url", value: "//old-bucket.s3-us-east-1.amazonaws.com" } + end let!(:s3_cdn_url) { { name: "s3_cdn_url", value: "https://s3-cdn.example.com" } } shared_examples "regular site remaps from S3" do @@ -450,20 +473,26 @@ RSpec.describe BackupRestore::UploadsRestorer do metadata: [no_multisite, s3_base_url], from: s3_url_regex("old-bucket", "/"), to: uploads_path(target_site_name), - regex: true + regex: true, ) end it "remaps when `s3_cdn_url` changes" do - SiteSetting::Upload.expects(:s3_cdn_url).returns("https://new-s3-cdn.example.com").at_least_once + SiteSetting::Upload + .expects(:s3_cdn_url) + .returns("https://new-s3-cdn.example.com") + .at_least_once expect_remaps( target_site_name: target_site_name, metadata: [no_multisite, s3_cdn_url], remaps: [ - { from: "https://s3-cdn.example.com/", to: "https://new-s3-cdn.example.com#{uploads_path(target_site_name)}" }, - { from: "s3-cdn.example.com", to: "new-s3-cdn.example.com" } - ] + { + from: "https://s3-cdn.example.com/", + to: "https://new-s3-cdn.example.com#{uploads_path(target_site_name)}", + }, + { from: "s3-cdn.example.com", to: "new-s3-cdn.example.com" }, + ], ) end end @@ -475,21 +504,24 @@ RSpec.describe BackupRestore::UploadsRestorer do metadata: [source_db_name, multisite, s3_base_url], from: s3_url_regex("old-bucket", "/"), to: "/", - regex: true + regex: true, ) end context "when `s3_cdn_url` is configured" do it "remaps when `s3_cdn_url` changes" do - SiteSetting::Upload.expects(:s3_cdn_url).returns("http://new-s3-cdn.example.com").at_least_once + SiteSetting::Upload + .expects(:s3_cdn_url) + .returns("http://new-s3-cdn.example.com") + .at_least_once expect_remaps( target_site_name: target_site_name, metadata: [source_db_name, multisite, s3_cdn_url], remaps: [ { from: "https://s3-cdn.example.com/", to: "//new-s3-cdn.example.com/" }, - { from: "s3-cdn.example.com", to: "new-s3-cdn.example.com" } - ] + { from: "s3-cdn.example.com", to: "new-s3-cdn.example.com" }, + ], ) end end @@ -503,8 +535,8 @@ RSpec.describe BackupRestore::UploadsRestorer do metadata: [source_db_name, multisite, s3_cdn_url], remaps: [ { from: "https://s3-cdn.example.com/", to: "//#{target_hostname}/" }, - { from: "s3-cdn.example.com", to: target_hostname } - ] + { from: "s3-cdn.example.com", to: target_hostname }, + ], ) end end @@ -593,7 +625,7 @@ RSpec.describe BackupRestore::UploadsRestorer do source_site_name: "xylan", target_site_name: "default", from: "/uploads/xylan/", - to: uploads_path("default") + to: uploads_path("default"), ) do |directory| FileUtils.mkdir_p(File.join(directory, "uploads", "PaxHeaders.27134")) FileUtils.mkdir_p(File.join(directory, "uploads", ".hidden")) diff --git a/spec/lib/bookmark_manager_spec.rb b/spec/lib/bookmark_manager_spec.rb index e362a06199..164cee1872 100644 --- a/spec/lib/bookmark_manager_spec.rb +++ b/spec/lib/bookmark_manager_spec.rb @@ -5,7 +5,7 @@ RSpec.describe BookmarkManager do let(:reminder_at) { 1.day.from_now } fab!(:post) { Fabricate(:post) } - let(:name) { 'Check this out!' } + let(:name) { "Check this out!" } subject { described_class.new(user) } @@ -40,7 +40,14 @@ RSpec.describe BookmarkManager do end describe ".update" do - let!(:bookmark) { Fabricate(:bookmark_next_business_day_reminder, user: user, bookmarkable: post, name: "Old name") } + let!(:bookmark) do + Fabricate( + :bookmark_next_business_day_reminder, + user: user, + bookmarkable: post, + name: "Old name", + ) + end let(:new_name) { "Some new name" } let(:new_reminder_at) { 10.days.from_now } let(:options) { {} } @@ -50,7 +57,7 @@ RSpec.describe BookmarkManager do bookmark_id: bookmark.id, name: new_name, reminder_at: new_reminder_at, - options: options + options: options, ) end @@ -69,7 +76,9 @@ RSpec.describe BookmarkManager do end context "when options are provided" do - let(:options) { { auto_delete_preference: Bookmark.auto_delete_preferences[:when_reminder_sent] } } + let(:options) do + { auto_delete_preference: Bookmark.auto_delete_preferences[:when_reminder_sent] } + end it "saves any additional options successfully" do update_bookmark @@ -86,9 +95,7 @@ RSpec.describe BookmarkManager do end context "if the bookmark no longer exists" do - before do - bookmark.destroy! - end + before { bookmark.destroy! } it "raises a not found error" do expect { update_bookmark }.to raise_error(Discourse::NotFound) end @@ -97,8 +104,12 @@ RSpec.describe BookmarkManager do describe ".destroy_for_topic" do let!(:topic) { Fabricate(:topic) } - let!(:bookmark1) { Fabricate(:bookmark, bookmarkable: Fabricate(:post, topic: topic), user: user) } - let!(:bookmark2) { Fabricate(:bookmark, bookmarkable: Fabricate(:post, topic: topic), user: user) } + let!(:bookmark1) do + Fabricate(:bookmark, bookmarkable: Fabricate(:post, topic: topic), user: user) + end + let!(:bookmark2) do + Fabricate(:bookmark, bookmarkable: Fabricate(:post, topic: topic), user: user) + end it "destroys all bookmarks for the topic for the specified user" do subject.destroy_for_topic(topic) @@ -136,9 +147,7 @@ RSpec.describe BookmarkManager do end context "when the bookmark does no longer exist" do - before do - bookmark.destroy - end + before { bookmark.destroy } it "does not error, and does not create a notification" do described_class.send_reminder_notification(bookmark.id) expect(notifications_for_user.any?).to eq(false) @@ -146,9 +155,7 @@ RSpec.describe BookmarkManager do end context "if the post has been deleted" do - before do - bookmark.bookmarkable.trash! - end + before { bookmark.bookmarkable.trash! } it "does not error and does not create a notification" do described_class.send_reminder_notification(bookmark.id) bookmark.reload @@ -157,7 +164,10 @@ RSpec.describe BookmarkManager do end def notifications_for_user - Notification.where(notification_type: Notification.types[:bookmark_reminder], user_id: bookmark.user.id) + Notification.where( + notification_type: Notification.types[:bookmark_reminder], + user_id: bookmark.user.id, + ) end end @@ -179,14 +189,14 @@ RSpec.describe BookmarkManager do context "if the bookmark is belonging to some other user" do let!(:bookmark) { Fabricate(:bookmark, user: Fabricate(:admin)) } it "raises an invalid access error" do - expect { subject.toggle_pin(bookmark_id: bookmark.id) }.to raise_error(Discourse::InvalidAccess) + expect { subject.toggle_pin(bookmark_id: bookmark.id) }.to raise_error( + Discourse::InvalidAccess, + ) end end context "if the bookmark no longer exists" do - before do - bookmark.destroy! - end + before { bookmark.destroy! } it "raises a not found error" do expect { subject.toggle_pin(bookmark_id: bookmark.id) }.to raise_error(Discourse::NotFound) end @@ -220,11 +230,18 @@ RSpec.describe BookmarkManager do it "adds a validation error when the bookmarkable_type is not registered" do subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "BlahFactory", name: name) - expect(subject.errors.full_messages).to include(I18n.t("bookmarks.errors.invalid_bookmarkable", type: "BlahFactory")) + expect(subject.errors.full_messages).to include( + I18n.t("bookmarks.errors.invalid_bookmarkable", type: "BlahFactory"), + ) end it "updates the topic user bookmarked column to true if any post is bookmarked" do - subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: name, reminder_at: reminder_at) + subject.create_for( + bookmarkable_id: post.id, + bookmarkable_type: "Post", + name: name, + reminder_at: reminder_at, + ) tu = TopicUser.find_by(user: user) expect(tu.bookmarked).to eq(true) tu.update(bookmarked: false) @@ -235,35 +252,63 @@ RSpec.describe BookmarkManager do end it "sets auto_delete_preference to clear_reminder by default" do - bookmark = subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: name, reminder_at: reminder_at) - expect(bookmark.auto_delete_preference).to eq(Bookmark.auto_delete_preferences[:clear_reminder]) - end - - context "when the user has set their bookmark_auto_delete_preference" do - before do - user.user_option.update!(bookmark_auto_delete_preference: Bookmark.auto_delete_preferences[:on_owner_reply]) - end - - it "sets auto_delete_preferences to the user's user_option.bookmark_auto_delete_preference" do - bookmark = subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: name, reminder_at: reminder_at) - expect(bookmark.auto_delete_preference).to eq(Bookmark.auto_delete_preferences[:on_owner_reply]) - end - - it "uses the passed in auto_delete_preference option instead of the user's one" do - bookmark = subject.create_for( + bookmark = + subject.create_for( bookmarkable_id: post.id, bookmarkable_type: "Post", name: name, reminder_at: reminder_at, - options: { auto_delete_preference: Bookmark.auto_delete_preferences[:when_reminder_sent] } ) - expect(bookmark.auto_delete_preference).to eq(Bookmark.auto_delete_preferences[:when_reminder_sent]) + expect(bookmark.auto_delete_preference).to eq( + Bookmark.auto_delete_preferences[:clear_reminder], + ) + end + + context "when the user has set their bookmark_auto_delete_preference" do + before do + user.user_option.update!( + bookmark_auto_delete_preference: Bookmark.auto_delete_preferences[:on_owner_reply], + ) + end + + it "sets auto_delete_preferences to the user's user_option.bookmark_auto_delete_preference" do + bookmark = + subject.create_for( + bookmarkable_id: post.id, + bookmarkable_type: "Post", + name: name, + reminder_at: reminder_at, + ) + expect(bookmark.auto_delete_preference).to eq( + Bookmark.auto_delete_preferences[:on_owner_reply], + ) + end + + it "uses the passed in auto_delete_preference option instead of the user's one" do + bookmark = + subject.create_for( + bookmarkable_id: post.id, + bookmarkable_type: "Post", + name: name, + reminder_at: reminder_at, + options: { + auto_delete_preference: Bookmark.auto_delete_preferences[:when_reminder_sent], + }, + ) + expect(bookmark.auto_delete_preference).to eq( + Bookmark.auto_delete_preferences[:when_reminder_sent], + ) end end context "when a reminder time is provided" do it "saves the values correctly" do - subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: name, reminder_at: reminder_at) + subject.create_for( + bookmarkable_id: post.id, + bookmarkable_type: "Post", + name: name, + reminder_at: reminder_at, + ) bookmark = Bookmark.find_by(user: user, bookmarkable: post) expect(bookmark.reminder_at).to eq_time(reminder_at) @@ -272,10 +317,18 @@ RSpec.describe BookmarkManager do end context "when options are provided" do - let(:options) { { auto_delete_preference: Bookmark.auto_delete_preferences[:when_reminder_sent] } } + let(:options) do + { auto_delete_preference: Bookmark.auto_delete_preferences[:when_reminder_sent] } + end it "saves any additional options successfully" do - subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: name, reminder_at: reminder_at, options: options) + subject.create_for( + bookmarkable_id: post.id, + bookmarkable_type: "Post", + name: name, + reminder_at: reminder_at, + options: options, + ) bookmark = Bookmark.find_by(user: user, bookmarkable: post) expect(bookmark.auto_delete_preference).to eq(1) @@ -283,20 +336,22 @@ RSpec.describe BookmarkManager do end context "when the bookmark already exists for the user & post" do - before do - Bookmark.create(bookmarkable: post, user: user) - end + before { Bookmark.create(bookmarkable: post, user: user) } it "adds an error to the manager" do subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post") - expect(subject.errors.full_messages).to include(I18n.t("bookmarks.errors.already_bookmarked", type: "Post")) + expect(subject.errors.full_messages).to include( + I18n.t("bookmarks.errors.already_bookmarked", type: "Post"), + ) end end context "when the bookmark name is too long" do it "adds an error to the manager" do subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: "test" * 100) - expect(subject.errors.full_messages).to include("Name is too long (maximum is 100 characters)") + expect(subject.errors.full_messages).to include( + "Name is too long (maximum is 100 characters)", + ) end end @@ -304,8 +359,15 @@ RSpec.describe BookmarkManager do let(:reminder_at) { 10.days.ago } it "adds an error to the manager" do - subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: name, reminder_at: reminder_at) - expect(subject.errors.full_messages).to include(I18n.t("bookmarks.errors.cannot_set_past_reminder")) + subject.create_for( + bookmarkable_id: post.id, + bookmarkable_type: "Post", + name: name, + reminder_at: reminder_at, + ) + expect(subject.errors.full_messages).to include( + I18n.t("bookmarks.errors.cannot_set_past_reminder"), + ) end end @@ -313,26 +375,33 @@ RSpec.describe BookmarkManager do let(:reminder_at) { 11.years.from_now } it "adds an error to the manager" do - subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: name, reminder_at: reminder_at) - expect(subject.errors.full_messages).to include(I18n.t("bookmarks.errors.cannot_set_reminder_in_distant_future")) + subject.create_for( + bookmarkable_id: post.id, + bookmarkable_type: "Post", + name: name, + reminder_at: reminder_at, + ) + expect(subject.errors.full_messages).to include( + I18n.t("bookmarks.errors.cannot_set_reminder_in_distant_future"), + ) end end context "when the post is inaccessible for the user" do - before do - post.trash! - end + before { post.trash! } it "raises an invalid access error" do - expect { subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: name) }.to raise_error(Discourse::InvalidAccess) + expect { + subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: name) + }.to raise_error(Discourse::InvalidAccess) end end context "when the topic is inaccessible for the user" do - before do - post.topic.update(category: Fabricate(:private_category, group: Fabricate(:group))) - end + before { post.topic.update(category: Fabricate(:private_category, group: Fabricate(:group))) } it "raises an invalid access error" do - expect { subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: name) }.to raise_error(Discourse::InvalidAccess) + expect { + subject.create_for(bookmarkable_id: post.id, bookmarkable_type: "Post", name: name) + }.to raise_error(Discourse::InvalidAccess) end end end diff --git a/spec/lib/bookmark_query_spec.rb b/spec/lib/bookmark_query_spec.rb index 69d218aa57..0017dfbf59 100644 --- a/spec/lib/bookmark_query_spec.rb +++ b/spec/lib/bookmark_query_spec.rb @@ -1,9 +1,7 @@ # frozen_string_literal: true RSpec.describe BookmarkQuery do - before do - SearchIndexer.enable - end + before { SearchIndexer.enable } fab!(:user) { Fabricate(:user) } let(:params) { {} } @@ -24,12 +22,12 @@ RSpec.describe BookmarkQuery do let(:post_bookmark) { Fabricate(:bookmark, user: user, bookmarkable: Fabricate(:post)) } let(:topic_bookmark) { Fabricate(:bookmark, user: user, bookmarkable: Fabricate(:topic)) } - let(:user_bookmark) { Fabricate(:bookmark, user: user, bookmarkable: Fabricate(:user, username: "bookmarkqueen")) } - - after do - Bookmark.reset_bookmarkables + let(:user_bookmark) do + Fabricate(:bookmark, user: user, bookmarkable: Fabricate(:user, username: "bookmarkqueen")) end + after { Bookmark.reset_bookmarkables } + it "returns all the bookmarks for a user" do expect(bookmark_query.list_all.count).to eq(3) end @@ -42,16 +40,16 @@ RSpec.describe BookmarkQuery do it "runs the on_preload block provided passing in bookmarks" do preloaded_bookmarks = [] - BookmarkQuery.on_preload do |bookmarks, bq| - (preloaded_bookmarks << bookmarks).flatten - end + BookmarkQuery.on_preload { |bookmarks, bq| (preloaded_bookmarks << bookmarks).flatten } bookmark_query.list_all expect(preloaded_bookmarks.any?).to eq(true) end it "returns a mixture of post, topic, and custom bookmarkable type bookmarks" do bookmarks = bookmark_query.list_all - expect(bookmarks.map(&:id)).to match_array([post_bookmark.id, topic_bookmark.id, user_bookmark.id]) + expect(bookmarks.map(&:id)).to match_array( + [post_bookmark.id, topic_bookmark.id, user_bookmark.id], + ) end it "handles the user not having permission for all of the bookmarks of a certain bookmarkable" do @@ -61,7 +59,9 @@ RSpec.describe BookmarkQuery do end it "handles the user not having permission to see any of their bookmarks" do - topic_bookmark.bookmarkable.update(category: Fabricate(:private_category, group: Fabricate(:group))) + topic_bookmark.bookmarkable.update( + category: Fabricate(:private_category, group: Fabricate(:group)), + ) post_bookmark.bookmarkable.topic.update(category: topic_bookmark.bookmarkable.category) UserTestBookmarkable.expects(:list_query).returns(nil) bookmarks = bookmark_query.list_all @@ -69,17 +69,21 @@ RSpec.describe BookmarkQuery do end context "when q param is provided" do - let!(:post) { Fabricate(:post, raw: "Some post content here", topic: Fabricate(:topic, title: "Bugfix game for devs")) } - - before do - Bookmark.reset_bookmarkables + let!(:post) do + Fabricate( + :post, + raw: "Some post content here", + topic: Fabricate(:topic, title: "Bugfix game for devs"), + ) end - after do - Bookmark.reset_bookmarkables - end + before { Bookmark.reset_bookmarkables } - let(:bookmark3) { Fabricate(:bookmark, user: user, name: "Check up later", bookmarkable: Fabricate(:post)) } + after { Bookmark.reset_bookmarkables } + + let(:bookmark3) do + Fabricate(:bookmark, user: user, name: "Check up later", bookmarkable: Fabricate(:post)) + end let(:bookmark4) { Fabricate(:bookmark, user: user, bookmarkable: post) } before do @@ -88,29 +92,29 @@ RSpec.describe BookmarkQuery do end it "can search by bookmark name" do - bookmarks = bookmark_query(params: { q: 'check' }).list_all + bookmarks = bookmark_query(params: { q: "check" }).list_all expect(bookmarks.map(&:id)).to eq([bookmark3.id]) end it "can search by post content" do - bookmarks = bookmark_query(params: { q: 'content' }).list_all + bookmarks = bookmark_query(params: { q: "content" }).list_all expect(bookmarks.map(&:id)).to eq([bookmark4.id]) end it "can search by topic title" do - bookmarks = bookmark_query(params: { q: 'bugfix' }).list_all + bookmarks = bookmark_query(params: { q: "bugfix" }).list_all expect(bookmarks.map(&:id)).to eq([bookmark4.id]) end context "with custom bookmarkable fitering" do - before do - register_test_bookmarkable + before { register_test_bookmarkable } + + let!(:bookmark5) do + Fabricate(:bookmark, user: user, bookmarkable: Fabricate(:user, username: "bookmarkking")) end - let!(:bookmark5) { Fabricate(:bookmark, user: user, bookmarkable: Fabricate(:user, username: "bookmarkking")) } - it "allows searching bookmarkables by fields in other tables" do - bookmarks = bookmark_query(params: { q: 'bookmarkk' }).list_all + bookmarks = bookmark_query(params: { q: "bookmarkk" }).list_all expect(bookmarks.map(&:id)).to eq([bookmark5.id]) end end @@ -157,9 +161,7 @@ RSpec.describe BookmarkQuery do end context "when the user is a topic_allowed_user" do - before do - TopicAllowedUser.create(topic: pm_topic, user: user) - end + before { TopicAllowedUser.create(topic: pm_topic, user: user) } it "shows the user the bookmark in the PM" do expect(bookmark_query.list_all.map(&:id).count).to eq(3) end @@ -192,7 +194,9 @@ RSpec.describe BookmarkQuery do context "when the topic category is private" do let(:group) { Fabricate(:group) } before do - post_bookmark.bookmarkable.topic.update(category: Fabricate(:private_category, group: group)) + post_bookmark.bookmarkable.topic.update( + category: Fabricate(:private_category, group: group), + ) post_bookmark.reload end it "does not show the user a post/topic in a private category they cannot see" do @@ -227,26 +231,18 @@ RSpec.describe BookmarkQuery do end it "order defaults to updated_at DESC" do - expect(bookmark_query.list_all.map(&:id)).to eq([ - bookmark1.id, - bookmark2.id, - bookmark5.id, - bookmark4.id, - bookmark3.id - ]) + expect(bookmark_query.list_all.map(&:id)).to eq( + [bookmark1.id, bookmark2.id, bookmark5.id, bookmark4.id, bookmark3.id], + ) end it "orders by reminder_at, then updated_at" do bookmark4.update_column(:reminder_at, 1.day.from_now) bookmark5.update_column(:reminder_at, 26.hours.from_now) - expect(bookmark_query.list_all.map(&:id)).to eq([ - bookmark4.id, - bookmark5.id, - bookmark1.id, - bookmark2.id, - bookmark3.id - ]) + expect(bookmark_query.list_all.map(&:id)).to eq( + [bookmark4.id, bookmark5.id, bookmark1.id, bookmark2.id, bookmark3.id], + ) end it "shows pinned bookmarks first ordered by reminder_at ASC then updated_at DESC" do @@ -261,13 +257,9 @@ RSpec.describe BookmarkQuery do bookmark5.update_column(:reminder_at, 1.day.from_now) - expect(bookmark_query.list_all.map(&:id)).to eq([ - bookmark3.id, - bookmark4.id, - bookmark1.id, - bookmark2.id, - bookmark5.id - ]) + expect(bookmark_query.list_all.map(&:id)).to eq( + [bookmark3.id, bookmark4.id, bookmark1.id, bookmark2.id, bookmark5.id], + ) end end end diff --git a/spec/lib/bookmark_reminder_notification_handler_spec.rb b/spec/lib/bookmark_reminder_notification_handler_spec.rb index cf660394ce..9a77ad9bb1 100644 --- a/spec/lib/bookmark_reminder_notification_handler_spec.rb +++ b/spec/lib/bookmark_reminder_notification_handler_spec.rb @@ -5,9 +5,7 @@ RSpec.describe BookmarkReminderNotificationHandler do fab!(:user) { Fabricate(:user) } - before do - Discourse.redis.flushdb - end + before { Discourse.redis.flushdb } describe "#send_notification" do let!(:bookmark) do @@ -42,7 +40,9 @@ RSpec.describe BookmarkReminderNotificationHandler do context "when the auto_delete_preference is when_reminder_sent" do before do TopicUser.create!(topic: bookmark.bookmarkable.topic, user: user, bookmarked: true) - bookmark.update(auto_delete_preference: Bookmark.auto_delete_preferences[:when_reminder_sent]) + bookmark.update( + auto_delete_preference: Bookmark.auto_delete_preferences[:when_reminder_sent], + ) end it "deletes the bookmark after the reminder gets sent" do @@ -52,17 +52,25 @@ RSpec.describe BookmarkReminderNotificationHandler do it "changes the TopicUser bookmarked column to false" do subject.new(bookmark).send_notification - expect(TopicUser.find_by(topic: bookmark.bookmarkable.topic, user: user).bookmarked).to eq(false) + expect(TopicUser.find_by(topic: bookmark.bookmarkable.topic, user: user).bookmarked).to eq( + false, + ) end context "if there are still other bookmarks in the topic" do before do - Fabricate(:bookmark, bookmarkable: Fabricate(:post, topic: bookmark.bookmarkable.topic), user: user) + Fabricate( + :bookmark, + bookmarkable: Fabricate(:post, topic: bookmark.bookmarkable.topic), + user: user, + ) end it "does not change the TopicUser bookmarked column to false" do subject.new(bookmark).send_notification - expect(TopicUser.find_by(topic: bookmark.bookmarkable.topic, user: user).bookmarked).to eq(true) + expect( + TopicUser.find_by(topic: bookmark.bookmarkable.topic, user: user).bookmarked, + ).to eq(true) end end end diff --git a/spec/lib/browser_detection_spec.rb b/spec/lib/browser_detection_spec.rb index d0820bc511..0232020c7c 100644 --- a/spec/lib/browser_detection_spec.rb +++ b/spec/lib/browser_detection_spec.rb @@ -1,42 +1,150 @@ # frozen_string_literal: true -require 'browser_detection' +require "browser_detection" RSpec.describe BrowserDetection do - it "detects browser, device and operating system" do [ ["Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 6.1)", :ie, :windows, :windows], - ["Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)", :ie, :windows, :windows], - ["Mozilla/5.0 (iPad; CPU OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1", :safari, :ipad, :ios], - ["Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1", :safari, :iphone, :ios], - ["Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/51.0.2704.104 Mobile/13F69 Safari/601.1.46", :chrome, :iphone, :ios], - ["Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19", :chrome, :android, :android], - ["Mozilla/5.0 (Linux; Android 4.4.2; XMP-6250 Build/HAWK) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Safari/537.36 ADAPI/2.0 (UUID:9e7df0ed-2a5c-4a19-bec7-2cc54800f99d) RK3188-ADAPI/1.2.84.533 (MODEL:XMP-6250)", :chrome, :android, :android], - ["Mozilla/5.0 (Linux; Android 5.1; Nexus 7 Build/LMY47O) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.105 Safari/537.36", :chrome, :android, :android], - ["Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", :chrome, :android, :android], - ["Mozilla/5.0 (Linux; Android; 4.1.2; GT-I9100 Build/000000) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1234.12 Mobile Safari/537.22 OPR/14.0.123.123", :opera, :android, :android], - ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0", :firefox, :mac, :macos], - ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0", :firefox, :mac, :macos], - ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", :chrome, :mac, :macos], - ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", :chrome, :windows, :windows], - ["Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.1", :firefox, :windows, :windows], + [ + "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)", + :ie, + :windows, + :windows, + ], + [ + "Mozilla/5.0 (iPad; CPU OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13F69 Safari/601.1", + :safari, + :ipad, + :ios, + ], + [ + "Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1", + :safari, + :iphone, + :ios, + ], + [ + "Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/51.0.2704.104 Mobile/13F69 Safari/601.1.46", + :chrome, + :iphone, + :ios, + ], + [ + "Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19", + :chrome, + :android, + :android, + ], + [ + "Mozilla/5.0 (Linux; Android 4.4.2; XMP-6250 Build/HAWK) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Safari/537.36 ADAPI/2.0 (UUID:9e7df0ed-2a5c-4a19-bec7-2cc54800f99d) RK3188-ADAPI/1.2.84.533 (MODEL:XMP-6250)", + :chrome, + :android, + :android, + ], + [ + "Mozilla/5.0 (Linux; Android 5.1; Nexus 7 Build/LMY47O) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.105 Safari/537.36", + :chrome, + :android, + :android, + ], + [ + "Mozilla/5.0 (Linux; Android 6.0.1; vivo 1603 Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.83 Mobile Safari/537.36", + :chrome, + :android, + :android, + ], + [ + "Mozilla/5.0 (Linux; Android; 4.1.2; GT-I9100 Build/000000) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1234.12 Mobile Safari/537.22 OPR/14.0.123.123", + :opera, + :android, + :android, + ], + [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0", + :firefox, + :mac, + :macos, + ], + [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0", + :firefox, + :mac, + :macos, + ], + [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36", + :chrome, + :mac, + :macos, + ], + [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36", + :chrome, + :windows, + :windows, + ], + [ + "Mozilla/5.0 (Windows NT 5.1; rv:7.0.1) Gecko/20100101 Firefox/7.0.1", + :firefox, + :windows, + :windows, + ], ["Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko", :ie, :windows, :windows], - ["Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", :chrome, :windows, :windows], - ["Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1", :firefox, :windows, :windows], - ["Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0", :firefox, :windows, :windows], - ["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", :chrome, :linux, :linux], - ["Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)", :firefox, :linux, :linux], + [ + "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", + :chrome, + :windows, + :windows, + ], + [ + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1", + :firefox, + :windows, + :windows, + ], + [ + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:54.0) Gecko/20100101 Firefox/54.0", + :firefox, + :windows, + :windows, + ], + [ + "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36", + :chrome, + :linux, + :linux, + ], + [ + "Mozilla/5.0 (X11; Linux x86_64; rv:10.0) Gecko/20150101 Firefox/47.0 (Chrome)", + :firefox, + :linux, + :linux, + ], ["Opera/9.80 (X11; Linux zvav; U; en) Presto/2.12.423 Version/12.16", :opera, :linux, :linux], - ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246", :edge, :windows, :windows], - ["Mozilla/5.0 (X11; CrOS x86_64 11895.95.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.125 Safari/537.36 ", :chrome, :chromebook, :chromeos], - ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edg/75.10240", :edge, :windows, :windows], - ["Discourse/163 CFNetwork/978.0.7 Darwin/18.6.0", :discoursehub, :unknown, :ios] + [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246", + :edge, + :windows, + :windows, + ], + [ + "Mozilla/5.0 (X11; CrOS x86_64 11895.95.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.125 Safari/537.36 ", + :chrome, + :chromebook, + :chromeos, + ], + [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edg/75.10240", + :edge, + :windows, + :windows, + ], + ["Discourse/163 CFNetwork/978.0.7 Darwin/18.6.0", :discoursehub, :unknown, :ios], ].each do |user_agent, browser, device, os| expect(BrowserDetection.browser(user_agent)).to eq(browser) expect(BrowserDetection.device(user_agent)).to eq(device) expect(BrowserDetection.os(user_agent)).to eq(os) end end - end diff --git a/spec/lib/cache_spec.rb b/spec/lib/cache_spec.rb index 19d79d81ba..ae09bcc39b 100644 --- a/spec/lib/cache_spec.rb +++ b/spec/lib/cache_spec.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require 'cache' +require "cache" RSpec.describe Cache do - let :cache do Cache.new end @@ -43,9 +42,7 @@ RSpec.describe Cache do it "can delete correctly" do cache.delete("key") - cache.fetch("key", expires_in: 1.minute) do - "test" - end + cache.fetch("key", expires_in: 1.minute) { "test" } expect(cache.fetch("key")).to eq("test") @@ -59,9 +56,7 @@ RSpec.describe Cache do key = cache.normalize_key("key") - cache.fetch("key", expires_in: 1.minute) do - "bob" - end + cache.fetch("key", expires_in: 1.minute) { "bob" } expect(Discourse.redis.ttl(key)).to be_within(2.seconds).of(1.minute) @@ -75,9 +70,10 @@ RSpec.describe Cache do it "can store and fetch correctly" do cache.delete "key" - r = cache.fetch "key" do - "bob" - end + r = + cache.fetch "key" do + "bob" + end expect(r).to eq("bob") end @@ -85,9 +81,10 @@ RSpec.describe Cache do it "can fetch existing correctly" do cache.write "key", "bill" - r = cache.fetch "key" do - "bob" - end + r = + cache.fetch "key" do + "bob" + end expect(r).to eq("bill") end diff --git a/spec/lib/category_badge_spec.rb b/spec/lib/category_badge_spec.rb index c6d04cead2..3d36d1af03 100644 --- a/spec/lib/category_badge_spec.rb +++ b/spec/lib/category_badge_spec.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require 'category_badge' +require "category_badge" RSpec.describe CategoryBadge do it "escapes HTML in category names / descriptions" do - c = Fabricate(:category, name: 'name', description: 'title') + c = Fabricate(:category, name: "name", description: "title") html = CategoryBadge.html_for(c) diff --git a/spec/lib/category_guardian_spec.rb b/spec/lib/category_guardian_spec.rb index a2679c81d3..0cbd94cea1 100644 --- a/spec/lib/category_guardian_spec.rb +++ b/spec/lib/category_guardian_spec.rb @@ -22,7 +22,13 @@ RSpec.describe CategoryGuardian do context "when restricted category" do fab!(:group) { Fabricate(:group) } - fab!(:category) { Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:readonly]) } + fab!(:category) do + Fabricate( + :private_category, + group: group, + permission_type: CategoryGroup.permission_types[:readonly], + ) + end fab!(:group_user) { Fabricate(:group_user, group: group, user: user) } it "returns false for anonymous user" do @@ -38,12 +44,22 @@ RSpec.describe CategoryGuardian do end it "returns true for member of group with create_post access" do - category = Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:create_post]) + category = + Fabricate( + :private_category, + group: group, + permission_type: CategoryGroup.permission_types[:create_post], + ) expect(Guardian.new(user).can_post_in_category?(category)).to eq(true) end it "returns true for member of group with full access" do - category = Fabricate(:private_category, group: group, permission_type: CategoryGroup.permission_types[:full]) + category = + Fabricate( + :private_category, + group: group, + permission_type: CategoryGroup.permission_types[:full], + ) expect(Guardian.new(user).can_post_in_category?(category)).to eq(true) end end diff --git a/spec/lib/common_passwords/common_passwords_spec.rb b/spec/lib/common_passwords/common_passwords_spec.rb index fba5e6804f..7373882a20 100644 --- a/spec/lib/common_passwords/common_passwords_spec.rb +++ b/spec/lib/common_passwords/common_passwords_spec.rb @@ -12,7 +12,7 @@ RSpec.describe CommonPasswords do it "returns false if password isn't in the common passwords list" do described_class.stubs(:password_list).returns(stub_everything(include?: false)) - @password = 'uncommonPassword' + @password = "uncommonPassword" expect(subject).to eq(false) end @@ -35,19 +35,19 @@ RSpec.describe CommonPasswords do end end - describe '#password_list' do + describe "#password_list" do before { Discourse.redis.flushdb } after { Discourse.redis.flushdb } it "loads the passwords file if redis doesn't have it" do Discourse.redis.without_namespace.stubs(:scard).returns(0) - described_class.expects(:load_passwords).returns(['password']) + described_class.expects(:load_passwords).returns(["password"]) list = described_class.password_list expect(list).to respond_to(:include?) end it "doesn't load the passwords file if redis has it" do - Discourse.redis.without_namespace.stubs(:scard).returns(10000) + Discourse.redis.without_namespace.stubs(:scard).returns(10_000) described_class.expects(:load_passwords).never list = described_class.password_list expect(list).to respond_to(:include?) @@ -55,7 +55,7 @@ RSpec.describe CommonPasswords do it "loads the passwords file if redis has an empty list" do Discourse.redis.without_namespace.stubs(:scard).returns(0) - described_class.expects(:load_passwords).returns(['password']) + described_class.expects(:load_passwords).returns(["password"]) list = described_class.password_list expect(list).to respond_to(:include?) end diff --git a/spec/lib/composer_messages_finder_spec.rb b/spec/lib/composer_messages_finder_spec.rb index 7ff75f30f3..1d7ad381a8 100644 --- a/spec/lib/composer_messages_finder_spec.rb +++ b/spec/lib/composer_messages_finder_spec.rb @@ -1,12 +1,12 @@ # encoding: utf-8 # frozen_string_literal: true -require 'composer_messages_finder' +require "composer_messages_finder" RSpec.describe ComposerMessagesFinder do describe "delegates work" do let(:user) { Fabricate.build(:user) } - let(:finder) { ComposerMessagesFinder.new(user, composer_action: 'createTopic') } + let(:finder) { ComposerMessagesFinder.new(user, composer_action: "createTopic") } it "calls all the message finders" do finder.expects(:check_education_message).once @@ -20,15 +20,13 @@ RSpec.describe ComposerMessagesFinder do end end - describe '.check_education_message' do + describe ".check_education_message" do let(:user) { Fabricate.build(:user) } - context 'when creating topic' do - let(:finder) { ComposerMessagesFinder.new(user, composer_action: 'createTopic') } + context "when creating topic" do + let(:finder) { ComposerMessagesFinder.new(user, composer_action: "createTopic") } - before do - SiteSetting.educate_until_posts = 10 - end + before { SiteSetting.educate_until_posts = 10 } it "returns a message for a user who has not posted any topics" do user.expects(:created_topic_count).returns(9) @@ -41,32 +39,34 @@ RSpec.describe ComposerMessagesFinder do end end - context 'with private message' do + context "with private message" do fab!(:topic) { Fabricate(:private_message_topic) } - context 'when starting a new private message' do - let(:finder) { ComposerMessagesFinder.new(user, composer_action: 'createTopic', topic_id: topic.id) } + context "when starting a new private message" do + let(:finder) do + ComposerMessagesFinder.new(user, composer_action: "createTopic", topic_id: topic.id) + end - it 'should return an empty string' do + it "should return an empty string" do expect(finder.check_education_message).to eq(nil) end end - context 'when replying to a private message' do - let(:finder) { ComposerMessagesFinder.new(user, composer_action: 'reply', topic_id: topic.id) } + context "when replying to a private message" do + let(:finder) do + ComposerMessagesFinder.new(user, composer_action: "reply", topic_id: topic.id) + end - it 'should return an empty string' do + it "should return an empty string" do expect(finder.check_education_message).to eq(nil) end end end - context 'when creating reply' do - let(:finder) { ComposerMessagesFinder.new(user, composer_action: 'reply') } + context "when creating reply" do + let(:finder) { ComposerMessagesFinder.new(user, composer_action: "reply") } - before do - SiteSetting.educate_until_posts = 10 - end + before { SiteSetting.educate_until_posts = 10 } it "returns a message for a user who has not posted any topics" do user.expects(:post_count).returns(9) @@ -80,11 +80,11 @@ RSpec.describe ComposerMessagesFinder do end end - describe '.check_new_user_many_replies' do + describe ".check_new_user_many_replies" do let(:user) { Fabricate.build(:user) } - context 'when replying' do - let(:finder) { ComposerMessagesFinder.new(user, composer_action: 'reply') } + context "when replying" do + let(:finder) { ComposerMessagesFinder.new(user, composer_action: "reply") } it "has no message when `posted_too_much_in_topic?` is false" do user.expects(:posted_too_much_in_topic?).returns(false) @@ -96,11 +96,10 @@ RSpec.describe ComposerMessagesFinder do expect(finder.check_new_user_many_replies).to be_present end end - end - describe '.check_avatar_notification' do - let(:finder) { ComposerMessagesFinder.new(user, composer_action: 'createTopic') } + describe ".check_avatar_notification" do + let(:finder) { ComposerMessagesFinder.new(user, composer_action: "createTopic") } fab!(:user) { Fabricate(:user) } context "with success" do @@ -126,7 +125,10 @@ RSpec.describe ComposerMessagesFinder do end it "doesn't notify users who have been notified already" do - UserHistory.create!(action: UserHistory.actions[:notified_about_avatar], target_user_id: user.id) + UserHistory.create!( + action: UserHistory.actions[:notified_about_avatar], + target_user_id: user.id, + ) expect(finder.check_avatar_notification).to be_blank end @@ -141,12 +143,12 @@ RSpec.describe ComposerMessagesFinder do end it "doesn't notify users if 'allow_uploaded_avatars' setting is disabled" do - SiteSetting.allow_uploaded_avatars = 'disabled' + SiteSetting.allow_uploaded_avatars = "disabled" expect(finder.check_avatar_notification).to be_blank end end - describe '.check_sequential_replies' do + describe ".check_sequential_replies" do fab!(:user) { Fabricate(:user) } fab!(:topic) { Fabricate(:topic) } @@ -164,16 +166,20 @@ RSpec.describe ComposerMessagesFinder do end it "does not give a message for new topics" do - finder = ComposerMessagesFinder.new(user, composer_action: 'createTopic') + finder = ComposerMessagesFinder.new(user, composer_action: "createTopic") expect(finder.check_sequential_replies).to be_blank end it "does not give a message without a topic id" do - expect(ComposerMessagesFinder.new(user, composer_action: 'reply').check_sequential_replies).to be_blank + expect( + ComposerMessagesFinder.new(user, composer_action: "reply").check_sequential_replies, + ).to be_blank end context "with reply" do - let(:finder) { ComposerMessagesFinder.new(user, composer_action: 'reply', topic_id: topic.id) } + let(:finder) do + ComposerMessagesFinder.new(user, composer_action: "reply", topic_id: topic.id) + end it "does not give a message to users who are still in the 'education' phase" do user.stubs(:post_count).returns(9) @@ -181,12 +187,20 @@ RSpec.describe ComposerMessagesFinder do end it "doesn't notify a user it has already notified about sequential replies" do - UserHistory.create!(action: UserHistory.actions[:notified_about_sequential_replies], target_user_id: user.id, topic_id: topic.id) + UserHistory.create!( + action: UserHistory.actions[:notified_about_sequential_replies], + target_user_id: user.id, + topic_id: topic.id, + ) expect(finder.check_sequential_replies).to be_blank end it "will notify you if it hasn't in the current topic" do - UserHistory.create!(action: UserHistory.actions[:notified_about_sequential_replies], target_user_id: user.id, topic_id: topic.id + 1) + UserHistory.create!( + action: UserHistory.actions[:notified_about_sequential_replies], + target_user_id: user.id, + topic_id: topic.id + 1, + ) expect(finder.check_sequential_replies).to be_present end @@ -215,13 +229,11 @@ RSpec.describe ComposerMessagesFinder do it "creates a notified_about_sequential_replies log" do expect(UserHistory.exists_for_user?(user, :notified_about_sequential_replies)).to eq(true) end - end end - end - describe '.check_dominating_topic' do + describe ".check_dominating_topic" do fab!(:user) { Fabricate(:user) } fab!(:topic) { Fabricate(:topic) } @@ -239,16 +251,20 @@ RSpec.describe ComposerMessagesFinder do end it "does not give a message for new topics" do - finder = ComposerMessagesFinder.new(user, composer_action: 'createTopic') + finder = ComposerMessagesFinder.new(user, composer_action: "createTopic") expect(finder.check_dominating_topic).to be_blank end it "does not give a message without a topic id" do - expect(ComposerMessagesFinder.new(user, composer_action: 'reply').check_dominating_topic).to be_blank + expect( + ComposerMessagesFinder.new(user, composer_action: "reply").check_dominating_topic, + ).to be_blank end context "with reply" do - let(:finder) { ComposerMessagesFinder.new(user, composer_action: 'reply', topic_id: topic.id) } + let(:finder) do + ComposerMessagesFinder.new(user, composer_action: "reply", topic_id: topic.id) + end it "does not give a message to users who are still in the 'education' phase" do user.stubs(:post_count).returns(9) @@ -261,12 +277,20 @@ RSpec.describe ComposerMessagesFinder do end it "doesn't notify a user it has already notified in this topic" do - UserHistory.create!(action: UserHistory.actions[:notified_about_dominating_topic], topic_id: topic.id, target_user_id: user.id) + UserHistory.create!( + action: UserHistory.actions[:notified_about_dominating_topic], + topic_id: topic.id, + target_user_id: user.id, + ) expect(finder.check_dominating_topic).to be_blank end it "notifies a user if the topic is different" do - UserHistory.create!(action: UserHistory.actions[:notified_about_dominating_topic], topic_id: topic.id + 1, target_user_id: user.id) + UserHistory.create!( + action: UserHistory.actions[:notified_about_dominating_topic], + topic_id: topic.id + 1, + target_user_id: user.id, + ) expect(finder.check_dominating_topic).to be_present end @@ -300,13 +324,11 @@ RSpec.describe ComposerMessagesFinder do it "creates a notified_about_dominating_topic log" do expect(UserHistory.exists_for_user?(user, :notified_about_dominating_topic)).to eq(true) end - end end - end - describe '.check_get_a_room' do + describe ".check_get_a_room" do fab!(:user) { Fabricate(:user) } fab!(:other_user) { Fabricate(:user) } fab!(:third_user) { Fabricate(:user) } @@ -317,13 +339,9 @@ RSpec.describe ComposerMessagesFinder do Fabricate(:post, topic: topic, user: third_user, reply_to_user_id: op.user_id) end - fab!(:first_reply) do - Fabricate(:post, topic: topic, user: user, reply_to_user_id: op.user_id) - end + fab!(:first_reply) { Fabricate(:post, topic: topic, user: user, reply_to_user_id: op.user_id) } - fab!(:second_reply) do - Fabricate(:post, topic: topic, user: user, reply_to_user_id: op.user_id) - end + fab!(:second_reply) { Fabricate(:post, topic: topic, user: user, reply_to_user_id: op.user_id) } before do SiteSetting.educate_until_posts = 10 @@ -332,23 +350,40 @@ RSpec.describe ComposerMessagesFinder do end it "does not show the message for new topics" do - finder = ComposerMessagesFinder.new(user, composer_action: 'createTopic') + finder = ComposerMessagesFinder.new(user, composer_action: "createTopic") expect(finder.check_get_a_room(min_users_posted: 2)).to be_blank end it "does not give a message without a topic id" do - expect(ComposerMessagesFinder.new(user, composer_action: 'reply').check_get_a_room(min_users_posted: 2)).to be_blank + expect( + ComposerMessagesFinder.new(user, composer_action: "reply").check_get_a_room( + min_users_posted: 2, + ), + ).to be_blank end it "does not give a message if the topic's category is read_restricted" do topic.category.update(read_restricted: true) - finder = ComposerMessagesFinder.new(user, composer_action: 'reply', topic_id: topic.id, post_id: op.id) + finder = + ComposerMessagesFinder.new( + user, + composer_action: "reply", + topic_id: topic.id, + post_id: op.id, + ) finder.check_get_a_room(min_users_posted: 2) expect(UserHistory.exists_for_user?(user, :notified_about_get_a_room)).to eq(false) end context "with reply" do - let(:finder) { ComposerMessagesFinder.new(user, composer_action: 'reply', topic_id: topic.id, post_id: op.id) } + let(:finder) do + ComposerMessagesFinder.new( + user, + composer_action: "reply", + topic_id: topic.id, + post_id: op.id, + ) + end it "does not give a message to users who are still in the 'education' phase" do user.stubs(:post_count).returns(9) @@ -359,7 +394,7 @@ RSpec.describe ComposerMessagesFinder do UserHistory.create!( action: UserHistory.actions[:notified_about_get_a_room], target_user_id: user.id, - topic_id: topic.id + topic_id: topic.id, ) expect(finder.check_get_a_room(min_users_posted: 2)).to be_blank end @@ -368,7 +403,7 @@ RSpec.describe ComposerMessagesFinder do UserHistory.create!( action: UserHistory.actions[:notified_about_get_a_room], target_user_id: user.id, - topic_id: topic.id + 1 + topic_id: topic.id + 1, ) expect(finder.check_get_a_room(min_users_posted: 2)).to be_present end @@ -390,17 +425,18 @@ RSpec.describe ComposerMessagesFinder do end it "doesn't notify in a message" do - topic.update_columns(category_id: nil, archetype: 'private_message') + topic.update_columns(category_id: nil, archetype: "private_message") expect(finder.check_get_a_room(min_users_posted: 2)).to be_blank end it "doesn't notify when replying to a different user" do - other_finder = ComposerMessagesFinder.new( - user, - composer_action: 'reply', - topic_id: topic.id, - post_id: other_user_reply.id - ) + other_finder = + ComposerMessagesFinder.new( + user, + composer_action: "reply", + topic_id: topic.id, + post_id: other_user_reply.id, + ) expect(other_finder.check_get_a_room(min_users_posted: 2)).to be_blank end @@ -418,79 +454,94 @@ RSpec.describe ComposerMessagesFinder do it "works as expected" do expect(message).to be_present - expect(message[:id]).to eq('get_a_room') + expect(message[:id]).to eq("get_a_room") expect(message[:wait_for_typing]).to eq(true) - expect(message[:templateName]).to eq('get-a-room') + expect(message[:templateName]).to eq("get-a-room") expect(UserHistory.exists_for_user?(user, :notified_about_get_a_room)).to eq(true) end end end - end - describe '.check_reviving_old_topic' do - fab!(:user) { Fabricate(:user) } + describe ".check_reviving_old_topic" do + fab!(:user) { Fabricate(:user) } fab!(:topic) { Fabricate(:topic) } it "does not give a message without a topic id" do - expect(described_class.new(user, composer_action: 'createTopic').check_reviving_old_topic).to be_blank - expect(described_class.new(user, composer_action: 'reply').check_reviving_old_topic).to be_blank + expect( + described_class.new(user, composer_action: "createTopic").check_reviving_old_topic, + ).to be_blank + expect( + described_class.new(user, composer_action: "reply").check_reviving_old_topic, + ).to be_blank end context "with a reply" do context "when warn_reviving_old_topic_age is 180 days" do - before do - SiteSetting.warn_reviving_old_topic_age = 180 - end + before { SiteSetting.warn_reviving_old_topic_age = 180 } it "does not notify if last post is recent" do topic = Fabricate(:topic, last_posted_at: 1.hour.ago) - expect(described_class.new(user, composer_action: 'reply', topic_id: topic.id).check_reviving_old_topic).to be_blank + expect( + described_class.new( + user, + composer_action: "reply", + topic_id: topic.id, + ).check_reviving_old_topic, + ).to be_blank end it "notifies if last post is old" do topic = Fabricate(:topic, last_posted_at: 181.days.ago) - message = described_class.new(user, composer_action: 'reply', topic_id: topic.id).check_reviving_old_topic + message = + described_class.new( + user, + composer_action: "reply", + topic_id: topic.id, + ).check_reviving_old_topic expect(message).not_to be_blank expect(message[:body]).to match(/6 months ago/) end end context "when warn_reviving_old_topic_age is 0" do - before do - SiteSetting.warn_reviving_old_topic_age = 0 - end + before { SiteSetting.warn_reviving_old_topic_age = 0 } it "does not notify if last post is new" do topic = Fabricate(:topic, last_posted_at: 1.hour.ago) - expect(described_class.new(user, composer_action: 'reply', topic_id: topic.id).check_reviving_old_topic).to be_blank + expect( + described_class.new( + user, + composer_action: "reply", + topic_id: topic.id, + ).check_reviving_old_topic, + ).to be_blank end it "does not notify if last post is old" do topic = Fabricate(:topic, last_posted_at: 365.days.ago) - expect(described_class.new(user, composer_action: 'reply', topic_id: topic.id).check_reviving_old_topic).to be_blank + expect( + described_class.new( + user, + composer_action: "reply", + topic_id: topic.id, + ).check_reviving_old_topic, + ).to be_blank end end end end - context 'when editing a post' do + context "when editing a post" do fab!(:user) { Fabricate(:user) } fab!(:topic) { Fabricate(:post).topic } let!(:post) do - PostCreator.create!( - user, - topic_id: topic.id, - post_number: 1, - raw: 'omg my first post' - ) + PostCreator.create!(user, topic_id: topic.id, post_number: 1, raw: "omg my first post") end - let(:edit_post_finder) do - ComposerMessagesFinder.new(user, composer_action: 'edit') - end + let(:edit_post_finder) { ComposerMessagesFinder.new(user, composer_action: "edit") } before do SiteSetting.disable_avatar_education_message = true @@ -502,25 +553,28 @@ RSpec.describe ComposerMessagesFinder do end end - describe '#user_not_seen_in_a_while' do + describe "#user_not_seen_in_a_while" do fab!(:user_1) { Fabricate(:user, last_seen_at: 3.years.ago) } fab!(:user_2) { Fabricate(:user, last_seen_at: 2.years.ago) } fab!(:user_3) { Fabricate(:user, last_seen_at: 6.months.ago) } - before do - SiteSetting.pm_warn_user_last_seen_months_ago = 24 - end + before { SiteSetting.pm_warn_user_last_seen_months_ago = 24 } - it 'returns users that have not been seen recently' do - users = ComposerMessagesFinder.user_not_seen_in_a_while([user_1.username, user_2.username, user_3.username]) + it "returns users that have not been seen recently" do + users = + ComposerMessagesFinder.user_not_seen_in_a_while( + [user_1.username, user_2.username, user_3.username], + ) expect(users).to contain_exactly(user_1.username, user_2.username) end - it 'accounts for pm_warn_user_last_seen_months_ago site setting' do + it "accounts for pm_warn_user_last_seen_months_ago site setting" do SiteSetting.pm_warn_user_last_seen_months_ago = 30 - users = ComposerMessagesFinder.user_not_seen_in_a_while([user_1.username, user_2.username, user_3.username]) + users = + ComposerMessagesFinder.user_not_seen_in_a_while( + [user_1.username, user_2.username, user_3.username], + ) expect(users).to contain_exactly(user_1.username) end end - end diff --git a/spec/lib/compression/engine_spec.rb b/spec/lib/compression/engine_spec.rb index 6ca9a969ed..87cbed2488 100644 --- a/spec/lib/compression/engine_spec.rb +++ b/spec/lib/compression/engine_spec.rb @@ -2,7 +2,7 @@ RSpec.describe Compression::Engine do let(:available_size) { SiteSetting.decompressed_theme_max_file_size_mb } - let(:folder_name) { 'test' } + let(:folder_name) { "test" } let(:temp_folder) do path = "#{Pathname.new(Dir.tmpdir).realpath}/#{SecureRandom.hex}" FileUtils.mkdir(path) @@ -12,30 +12,36 @@ RSpec.describe Compression::Engine do before do Dir.chdir(temp_folder) do FileUtils.mkdir_p("#{folder_name}/a") - File.write("#{folder_name}/hello.txt", 'hello world') - File.write("#{folder_name}/a/inner", 'hello world inner') + File.write("#{folder_name}/hello.txt", "hello world") + File.write("#{folder_name}/a/inner", "hello world inner") end end after { FileUtils.rm_rf(temp_folder) } - it 'raises an exception when the file is not supported' do - unknown_extension = 'a_file.crazyext' - expect { described_class.engine_for(unknown_extension) }.to raise_error Compression::Engine::UnsupportedFileExtension + it "raises an exception when the file is not supported" do + unknown_extension = "a_file.crazyext" + expect { + described_class.engine_for(unknown_extension) + }.to raise_error Compression::Engine::UnsupportedFileExtension end - describe 'compressing and decompressing files' do + describe "compressing and decompressing files" do before do Dir.chdir(temp_folder) do - @compressed_path = Compression::Engine.engine_for("#{folder_name}#{extension}").compress(temp_folder, folder_name) + @compressed_path = + Compression::Engine.engine_for("#{folder_name}#{extension}").compress( + temp_folder, + folder_name, + ) FileUtils.rm_rf("#{folder_name}/") end end - context 'when working with zip files' do - let(:extension) { '.zip' } + context "when working with zip files" do + let(:extension) { ".zip" } - it 'decompresses the folder and inspects files correctly' do + it "decompresses the folder and inspects files correctly" do engine = described_class.engine_for(@compressed_path) extract_location = "#{temp_folder}/extract_location" @@ -52,16 +58,12 @@ RSpec.describe Compression::Engine do zip_file = "#{temp_folder}/theme.zip" Zip::File.open(zip_file, create: true) do |zipfile| - zipfile.get_output_stream("child-file") do |f| - f.puts("child file") - end + zipfile.get_output_stream("child-file") { |f| f.puts("child file") } zipfile.get_output_stream("../escape-decompression-folder.txt") do |f| f.puts("file that attempts to escape the decompression destination directory") end zipfile.mkdir("child-dir") - zipfile.get_output_stream("child-dir/grandchild-file") do |f| - f.puts("grandchild file") - end + zipfile.get_output_stream("child-dir/grandchild-file") { |f| f.puts("grandchild file") } end extract_location = "#{temp_folder}/extract_location" @@ -74,7 +76,7 @@ RSpec.describe Compression::Engine do "extract_location/child-file", "extract_location/child-dir", "extract_location/child-dir/grandchild-file", - "theme.zip" + "theme.zip", ) end end @@ -97,10 +99,10 @@ RSpec.describe Compression::Engine do end end - context 'when working with .tar.gz files' do - let(:extension) { '.tar.gz' } + context "when working with .tar.gz files" do + let(:extension) { ".tar.gz" } - it 'decompresses the folder and inspects files correctly' do + it "decompresses the folder and inspects files correctly" do engine = described_class.engine_for(@compressed_path) engine.decompress(temp_folder, "#{temp_folder}/#{folder_name}.tar.gz", available_size) @@ -116,16 +118,12 @@ RSpec.describe Compression::Engine do tar_file = "#{temp_folder}/theme.tar" File.open(tar_file, "wb") do |file| Gem::Package::TarWriter.new(file) do |tar| - tar.add_file("child-file", 644) do |tf| - tf.write("child file") - end + tar.add_file("child-file", 644) { |tf| tf.write("child file") } tar.add_file("../escape-extraction-folder", 644) do |tf| tf.write("file that attempts to escape the decompression destination directory") end tar.mkdir("child-dir", 755) - tar.add_file("child-dir/grandchild-file", 644) do |tf| - tf.write("grandchild file") - end + tar.add_file("child-dir/grandchild-file", 644) { |tf| tf.write("grandchild file") } end end tar_gz_file = "#{temp_folder}/theme.tar.gz" @@ -167,10 +165,10 @@ RSpec.describe Compression::Engine do end end - context 'when working with .tar files' do - let(:extension) { '.tar' } + context "when working with .tar files" do + let(:extension) { ".tar" } - it 'decompress the folder and inspect files correctly' do + it "decompress the folder and inspect files correctly" do engine = described_class.engine_for(@compressed_path) engine.decompress(temp_folder, "#{temp_folder}/#{folder_name}.tar", available_size) diff --git a/spec/lib/concern/cached_counting_spec.rb b/spec/lib/concern/cached_counting_spec.rb index 260744902d..041a4c3c24 100644 --- a/spec/lib/concern/cached_counting_spec.rb +++ b/spec/lib/concern/cached_counting_spec.rb @@ -39,7 +39,6 @@ RSpec.describe CachedCounting do end it "can dispatch counts to backing class" do - CachedCounting.queue("a,a", TestCachedCounting) CachedCounting.queue("a,a", TestCachedCounting) CachedCounting.queue("b", TestCachedCounting) @@ -48,7 +47,6 @@ RSpec.describe CachedCounting do CachedCounting.flush_to_db expect(TestCachedCounting.data).to eq({ "a,a" => 2, "b" => 1 }) - end end end @@ -76,20 +74,15 @@ RSpec.describe CachedCounting do CachedCounting.enable end - after do - CachedCounting.disable - end + after { CachedCounting.disable } it "can dispatch data via background thread" do - freeze_time d1 = Time.now.utc.to_date RailsCacheCounter.perform_increment!("a,a") RailsCacheCounter.perform_increment!("b") - 20.times do - RailsCacheCounter.perform_increment!("a,a") - end + 20.times { RailsCacheCounter.perform_increment!("a,a") } freeze_time 2.days.from_now d2 = Time.now.utc.to_date @@ -99,12 +92,7 @@ RSpec.describe CachedCounting do CachedCounting.flush - expected = { - ["a,a", d1] => 21, - ["b", d1] => 1, - ["a,a", d2] => 1, - ["d", d2] => 1, - } + expected = { ["a,a", d1] => 21, ["b", d1] => 1, ["a,a", d2] => 1, ["d", d2] => 1 } expect(RailsCacheCounter.cache_data).to eq(expected) end diff --git a/spec/lib/concern/category_hashtag_spec.rb b/spec/lib/concern/category_hashtag_spec.rb index 57d687beb2..9a815653d5 100644 --- a/spec/lib/concern/category_hashtag_spec.rb +++ b/spec/lib/concern/category_hashtag_spec.rb @@ -1,18 +1,20 @@ # frozen_string_literal: true RSpec.describe CategoryHashtag do - describe '#query_from_hashtag_slug' do + describe "#query_from_hashtag_slug" do fab!(:parent_category) { Fabricate(:category) } fab!(:child_category) { Fabricate(:category, parent_category: parent_category) } it "should return the right result for a parent category slug" do - expect(Category.query_from_hashtag_slug(parent_category.slug)) - .to eq(parent_category) + expect(Category.query_from_hashtag_slug(parent_category.slug)).to eq(parent_category) end it "should return the right result for a parent and child category slug" do - expect(Category.query_from_hashtag_slug("#{parent_category.slug}#{CategoryHashtag::SEPARATOR}#{child_category.slug}")) - .to eq(child_category) + expect( + Category.query_from_hashtag_slug( + "#{parent_category.slug}#{CategoryHashtag::SEPARATOR}#{child_category.slug}", + ), + ).to eq(child_category) end it "should return nil for incorrect parent category slug" do @@ -20,22 +22,29 @@ RSpec.describe CategoryHashtag do end it "should return nil for incorrect parent and child category slug" do - expect(Category.query_from_hashtag_slug("random-slug#{CategoryHashtag::SEPARATOR}random-slug")).to eq(nil) + expect( + Category.query_from_hashtag_slug("random-slug#{CategoryHashtag::SEPARATOR}random-slug"), + ).to eq(nil) end it "should return nil for a non-existent root and a parent subcategory" do - expect(Category.query_from_hashtag_slug("non-existent#{CategoryHashtag::SEPARATOR}#{parent_category.slug}")).to eq(nil) + expect( + Category.query_from_hashtag_slug( + "non-existent#{CategoryHashtag::SEPARATOR}#{parent_category.slug}", + ), + ).to eq(nil) end context "with multi-level categories" do - before do - SiteSetting.max_category_nesting = 3 - end + before { SiteSetting.max_category_nesting = 3 } it "should return the right result for a grand child category slug" do category = Fabricate(:category, parent_category: child_category) - expect(Category.query_from_hashtag_slug("#{child_category.slug}#{CategoryHashtag::SEPARATOR}#{category.slug}")) - .to eq(category) + expect( + Category.query_from_hashtag_slug( + "#{child_category.slug}#{CategoryHashtag::SEPARATOR}#{category.slug}", + ), + ).to eq(category) end end end diff --git a/spec/lib/concern/has_custom_fields_spec.rb b/spec/lib/concern/has_custom_fields_spec.rb index af0bbaebd8..0557193c61 100644 --- a/spec/lib/concern/has_custom_fields_spec.rb +++ b/spec/lib/concern/has_custom_fields_spec.rb @@ -4,7 +4,9 @@ RSpec.describe HasCustomFields do describe "custom_fields" do before do DB.exec("create temporary table custom_fields_test_items(id SERIAL primary key)") - DB.exec("create temporary table custom_fields_test_item_custom_fields(id SERIAL primary key, custom_fields_test_item_id int, name varchar(256) not null, value text, created_at TIMESTAMP, updated_at TIMESTAMP)") + DB.exec( + "create temporary table custom_fields_test_item_custom_fields(id SERIAL primary key, custom_fields_test_item_id int, name varchar(256) not null, value text, created_at TIMESTAMP, updated_at TIMESTAMP)", + ) DB.exec(<<~SQL) CREATE UNIQUE INDEX ON custom_fields_test_item_custom_fields (custom_fields_test_item_id) WHERE NAME = 'rare' @@ -38,7 +40,9 @@ RSpec.describe HasCustomFields do it "errors if a custom field is not preloaded" do test_item = CustomFieldsTestItem.new CustomFieldsTestItem.preload_custom_fields([test_item], ["test_field"]) - expect { test_item.custom_fields["other_field"] }.to raise_error(HasCustomFields::NotPreloadedError) + expect { test_item.custom_fields["other_field"] }.to raise_error( + HasCustomFields::NotPreloadedError, + ) end it "resets the preloaded_custom_fields if preload_custom_fields is called twice" do @@ -105,7 +109,10 @@ RSpec.describe HasCustomFields do # should be casted right after saving expect(test_item.custom_fields["a"]).to eq("0") - DB.exec("UPDATE custom_fields_test_item_custom_fields SET value='1' WHERE custom_fields_test_item_id=? AND name='a'", test_item.id) + DB.exec( + "UPDATE custom_fields_test_item_custom_fields SET value='1' WHERE custom_fields_test_item_id=? AND name='a'", + test_item.id, + ) # still the same, did not load expect(test_item.custom_fields["a"]).to eq("0") @@ -137,25 +144,25 @@ RSpec.describe HasCustomFields do expect(db_item.custom_fields).to eq("array" => [1]) test_item = CustomFieldsTestItem.new - test_item.custom_fields = { "a" => ["b", "c", "d"] } + test_item.custom_fields = { "a" => %w[b c d] } test_item.save db_item = CustomFieldsTestItem.find(test_item.id) - expect(db_item.custom_fields).to eq("a" => ["b", "c", "d"]) + expect(db_item.custom_fields).to eq("a" => %w[b c d]) - db_item.custom_fields.update('a' => ['c', 'd']) + db_item.custom_fields.update("a" => %w[c d]) db_item.save - expect(db_item.custom_fields).to eq("a" => ["c", "d"]) + expect(db_item.custom_fields).to eq("a" => %w[c d]) # It can be updated to the exact same value - db_item.custom_fields.update('a' => ['c']) + db_item.custom_fields.update("a" => ["c"]) db_item.save expect(db_item.custom_fields).to eq("a" => "c") - db_item.custom_fields.update('a' => ['c']) + db_item.custom_fields.update("a" => ["c"]) db_item.save expect(db_item.custom_fields).to eq("a" => "c") - db_item.custom_fields.delete('a') + db_item.custom_fields.delete("a") expect(db_item.custom_fields).to eq({}) end @@ -176,10 +183,10 @@ RSpec.describe HasCustomFields do test_item = CustomFieldsTestItem.new test_item.custom_fields = { "a" => ["b", 10, "d"] } test_item.save - expect(test_item.custom_fields).to eq("a" => ["b", "10", "d"]) + expect(test_item.custom_fields).to eq("a" => %w[b 10 d]) db_item = CustomFieldsTestItem.find(test_item.id) - expect(db_item.custom_fields).to eq("a" => ["b", "10", "d"]) + expect(db_item.custom_fields).to eq("a" => %w[b 10 d]) end it "supports type coercion" do @@ -192,14 +199,22 @@ RSpec.describe HasCustomFields do test_item.save test_item.reload - expect(test_item.custom_fields).to eq("bool" => true, "int" => 1, "json" => { "foo" => "bar" }) + expect(test_item.custom_fields).to eq( + "bool" => true, + "int" => 1, + "json" => { + "foo" => "bar", + }, + ) - before_ids = CustomFieldsTestItemCustomField.where(custom_fields_test_item_id: test_item.id).pluck(:id) + before_ids = + CustomFieldsTestItemCustomField.where(custom_fields_test_item_id: test_item.id).pluck(:id) test_item.custom_fields["bool"] = false test_item.save - after_ids = CustomFieldsTestItemCustomField.where(custom_fields_test_item_id: test_item.id).pluck(:id) + after_ids = + CustomFieldsTestItemCustomField.where(custom_fields_test_item_id: test_item.id).pluck(:id) # we updated only 1 custom field, so there should be only 1 different id expect((before_ids - after_ids).size).to eq(1) @@ -234,23 +249,19 @@ RSpec.describe HasCustomFields do CustomFieldsTestItem.register_custom_field_type(field_type, :json) item = CustomFieldsTestItem.new - item.custom_fields = { - "json_array" => [{ a: "test" }, { b: "another" }] - } + item.custom_fields = { "json_array" => [{ a: "test" }, { b: "another" }] } item.save item.reload - expect(item.custom_fields[field_type]).to eq( - [{ "a" => "test" }, { "b" => "another" }] - ) + expect(item.custom_fields[field_type]).to eq([{ "a" => "test" }, { "b" => "another" }]) - item.custom_fields["json_array"] = ['a', 'b'] + item.custom_fields["json_array"] = %w[a b] item.save item.reload - expect(item.custom_fields[field_type]).to eq(["a", "b"]) + expect(item.custom_fields[field_type]).to eq(%w[a b]) end it "will not fail to load custom fields if json is corrupt" do @@ -262,7 +273,7 @@ RSpec.describe HasCustomFields do CustomFieldsTestItemCustomField.create!( custom_fields_test_item_id: item.id, name: field_type, - value: "{test" + value: "{test", ) item = item.reload @@ -271,34 +282,34 @@ RSpec.describe HasCustomFields do it "supports bulk retrieval with a list of ids" do item1 = CustomFieldsTestItem.new - item1.custom_fields = { "a" => ["b", "c", "d"], 'not_allowlisted' => 'secret' } + item1.custom_fields = { "a" => %w[b c d], "not_allowlisted" => "secret" } item1.save item2 = CustomFieldsTestItem.new - item2.custom_fields = { "e" => 'hallo' } + item2.custom_fields = { "e" => "hallo" } item2.save - fields = CustomFieldsTestItem.custom_fields_for_ids([item1.id, item2.id], ['a', 'e']) + fields = CustomFieldsTestItem.custom_fields_for_ids([item1.id, item2.id], %w[a e]) expect(fields).to be_present - expect(fields[item1.id]['a']).to match_array(['b', 'c', 'd']) - expect(fields[item1.id]['not_allowlisted']).to be_blank - expect(fields[item2.id]['e']).to eq('hallo') + expect(fields[item1.id]["a"]).to match_array(%w[b c d]) + expect(fields[item1.id]["not_allowlisted"]).to be_blank + expect(fields[item2.id]["e"]).to eq("hallo") end it "handles interleaving saving properly" do - field_type = 'deep-nest-test' + field_type = "deep-nest-test" CustomFieldsTestItem.register_custom_field_type(field_type, :json) test_item = CustomFieldsTestItem.create! test_item.custom_fields[field_type] ||= {} - test_item.custom_fields[field_type]['b'] ||= {} - test_item.custom_fields[field_type]['b']['c'] = 'd' + test_item.custom_fields[field_type]["b"] ||= {} + test_item.custom_fields[field_type]["b"]["c"] = "d" test_item.save_custom_fields(true) db_item = CustomFieldsTestItem.find(test_item.id) - db_item.custom_fields[field_type]['b']['e'] = 'f' - test_item.custom_fields[field_type]['b']['e'] = 'f' - expected = { field_type => { 'b' => { 'c' => 'd', 'e' => 'f' } } } + db_item.custom_fields[field_type]["b"]["e"] = "f" + test_item.custom_fields[field_type]["b"]["e"] = "f" + expected = { field_type => { "b" => { "c" => "d", "e" => "f" } } } db_item.save_custom_fields(true) expect(db_item.reload.custom_fields).to eq(expected) @@ -307,7 +318,7 @@ RSpec.describe HasCustomFields do expect(test_item.reload.custom_fields).to eq(expected) end - it 'determines clean state correctly for mutable fields' do + it "determines clean state correctly for mutable fields" do json_field = "json_field" array_field = "array_field" CustomFieldsTestItem.register_custom_field_type(json_field, :json) @@ -339,94 +350,94 @@ RSpec.describe HasCustomFields do describe "create_singular" do it "creates new records" do item = CustomFieldsTestItem.create! - item.create_singular('hello', 'world') - expect(item.reload.custom_fields['hello']).to eq('world') + item.create_singular("hello", "world") + expect(item.reload.custom_fields["hello"]).to eq("world") end it "upserts on a database constraint error" do item0 = CustomFieldsTestItem.new item0.custom_fields = { "rare" => "gem" } item0.save - expect(item0.reload.custom_fields['rare']).to eq("gem") + expect(item0.reload.custom_fields["rare"]).to eq("gem") - item0.create_singular('rare', "diamond") - expect(item0.reload.custom_fields['rare']).to eq("diamond") + item0.create_singular("rare", "diamond") + expect(item0.reload.custom_fields["rare"]).to eq("diamond") end end describe "upsert_custom_fields" do - it 'upserts records' do + it "upserts records" do test_item = CustomFieldsTestItem.create - test_item.upsert_custom_fields('hello' => 'world', 'abc' => 'def') + test_item.upsert_custom_fields("hello" => "world", "abc" => "def") # In memory - expect(test_item.custom_fields['hello']).to eq('world') - expect(test_item.custom_fields['abc']).to eq('def') + expect(test_item.custom_fields["hello"]).to eq("world") + expect(test_item.custom_fields["abc"]).to eq("def") # Persisted test_item.reload - expect(test_item.custom_fields['hello']).to eq('world') - expect(test_item.custom_fields['abc']).to eq('def') + expect(test_item.custom_fields["hello"]).to eq("world") + expect(test_item.custom_fields["abc"]).to eq("def") # In memory - test_item.upsert_custom_fields('abc' => 'ghi') - expect(test_item.custom_fields['hello']).to eq('world') - expect(test_item.custom_fields['abc']).to eq('ghi') + test_item.upsert_custom_fields("abc" => "ghi") + expect(test_item.custom_fields["hello"]).to eq("world") + expect(test_item.custom_fields["abc"]).to eq("ghi") # Persisted test_item.reload - expect(test_item.custom_fields['hello']).to eq('world') - expect(test_item.custom_fields['abc']).to eq('ghi') + expect(test_item.custom_fields["hello"]).to eq("world") + expect(test_item.custom_fields["abc"]).to eq("ghi") end - it 'allows upsert to use keywords' do + it "allows upsert to use keywords" do test_item = CustomFieldsTestItem.create - test_item.upsert_custom_fields(hello: 'world', abc: 'def') + test_item.upsert_custom_fields(hello: "world", abc: "def") # In memory - expect(test_item.custom_fields['hello']).to eq('world') - expect(test_item.custom_fields['abc']).to eq('def') + expect(test_item.custom_fields["hello"]).to eq("world") + expect(test_item.custom_fields["abc"]).to eq("def") # Persisted test_item.reload - expect(test_item.custom_fields['hello']).to eq('world') - expect(test_item.custom_fields['abc']).to eq('def') + expect(test_item.custom_fields["hello"]).to eq("world") + expect(test_item.custom_fields["abc"]).to eq("def") # In memory - test_item.upsert_custom_fields('abc' => 'ghi') - expect(test_item.custom_fields['hello']).to eq('world') - expect(test_item.custom_fields['abc']).to eq('ghi') + test_item.upsert_custom_fields("abc" => "ghi") + expect(test_item.custom_fields["hello"]).to eq("world") + expect(test_item.custom_fields["abc"]).to eq("ghi") # Persisted test_item.reload - expect(test_item.custom_fields['hello']).to eq('world') - expect(test_item.custom_fields['abc']).to eq('ghi') + expect(test_item.custom_fields["hello"]).to eq("world") + expect(test_item.custom_fields["abc"]).to eq("ghi") end - it 'allows using string and symbol indices interchangeably' do + it "allows using string and symbol indices interchangeably" do test_item = CustomFieldsTestItem.new test_item.custom_fields["bob"] = "marley" test_item.custom_fields["jack"] = "black" # In memory - expect(test_item.custom_fields[:bob]).to eq('marley') - expect(test_item.custom_fields[:jack]).to eq('black') + expect(test_item.custom_fields[:bob]).to eq("marley") + expect(test_item.custom_fields[:jack]).to eq("black") # Persisted test_item.save test_item.reload - expect(test_item.custom_fields[:bob]).to eq('marley') - expect(test_item.custom_fields[:jack]).to eq('black') + expect(test_item.custom_fields[:bob]).to eq("marley") + expect(test_item.custom_fields[:jack]).to eq("black") # Update via string index again - test_item.custom_fields['bob'] = 'the builder' + test_item.custom_fields["bob"] = "the builder" - expect(test_item.custom_fields[:bob]).to eq('the builder') + expect(test_item.custom_fields[:bob]).to eq("the builder") test_item.save test_item.reload - expect(test_item.custom_fields[:bob]).to eq('the builder') + expect(test_item.custom_fields[:bob]).to eq("the builder") end end end diff --git a/spec/lib/concern/has_search_data_spec.rb b/spec/lib/concern/has_search_data_spec.rb index 74f4fbb66d..b7e1f57942 100644 --- a/spec/lib/concern/has_search_data_spec.rb +++ b/spec/lib/concern/has_search_data_spec.rb @@ -4,7 +4,9 @@ RSpec.describe HasSearchData do describe "belongs to its model" do before do DB.exec("create temporary table model_items(id SERIAL primary key)") - DB.exec("create temporary table model_item_search_data(model_item_id int primary key, search_data tsvector, raw_data text, locale text)") + DB.exec( + "create temporary table model_item_search_data(model_item_id int primary key, search_data tsvector, raw_data text, locale text)", + ) class ModelItem < ActiveRecord::Base has_one :model_item_search_data, dependent: :destroy @@ -29,17 +31,18 @@ RSpec.describe HasSearchData do item = ModelItem.create! item.create_model_item_search_data!( model_item_id: item.id, - search_data: 'a', - raw_data: 'a', - locale: 'en') + search_data: "a", + raw_data: "a", + locale: "en", + ) item end - it 'sets its primary key into associated model' do - expect(ModelItemSearchData.primary_key).to eq 'model_item_id' + it "sets its primary key into associated model" do + expect(ModelItemSearchData.primary_key).to eq "model_item_id" end - it 'can access the model' do + it "can access the model" do record_id = item.id expect(ModelItemSearchData.find_by(model_item_id: record_id).model_item_id).to eq record_id end diff --git a/spec/lib/concern/positionable_spec.rb b/spec/lib/concern/positionable_spec.rb index f4a5746386..7beb129820 100644 --- a/spec/lib/concern/positionable_spec.rb +++ b/spec/lib/concern/positionable_spec.rb @@ -2,7 +2,7 @@ RSpec.describe Positionable do def positions - TestItem.order('position asc, id asc').pluck(:id) + TestItem.order("position asc, id asc").pluck(:id) end describe "move_to" do @@ -23,9 +23,7 @@ RSpec.describe Positionable do end it "can position stuff correctly" do - 5.times do |i| - DB.exec("insert into test_items(id,position) values(#{i}, #{i})") - end + 5.times { |i| DB.exec("insert into test_items(id,position) values(#{i}, #{i})") } expect(positions).to eq([0, 1, 2, 3, 4]) TestItem.find(3).move_to(0) diff --git a/spec/lib/concern/searchable_spec.rb b/spec/lib/concern/searchable_spec.rb index b92b0eb4af..22bb1b5fc2 100644 --- a/spec/lib/concern/searchable_spec.rb +++ b/spec/lib/concern/searchable_spec.rb @@ -4,14 +4,16 @@ RSpec.describe Searchable do describe "has search data" do before do DB.exec("create temporary table searchable_records(id SERIAL primary key)") - DB.exec("create temporary table searchable_record_search_data(searchable_record_id int primary key, search_data tsvector, raw_data text, locale text)") + DB.exec( + "create temporary table searchable_record_search_data(searchable_record_id int primary key, search_data tsvector, raw_data text, locale text)", + ) class SearchableRecord < ActiveRecord::Base include Searchable end class SearchableRecordSearchData < ActiveRecord::Base - self.primary_key = 'searchable_record_id' + self.primary_key = "searchable_record_id" belongs_to :test_item end end @@ -28,26 +30,20 @@ RSpec.describe Searchable do let(:item) { SearchableRecord.create! } - it 'can build the data' do + it "can build the data" do expect(item.build_searchable_record_search_data).to be_truthy end - it 'can save the data' do - item.build_searchable_record_search_data( - search_data: '', - raw_data: 'a', - locale: 'en') + it "can save the data" do + item.build_searchable_record_search_data(search_data: "", raw_data: "a", locale: "en") item.save loaded = SearchableRecord.find(item.id) - expect(loaded.searchable_record_search_data.raw_data).to eq 'a' + expect(loaded.searchable_record_search_data.raw_data).to eq "a" end - it 'destroy the search data when the item is deprived' do - item.build_searchable_record_search_data( - search_data: '', - raw_data: 'a', - locale: 'en') + it "destroy the search data when the item is deprived" do + item.build_searchable_record_search_data(search_data: "", raw_data: "a", locale: "en") item.save item_id = item.id item.destroy diff --git a/spec/lib/concern/second_factor_manager_spec.rb b/spec/lib/concern/second_factor_manager_spec.rb index a30b726e73..80dfeb67a4 100644 --- a/spec/lib/concern/second_factor_manager_spec.rb +++ b/spec/lib/concern/second_factor_manager_spec.rb @@ -8,29 +8,31 @@ RSpec.describe SecondFactorManager do :user_security_key, user: user, public_key: valid_security_key_data[:public_key], - credential_id: valid_security_key_data[:credential_id] + credential_id: valid_security_key_data[:credential_id], ) end fab!(:another_user) { Fabricate(:user) } fab!(:user_second_factor_backup) { Fabricate(:user_second_factor_backup) } - let(:user_backup) { user_second_factor_backup.user } + let(:user_backup) { user_second_factor_backup.user } - describe '#totp' do - it 'should return the right data' do + describe "#totp" do + it "should return the right data" do totp = nil - expect do - totp = another_user.create_totp(enabled: true) - end.to change { UserSecondFactor.count }.by(1) + expect do totp = another_user.create_totp(enabled: true) end.to change { + UserSecondFactor.count + }.by(1) expect(totp.totp_object.issuer).to eq(SiteSetting.title) - expect(totp.totp_object.secret).to eq(another_user.reload.user_second_factors.totps.first.data) + expect(totp.totp_object.secret).to eq( + another_user.reload.user_second_factors.totps.first.data, + ) end end - describe '#create_totp' do - it 'should create the right record' do + describe "#create_totp" do + it "should create the right record" do second_factor = another_user.create_totp(enabled: true) expect(second_factor.method).to eq(UserSecondFactor.methods[:totp]) @@ -39,28 +41,28 @@ RSpec.describe SecondFactorManager do end end - describe '#totp_provisioning_uri' do - it 'should return the right uri' do + describe "#totp_provisioning_uri" do + it "should return the right uri" do expect(user.user_second_factors.totps.first.totp_provisioning_uri).to eq( - "otpauth://totp/#{SiteSetting.title}:#{ERB::Util.url_encode(user.email)}?secret=#{user_second_factor_totp.data}&issuer=#{SiteSetting.title}" + "otpauth://totp/#{SiteSetting.title}:#{ERB::Util.url_encode(user.email)}?secret=#{user_second_factor_totp.data}&issuer=#{SiteSetting.title}", ) end - it 'should handle a colon in the site title' do - SiteSetting.title = 'Spaceballs: The Discourse' + it "should handle a colon in the site title" do + SiteSetting.title = "Spaceballs: The Discourse" expect(user.user_second_factors.totps.first.totp_provisioning_uri).to eq( - "otpauth://totp/Spaceballs%20The%20Discourse:#{ERB::Util.url_encode(user.email)}?secret=#{user_second_factor_totp.data}&issuer=Spaceballs%20The%20Discourse" + "otpauth://totp/Spaceballs%20The%20Discourse:#{ERB::Util.url_encode(user.email)}?secret=#{user_second_factor_totp.data}&issuer=Spaceballs%20The%20Discourse", ) end - it 'should handle a two words before a colon in the title' do - SiteSetting.title = 'Our Spaceballs: The Discourse' + it "should handle a two words before a colon in the title" do + SiteSetting.title = "Our Spaceballs: The Discourse" expect(user.user_second_factors.totps.first.totp_provisioning_uri).to eq( - "otpauth://totp/Our%20Spaceballs%20The%20Discourse:#{ERB::Util.url_encode(user.email)}?secret=#{user_second_factor_totp.data}&issuer=Our%20Spaceballs%20The%20Discourse" + "otpauth://totp/Our%20Spaceballs%20The%20Discourse:#{ERB::Util.url_encode(user.email)}?secret=#{user_second_factor_totp.data}&issuer=Our%20Spaceballs%20The%20Discourse", ) end end - describe '#authenticate_totp' do - it 'should be able to authenticate a token' do + describe "#authenticate_totp" do + it "should be able to authenticate a token" do freeze_time do expect(user.user_second_factors.totps.first.last_used).to eq(nil) @@ -72,52 +74,52 @@ RSpec.describe SecondFactorManager do end end - describe 'when token is blank' do - it 'should be false' do + describe "when token is blank" do + it "should be false" do expect(user.authenticate_totp(nil)).to eq(false) expect(user.user_second_factors.totps.first.last_used).to eq(nil) end end - describe 'when token is invalid' do - it 'should be false' do - expect(user.authenticate_totp('111111')).to eq(false) + describe "when token is invalid" do + it "should be false" do + expect(user.authenticate_totp("111111")).to eq(false) expect(user.user_second_factors.totps.first.last_used).to eq(nil) end end end - describe '#totp_enabled?' do - describe 'when user does not have a second factor record' do - it 'should return false' do + describe "#totp_enabled?" do + describe "when user does not have a second factor record" do + it "should return false" do expect(another_user.totp_enabled?).to eq(false) end end describe "when user's second factor record is disabled" do - it 'should return false' do + it "should return false" do disable_totp expect(user.totp_enabled?).to eq(false) end end describe "when user's second factor record is enabled" do - it 'should return true' do + it "should return true" do expect(user.totp_enabled?).to eq(true) end end - describe 'when SSO is enabled' do - it 'should return false' do - SiteSetting.discourse_connect_url = 'http://someurl.com' + describe "when SSO is enabled" do + it "should return false" do + SiteSetting.discourse_connect_url = "http://someurl.com" SiteSetting.enable_discourse_connect = true expect(user.totp_enabled?).to eq(false) end end - describe 'when local login is disabled' do - it 'should return false' do + describe "when local login is disabled" do + it "should return false" do SiteSetting.enable_local_logins = false expect(user.totp_enabled?).to eq(false) @@ -166,9 +168,7 @@ RSpec.describe SecondFactorManager do let(:secure_session) { {} } context "when neither security keys nor totp/backup codes are enabled" do - before do - disable_security_key && disable_totp - end + before { disable_security_key && disable_totp } it "returns OK, because it doesn't need to authenticate" do expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true) end @@ -186,13 +186,20 @@ RSpec.describe SecondFactorManager do end context "when security key params are valid" do - let(:params) { { second_factor_token: valid_security_key_auth_post_data, second_factor_method: UserSecondFactor.methods[:security_key] } } + let(:params) do + { + second_factor_token: valid_security_key_auth_post_data, + second_factor_method: UserSecondFactor.methods[:security_key], + } + end it "returns OK" do expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true) end it "sets used_2fa_method to security keys" do - expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(UserSecondFactor.methods[:security_key]) + expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq( + UserSecondFactor.methods[:security_key], + ) end end @@ -200,12 +207,12 @@ RSpec.describe SecondFactorManager do let(:params) do { second_factor_token: { - signature: 'bad', - clientData: 'bad', - authenticatorData: 'bad', - credentialId: 'bad' + signature: "bad", + clientData: "bad", + authenticatorData: "bad", + credentialId: "bad", }, - second_factor_method: UserSecondFactor.methods[:security_key] + second_factor_method: UserSecondFactor.methods[:security_key], } end it "returns not OK" do @@ -218,15 +225,13 @@ RSpec.describe SecondFactorManager do end context "when only totp is enabled" do - before do - disable_security_key - end + before { disable_security_key } context "when totp is valid" do let(:params) do { second_factor_token: user.user_second_factors.totps.first.totp_object.now, - second_factor_method: UserSecondFactor.methods[:totp] + second_factor_method: UserSecondFactor.methods[:totp], } end it "returns OK" do @@ -234,16 +239,15 @@ RSpec.describe SecondFactorManager do end it "sets used_2fa_method to totp" do - expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(UserSecondFactor.methods[:totp]) + expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq( + UserSecondFactor.methods[:totp], + ) end end context "when totp is invalid" do let(:params) do - { - second_factor_token: "blah", - second_factor_method: UserSecondFactor.methods[:totp] - } + { second_factor_token: "blah", second_factor_method: UserSecondFactor.methods[:totp] } end it "returns not OK" do result = user.authenticate_second_factor(params, secure_session) @@ -277,26 +281,21 @@ RSpec.describe SecondFactorManager do let(:token) { user.user_second_factors.totps.first.totp_object.now } context "when totp params are provided" do - let(:params) do - { - second_factor_token: token, - second_factor_method: method - } - end + let(:params) { { second_factor_token: token, second_factor_method: method } } it "validates totp OK" do expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true) end it "sets used_2fa_method to totp" do - expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(UserSecondFactor.methods[:totp]) + expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq( + UserSecondFactor.methods[:totp], + ) end context "when the user does not have TOTP enabled" do - let(:token) { 'test' } - before do - user.totps.destroy_all - end + let(:token) { "test" } + before { user.totps.destroy_all } it "returns an error" do result = user.authenticate_second_factor(params, secure_session) @@ -317,19 +316,21 @@ RSpec.describe SecondFactorManager do end context "when security key params are valid" do - let(:params) { { second_factor_token: valid_security_key_auth_post_data, second_factor_method: method } } + let(:params) do + { second_factor_token: valid_security_key_auth_post_data, second_factor_method: method } + end it "returns OK" do expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true) end it "sets used_2fa_method to security keys" do - expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(UserSecondFactor.methods[:security_key]) + expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq( + UserSecondFactor.methods[:security_key], + ) end context "when the user does not have security keys enabled" do - before do - user.security_keys.destroy_all - end + before { user.security_keys.destroy_all } it "returns an error" do result = user.authenticate_second_factor(params, secure_session) @@ -347,10 +348,7 @@ RSpec.describe SecondFactorManager do context "when backup code params are provided" do let(:params) do - { - second_factor_token: 'iAmValidBackupCode', - second_factor_method: method - } + { second_factor_token: "iAmValidBackupCode", second_factor_method: method } end context "when backup codes enabled" do @@ -359,14 +357,14 @@ RSpec.describe SecondFactorManager do end it "sets used_2fa_method to backup codes" do - expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(UserSecondFactor.methods[:backup_codes]) + expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq( + UserSecondFactor.methods[:backup_codes], + ) end end context "when backup codes disabled" do - before do - user.user_second_factors.backup_codes.destroy_all - end + before { user.user_second_factors.backup_codes.destroy_all } it "returns an error" do result = user.authenticate_second_factor(params, secure_session) @@ -379,14 +377,21 @@ RSpec.describe SecondFactorManager do end context "when no totp params are provided" do - let(:params) { { second_factor_token: valid_security_key_auth_post_data, second_factor_method: UserSecondFactor.methods[:security_key] } } + let(:params) do + { + second_factor_token: valid_security_key_auth_post_data, + second_factor_method: UserSecondFactor.methods[:security_key], + } + end it "validates the security key OK" do expect(user.authenticate_second_factor(params, secure_session).ok).to eq(true) end it "sets used_2fa_method to security keys" do - expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(UserSecondFactor.methods[:security_key]) + expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq( + UserSecondFactor.methods[:security_key], + ) end end @@ -394,7 +399,7 @@ RSpec.describe SecondFactorManager do let(:params) do { second_factor_token: user.user_second_factors.totps.first.totp_object.now, - second_factor_method: UserSecondFactor.methods[:totp] + second_factor_method: UserSecondFactor.methods[:totp], } end @@ -403,26 +408,30 @@ RSpec.describe SecondFactorManager do end it "sets used_2fa_method to totp" do - expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq(UserSecondFactor.methods[:totp]) + expect(user.authenticate_second_factor(params, secure_session).used_2fa_method).to eq( + UserSecondFactor.methods[:totp], + ) end end end end - describe 'backup codes' do - describe '#generate_backup_codes' do - it 'should generate and store 10 backup codes' do + describe "backup codes" do + describe "#generate_backup_codes" do + it "should generate and store 10 backup codes" do backup_codes = user.generate_backup_codes expect(backup_codes.length).to be 10 expect(user_backup.user_second_factors.backup_codes).to be_present - expect(user_backup.user_second_factors.backup_codes.pluck(:method).uniq[0]).to eq(UserSecondFactor.methods[:backup_codes]) + expect(user_backup.user_second_factors.backup_codes.pluck(:method).uniq[0]).to eq( + UserSecondFactor.methods[:backup_codes], + ) expect(user_backup.user_second_factors.backup_codes.pluck(:enabled).uniq[0]).to eq(true) end end - describe '#create_backup_codes' do - it 'should create 10 backup code records' do + describe "#create_backup_codes" do + it "should create 10 backup code records" do raw_codes = Array.new(10) { SecureRandom.hex(8) } backup_codes = another_user.create_backup_codes(raw_codes) @@ -430,58 +439,58 @@ RSpec.describe SecondFactorManager do end end - describe '#authenticate_backup_code' do - it 'should be able to authenticate a backup code' do + describe "#authenticate_backup_code" do + it "should be able to authenticate a backup code" do backup_code = "iAmValidBackupCode" expect(user_backup.authenticate_backup_code(backup_code)).to eq(true) expect(user_backup.authenticate_backup_code(backup_code)).to eq(false) end - describe 'when code is blank' do - it 'should be false' do + describe "when code is blank" do + it "should be false" do expect(user_backup.authenticate_backup_code(nil)).to eq(false) end end - describe 'when code is invalid' do - it 'should be false' do + describe "when code is invalid" do + it "should be false" do expect(user_backup.authenticate_backup_code("notValidBackupCode")).to eq(false) end end end - describe '#backup_codes_enabled?' do - describe 'when user does not have a second factor backup enabled' do - it 'should return false' do + describe "#backup_codes_enabled?" do + describe "when user does not have a second factor backup enabled" do + it "should return false" do expect(another_user.backup_codes_enabled?).to eq(false) end end describe "when user's second factor backup codes have been used" do - it 'should return false' do + it "should return false" do user_backup.user_second_factors.backup_codes.update_all(enabled: false) expect(user_backup.backup_codes_enabled?).to eq(false) end end describe "when user's second factor code is available" do - it 'should return true' do + it "should return true" do expect(user_backup.backup_codes_enabled?).to eq(true) end end - describe 'when SSO is enabled' do - it 'should return false' do - SiteSetting.discourse_connect_url = 'http://someurl.com' + describe "when SSO is enabled" do + it "should return false" do + SiteSetting.discourse_connect_url = "http://someurl.com" SiteSetting.enable_discourse_connect = true expect(user_backup.backup_codes_enabled?).to eq(false) end end - describe 'when local login is disabled' do - it 'should return false' do + describe "when local login is disabled" do + it "should return false" do SiteSetting.enable_local_logins = false expect(user_backup.backup_codes_enabled?).to eq(false) diff --git a/spec/lib/content_buffer_spec.rb b/spec/lib/content_buffer_spec.rb index efc8ffe005..567f808b6e 100644 --- a/spec/lib/content_buffer_spec.rb +++ b/spec/lib/content_buffer_spec.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require 'content_buffer' +require "content_buffer" RSpec.describe ContentBuffer do - it "handles deletion across lines properly" do c = ContentBuffer.new("a\nbc\nc") c.apply_transform!(start: { row: 0, col: 0 }, finish: { col: 1, row: 1 }, operation: :delete) @@ -26,5 +25,4 @@ RSpec.describe ContentBuffer do c.apply_transform!(start: { row: 0, col: 5 }, operation: :insert, text: "\nworld") expect(c.to_s).to eq("hello\nworld!") end - end diff --git a/spec/lib/content_security_policy/builder_spec.rb b/spec/lib/content_security_policy/builder_spec.rb index fd0ad5ba10..ec58256d2a 100644 --- a/spec/lib/content_security_policy/builder_spec.rb +++ b/spec/lib/content_security_policy/builder_spec.rb @@ -2,29 +2,29 @@ RSpec.describe ContentSecurityPolicy::Builder do let(:builder) { described_class.new(base_url: Discourse.base_url) } - describe '#<<' do - it 'normalizes directive name' do + describe "#<<" do + it "normalizes directive name" do builder << { - script_src: ['symbol_underscore'], - 'script-src': ['symbol_dash'], - 'script_src' => ['string_underscore'], - 'script-src' => ['string_dash'], + :script_src => ["symbol_underscore"], + :"script-src" => ["symbol_dash"], + "script_src" => ["string_underscore"], + "script-src" => ["string_dash"], } - script_srcs = parse(builder.build)['script-src'] + script_srcs = parse(builder.build)["script-src"] - expect(script_srcs).to include(*%w[symbol_underscore symbol_dash string_underscore symbol_underscore]) + expect(script_srcs).to include( + *%w[symbol_underscore symbol_dash string_underscore symbol_underscore], + ) end - it 'rejects invalid directives and ones that are not allowed to be extended' do - builder << { - invalid_src: ['invalid'], - } + it "rejects invalid directives and ones that are not allowed to be extended" do + builder << { invalid_src: ["invalid"] } - expect(builder.build).to_not include('invalid') + expect(builder.build).to_not include("invalid") end - it 'no-ops on invalid values' do + it "no-ops on invalid values" do previous = builder.build builder << nil @@ -38,9 +38,12 @@ RSpec.describe ContentSecurityPolicy::Builder do end def parse(csp_string) - csp_string.split(';').map do |policy| - directive, *sources = policy.split - [directive, sources] - end.to_h + csp_string + .split(";") + .map do |policy| + directive, *sources = policy.split + [directive, sources] + end + .to_h end end diff --git a/spec/lib/content_security_policy_spec.rb b/spec/lib/content_security_policy_spec.rb index 46f11f63be..2d36edfbdc 100644 --- a/spec/lib/content_security_policy_spec.rb +++ b/spec/lib/content_security_policy_spec.rb @@ -1,223 +1,231 @@ # frozen_string_literal: true RSpec.describe ContentSecurityPolicy do - after do - DiscoursePluginRegistry.reset! - end + after { DiscoursePluginRegistry.reset! } - describe 'report-uri' do - it 'is enabled by SiteSetting' do + describe "report-uri" do + it "is enabled by SiteSetting" do SiteSetting.content_security_policy_collect_reports = true - report_uri = parse(policy)['report-uri'].first - expect(report_uri).to eq('http://test.localhost/csp_reports') + report_uri = parse(policy)["report-uri"].first + expect(report_uri).to eq("http://test.localhost/csp_reports") SiteSetting.content_security_policy_collect_reports = false - report_uri = parse(policy)['report-uri'] + report_uri = parse(policy)["report-uri"] expect(report_uri).to eq(nil) end end - describe 'base-uri' do - it 'is set to self' do - base_uri = parse(policy)['base-uri'] + describe "base-uri" do + it "is set to self" do + base_uri = parse(policy)["base-uri"] expect(base_uri).to eq(["'self'"]) end end - describe 'object-src' do - it 'is set to none' do - object_srcs = parse(policy)['object-src'] + describe "object-src" do + it "is set to none" do + object_srcs = parse(policy)["object-src"] expect(object_srcs).to eq(["'none'"]) end end - describe 'upgrade-insecure-requests' do - it 'is not included when force_https is off' do + describe "upgrade-insecure-requests" do + it "is not included when force_https is off" do SiteSetting.force_https = false - expect(parse(policy)['upgrade-insecure-requests']).to eq(nil) + expect(parse(policy)["upgrade-insecure-requests"]).to eq(nil) end - it 'is included when force_https is on' do + it "is included when force_https is on" do SiteSetting.force_https = true - expect(parse(policy)['upgrade-insecure-requests']).to eq([]) + expect(parse(policy)["upgrade-insecure-requests"]).to eq([]) end end - describe 'worker-src' do - it 'has expected values' do - worker_srcs = parse(policy)['worker-src'] - expect(worker_srcs).to eq(%w[ - 'self' - http://test.localhost/assets/ - http://test.localhost/brotli_asset/ - http://test.localhost/javascripts/ - http://test.localhost/plugins/ - ]) + describe "worker-src" do + it "has expected values" do + worker_srcs = parse(policy)["worker-src"] + expect(worker_srcs).to eq( + %w[ + 'self' + http://test.localhost/assets/ + http://test.localhost/brotli_asset/ + http://test.localhost/javascripts/ + http://test.localhost/plugins/ + ], + ) end end - describe 'script-src' do - it 'always has self, logster, sidekiq, and assets' do - script_srcs = parse(policy)['script-src'] - expect(script_srcs).to include(*%w[ - http://test.localhost/logs/ - http://test.localhost/sidekiq/ - http://test.localhost/mini-profiler-resources/ - http://test.localhost/assets/ - http://test.localhost/brotli_asset/ - http://test.localhost/extra-locales/ - http://test.localhost/highlight-js/ - http://test.localhost/javascripts/ - http://test.localhost/plugins/ - http://test.localhost/theme-javascripts/ - http://test.localhost/svg-sprite/ - ]) + describe "script-src" do + it "always has self, logster, sidekiq, and assets" do + script_srcs = parse(policy)["script-src"] + expect(script_srcs).to include( + *%w[ + http://test.localhost/logs/ + http://test.localhost/sidekiq/ + http://test.localhost/mini-profiler-resources/ + http://test.localhost/assets/ + http://test.localhost/brotli_asset/ + http://test.localhost/extra-locales/ + http://test.localhost/highlight-js/ + http://test.localhost/javascripts/ + http://test.localhost/plugins/ + http://test.localhost/theme-javascripts/ + http://test.localhost/svg-sprite/ + ], + ) end it 'includes "report-sample" when report collection is enabled' do SiteSetting.content_security_policy_collect_reports = true - script_srcs = parse(policy)['script-src'] + script_srcs = parse(policy)["script-src"] expect(script_srcs).to include("'report-sample'") end - context 'for Google Analytics' do - before do - SiteSetting.ga_universal_tracking_code = 'UA-12345678-9' + context "for Google Analytics" do + before { SiteSetting.ga_universal_tracking_code = "UA-12345678-9" } + + it "allowlists Google Analytics v3 when integrated" do + script_srcs = parse(policy)["script-src"] + expect(script_srcs).to include("https://www.google-analytics.com/analytics.js") + expect(script_srcs).not_to include("https://www.googletagmanager.com/gtag/js") end - it 'allowlists Google Analytics v3 when integrated' do - script_srcs = parse(policy)['script-src'] - expect(script_srcs).to include('https://www.google-analytics.com/analytics.js') - expect(script_srcs).not_to include('https://www.googletagmanager.com/gtag/js') - end + it "allowlists Google Analytics v4 when integrated" do + SiteSetting.ga_version = "v4_gtag" - it 'allowlists Google Analytics v4 when integrated' do - SiteSetting.ga_version = 'v4_gtag' - - script_srcs = parse(policy)['script-src'] - expect(script_srcs).to include('https://www.google-analytics.com/analytics.js') - expect(script_srcs).to include('https://www.googletagmanager.com/gtag/js') + script_srcs = parse(policy)["script-src"] + expect(script_srcs).to include("https://www.google-analytics.com/analytics.js") + expect(script_srcs).to include("https://www.googletagmanager.com/gtag/js") end end - it 'allowlists Google Tag Manager when integrated' do - SiteSetting.gtm_container_id = 'GTM-ABCDEF' + it "allowlists Google Tag Manager when integrated" do + SiteSetting.gtm_container_id = "GTM-ABCDEF" - script_srcs = parse(policy)['script-src'] - expect(script_srcs).to include('https://www.googletagmanager.com/gtm.js') - expect(script_srcs.to_s).to include('nonce-') + script_srcs = parse(policy)["script-src"] + expect(script_srcs).to include("https://www.googletagmanager.com/gtm.js") + expect(script_srcs.to_s).to include("nonce-") end - it 'allowlists CDN assets when integrated' do - set_cdn_url('https://cdn.com') + it "allowlists CDN assets when integrated" do + set_cdn_url("https://cdn.com") - script_srcs = parse(policy)['script-src'] - expect(script_srcs).to include(*%w[ - https://cdn.com/assets/ - https://cdn.com/brotli_asset/ - https://cdn.com/highlight-js/ - https://cdn.com/javascripts/ - https://cdn.com/plugins/ - https://cdn.com/theme-javascripts/ - http://test.localhost/extra-locales/ - ]) + script_srcs = parse(policy)["script-src"] + expect(script_srcs).to include( + *%w[ + https://cdn.com/assets/ + https://cdn.com/brotli_asset/ + https://cdn.com/highlight-js/ + https://cdn.com/javascripts/ + https://cdn.com/plugins/ + https://cdn.com/theme-javascripts/ + http://test.localhost/extra-locales/ + ], + ) - global_setting(:s3_cdn_url, 'https://s3-cdn.com') + global_setting(:s3_cdn_url, "https://s3-cdn.com") - script_srcs = parse(policy)['script-src'] - expect(script_srcs).to include(*%w[ - https://s3-cdn.com/assets/ - https://s3-cdn.com/brotli_asset/ - https://cdn.com/highlight-js/ - https://cdn.com/javascripts/ - https://cdn.com/plugins/ - https://cdn.com/theme-javascripts/ - http://test.localhost/extra-locales/ - ]) + script_srcs = parse(policy)["script-src"] + expect(script_srcs).to include( + *%w[ + https://s3-cdn.com/assets/ + https://s3-cdn.com/brotli_asset/ + https://cdn.com/highlight-js/ + https://cdn.com/javascripts/ + https://cdn.com/plugins/ + https://cdn.com/theme-javascripts/ + http://test.localhost/extra-locales/ + ], + ) - global_setting(:s3_asset_cdn_url, 'https://s3-asset-cdn.com') + global_setting(:s3_asset_cdn_url, "https://s3-asset-cdn.com") - script_srcs = parse(policy)['script-src'] - expect(script_srcs).to include(*%w[ - https://s3-asset-cdn.com/assets/ - https://s3-asset-cdn.com/brotli_asset/ - https://cdn.com/highlight-js/ - https://cdn.com/javascripts/ - https://cdn.com/plugins/ - https://cdn.com/theme-javascripts/ - http://test.localhost/extra-locales/ - ]) + script_srcs = parse(policy)["script-src"] + expect(script_srcs).to include( + *%w[ + https://s3-asset-cdn.com/assets/ + https://s3-asset-cdn.com/brotli_asset/ + https://cdn.com/highlight-js/ + https://cdn.com/javascripts/ + https://cdn.com/plugins/ + https://cdn.com/theme-javascripts/ + http://test.localhost/extra-locales/ + ], + ) end - it 'adds subfolder to CDN assets' do - set_cdn_url('https://cdn.com') - set_subfolder('/forum') + it "adds subfolder to CDN assets" do + set_cdn_url("https://cdn.com") + set_subfolder("/forum") - script_srcs = parse(policy)['script-src'] - expect(script_srcs).to include(*%w[ - https://cdn.com/forum/assets/ - https://cdn.com/forum/brotli_asset/ - https://cdn.com/forum/highlight-js/ - https://cdn.com/forum/javascripts/ - https://cdn.com/forum/plugins/ - https://cdn.com/forum/theme-javascripts/ - http://test.localhost/forum/extra-locales/ - ]) + script_srcs = parse(policy)["script-src"] + expect(script_srcs).to include( + *%w[ + https://cdn.com/forum/assets/ + https://cdn.com/forum/brotli_asset/ + https://cdn.com/forum/highlight-js/ + https://cdn.com/forum/javascripts/ + https://cdn.com/forum/plugins/ + https://cdn.com/forum/theme-javascripts/ + http://test.localhost/forum/extra-locales/ + ], + ) - global_setting(:s3_cdn_url, 'https://s3-cdn.com') + global_setting(:s3_cdn_url, "https://s3-cdn.com") - script_srcs = parse(policy)['script-src'] - expect(script_srcs).to include(*%w[ - https://s3-cdn.com/assets/ - https://s3-cdn.com/brotli_asset/ - https://cdn.com/forum/highlight-js/ - https://cdn.com/forum/javascripts/ - https://cdn.com/forum/plugins/ - https://cdn.com/forum/theme-javascripts/ - http://test.localhost/forum/extra-locales/ - ]) + script_srcs = parse(policy)["script-src"] + expect(script_srcs).to include( + *%w[ + https://s3-cdn.com/assets/ + https://s3-cdn.com/brotli_asset/ + https://cdn.com/forum/highlight-js/ + https://cdn.com/forum/javascripts/ + https://cdn.com/forum/plugins/ + https://cdn.com/forum/theme-javascripts/ + http://test.localhost/forum/extra-locales/ + ], + ) end end - describe 'manifest-src' do - it 'is set to self' do - expect(parse(policy)['manifest-src']).to eq(["'self'"]) + describe "manifest-src" do + it "is set to self" do + expect(parse(policy)["manifest-src"]).to eq(["'self'"]) end end - describe 'frame-ancestors' do - context 'with content_security_policy_frame_ancestors enabled' do + describe "frame-ancestors" do + context "with content_security_policy_frame_ancestors enabled" do before do SiteSetting.content_security_policy_frame_ancestors = true - Fabricate(:embeddable_host, host: 'https://a.org') - Fabricate(:embeddable_host, host: 'https://b.org') + Fabricate(:embeddable_host, host: "https://a.org") + Fabricate(:embeddable_host, host: "https://b.org") end - it 'always has self' do - frame_ancestors = parse(policy)['frame-ancestors'] + it "always has self" do + frame_ancestors = parse(policy)["frame-ancestors"] expect(frame_ancestors).to include("'self'") end - it 'includes all EmbeddableHost' do + it "includes all EmbeddableHost" do EmbeddableHost - frame_ancestors = parse(policy)['frame-ancestors'] + frame_ancestors = parse(policy)["frame-ancestors"] expect(frame_ancestors).to include("https://a.org") expect(frame_ancestors).to include("https://b.org") end end - context 'with content_security_policy_frame_ancestors disabled' do - before do - SiteSetting.content_security_policy_frame_ancestors = false - end + context "with content_security_policy_frame_ancestors disabled" do + before { SiteSetting.content_security_policy_frame_ancestors = false } - it 'does not set frame-ancestors' do - frame_ancestors = parse(policy)['frame-ancestors'] + it "does not set frame-ancestors" do + frame_ancestors = parse(policy)["frame-ancestors"] expect(frame_ancestors).to be_nil end end end - context 'with a plugin' do + context "with a plugin" do let(:plugin_class) do Class.new(Plugin::Instance) do attr_accessor :enabled @@ -227,29 +235,29 @@ RSpec.describe ContentSecurityPolicy do end end - it 'can extend script-src, object-src, manifest-src' do + it "can extend script-src, object-src, manifest-src" do plugin = plugin_class.new(nil, "#{Rails.root}/spec/fixtures/plugins/csp_extension/plugin.rb") plugin.activate! Discourse.plugins << plugin plugin.enabled = true - expect(parse(policy)['script-src']).to include('https://from-plugin.com') - expect(parse(policy)['script-src']).to include('http://test.localhost/local/path') - expect(parse(policy)['object-src']).to include('https://test-stripping.com') - expect(parse(policy)['object-src']).to_not include("'none'") - expect(parse(policy)['manifest-src']).to include("'self'") - expect(parse(policy)['manifest-src']).to include('https://manifest-src.com') + expect(parse(policy)["script-src"]).to include("https://from-plugin.com") + expect(parse(policy)["script-src"]).to include("http://test.localhost/local/path") + expect(parse(policy)["object-src"]).to include("https://test-stripping.com") + expect(parse(policy)["object-src"]).to_not include("'none'") + expect(parse(policy)["manifest-src"]).to include("'self'") + expect(parse(policy)["manifest-src"]).to include("https://manifest-src.com") plugin.enabled = false - expect(parse(policy)['script-src']).to_not include('https://from-plugin.com') - expect(parse(policy)['manifest-src']).to_not include('https://manifest-src.com') + expect(parse(policy)["script-src"]).to_not include("https://from-plugin.com") + expect(parse(policy)["manifest-src"]).to_not include("https://manifest-src.com") Discourse.plugins.delete plugin DiscoursePluginRegistry.reset! end - it 'can extend frame_ancestors' do + it "can extend frame_ancestors" do SiteSetting.content_security_policy_frame_ancestors = true plugin = plugin_class.new(nil, "#{Rails.root}/spec/fixtures/plugins/csp_extension/plugin.rb") @@ -257,25 +265,25 @@ RSpec.describe ContentSecurityPolicy do Discourse.plugins << plugin plugin.enabled = true - expect(parse(policy)['frame-ancestors']).to include("'self'") - expect(parse(policy)['frame-ancestors']).to include('https://frame-ancestors-plugin.ext') + expect(parse(policy)["frame-ancestors"]).to include("'self'") + expect(parse(policy)["frame-ancestors"]).to include("https://frame-ancestors-plugin.ext") plugin.enabled = false - expect(parse(policy)['frame-ancestors']).to_not include('https://frame-ancestors-plugin.ext') + expect(parse(policy)["frame-ancestors"]).to_not include("https://frame-ancestors-plugin.ext") Discourse.plugins.delete plugin DiscoursePluginRegistry.reset! end end - it 'only includes unsafe-inline for qunit paths' do - expect(parse(policy(path_info: "/qunit"))['script-src']).to include("'unsafe-eval'") - expect(parse(policy(path_info: "/wizard/qunit"))['script-src']).to include("'unsafe-eval'") - expect(parse(policy(path_info: "/"))['script-src']).to_not include("'unsafe-eval'") + it "only includes unsafe-inline for qunit paths" do + expect(parse(policy(path_info: "/qunit"))["script-src"]).to include("'unsafe-eval'") + expect(parse(policy(path_info: "/wizard/qunit"))["script-src"]).to include("'unsafe-eval'") + expect(parse(policy(path_info: "/"))["script-src"]).to_not include("'unsafe-eval'") end context "with a theme" do - let!(:theme) { + let!(:theme) do Fabricate(:theme).tap do |t| settings = <<~YML extend_content_security_policy: @@ -285,58 +293,67 @@ RSpec.describe ContentSecurityPolicy do t.set_field(target: :settings, name: :yaml, value: settings) t.save! end - } + end def theme_policy policy(theme.id) end - it 'can be extended by themes' do + it "can be extended by themes" do policy # call this first to make sure further actions clear the cache - expect(parse(policy)['script-src']).not_to include('from-theme.com') + expect(parse(policy)["script-src"]).not_to include("from-theme.com") - expect(parse(theme_policy)['script-src']).to include('from-theme.com') + expect(parse(theme_policy)["script-src"]).to include("from-theme.com") - theme.update_setting(:extend_content_security_policy, "script-src: https://from-theme.net|worker-src: from-theme.com") + theme.update_setting( + :extend_content_security_policy, + "script-src: https://from-theme.net|worker-src: from-theme.com", + ) theme.save! - expect(parse(theme_policy)['script-src']).to_not include('from-theme.com') - expect(parse(theme_policy)['script-src']).to include('https://from-theme.net') - expect(parse(theme_policy)['worker-src']).to include('from-theme.com') + expect(parse(theme_policy)["script-src"]).to_not include("from-theme.com") + expect(parse(theme_policy)["script-src"]).to include("https://from-theme.net") + expect(parse(theme_policy)["worker-src"]).to include("from-theme.com") theme.destroy! - expect(parse(theme_policy)['script-src']).to_not include('https://from-theme.net') - expect(parse(theme_policy)['worker-src']).to_not include('from-theme.com') + expect(parse(theme_policy)["script-src"]).to_not include("https://from-theme.net") + expect(parse(theme_policy)["worker-src"]).to_not include("from-theme.com") end - it 'can be extended by theme modifiers' do + it "can be extended by theme modifiers" do policy # call this first to make sure further actions clear the cache - theme.theme_modifier_set.csp_extensions = ["script-src: https://from-theme-flag.script", "worker-src: from-theme-flag.worker"] + theme.theme_modifier_set.csp_extensions = [ + "script-src: https://from-theme-flag.script", + "worker-src: from-theme-flag.worker", + ] theme.save! child_theme = Fabricate(:theme, component: true) theme.add_relative_theme!(:child, child_theme) - child_theme.theme_modifier_set.csp_extensions = ["script-src: https://child-theme-flag.script", "worker-src: child-theme-flag.worker"] + child_theme.theme_modifier_set.csp_extensions = [ + "script-src: https://child-theme-flag.script", + "worker-src: child-theme-flag.worker", + ] child_theme.save! - expect(parse(theme_policy)['script-src']).to include('https://from-theme-flag.script') - expect(parse(theme_policy)['script-src']).to include('https://child-theme-flag.script') - expect(parse(theme_policy)['worker-src']).to include('from-theme-flag.worker') - expect(parse(theme_policy)['worker-src']).to include('child-theme-flag.worker') + expect(parse(theme_policy)["script-src"]).to include("https://from-theme-flag.script") + expect(parse(theme_policy)["script-src"]).to include("https://child-theme-flag.script") + expect(parse(theme_policy)["worker-src"]).to include("from-theme-flag.worker") + expect(parse(theme_policy)["worker-src"]).to include("child-theme-flag.worker") theme.destroy! child_theme.destroy! - expect(parse(theme_policy)['script-src']).to_not include('https://from-theme-flag.script') - expect(parse(theme_policy)['worker-src']).to_not include('from-theme-flag.worker') - expect(parse(theme_policy)['worker-src']).to_not include('from-theme-flag.worker') - expect(parse(theme_policy)['worker-src']).to_not include('child-theme-flag.worker') + expect(parse(theme_policy)["script-src"]).to_not include("https://from-theme-flag.script") + expect(parse(theme_policy)["worker-src"]).to_not include("from-theme-flag.worker") + expect(parse(theme_policy)["worker-src"]).to_not include("from-theme-flag.worker") + expect(parse(theme_policy)["worker-src"]).to_not include("child-theme-flag.worker") end - it 'is extended automatically when themes reference external scripts' do + it "is extended automatically when themes reference external scripts" do policy # call this first to make sure further actions clear the cache theme.set_field(target: :common, name: "header", value: <<~HTML) @@ -350,30 +367,35 @@ RSpec.describe ContentSecurityPolicy do theme.set_field(target: :desktop, name: "header", value: "") theme.save! - expect(parse(theme_policy)['script-src']).to include('https://example.com/myscript.js') - expect(parse(theme_policy)['script-src']).to include('https://example.com/myscript2.js') - expect(parse(theme_policy)['script-src']).not_to include('?') - expect(parse(theme_policy)['script-src']).to include('example2.com/protocol-less-script.js') - expect(parse(theme_policy)['script-src']).not_to include('domain-only.com') - expect(parse(theme_policy)['script-src']).not_to include(a_string_matching /^\/theme-javascripts/) + expect(parse(theme_policy)["script-src"]).to include("https://example.com/myscript.js") + expect(parse(theme_policy)["script-src"]).to include("https://example.com/myscript2.js") + expect(parse(theme_policy)["script-src"]).not_to include("?") + expect(parse(theme_policy)["script-src"]).to include("example2.com/protocol-less-script.js") + expect(parse(theme_policy)["script-src"]).not_to include("domain-only.com") + expect(parse(theme_policy)["script-src"]).not_to include( + a_string_matching %r{^/theme-javascripts} + ) theme.destroy! - expect(parse(theme_policy)['script-src']).to_not include('https://example.com/myscript.js') + expect(parse(theme_policy)["script-src"]).to_not include("https://example.com/myscript.js") end end - it 'can be extended by site setting' do - SiteSetting.content_security_policy_script_src = 'from-site-setting.com|from-site-setting.net' + it "can be extended by site setting" do + SiteSetting.content_security_policy_script_src = "from-site-setting.com|from-site-setting.net" - expect(parse(policy)['script-src']).to include('from-site-setting.com', 'from-site-setting.net') + expect(parse(policy)["script-src"]).to include("from-site-setting.com", "from-site-setting.net") end def parse(csp_string) - csp_string.split(';').map do |policy| - directive, *sources = policy.split - [directive, sources] - end.to_h + csp_string + .split(";") + .map do |policy| + directive, *sources = policy.split + [directive, sources] + end + .to_h end def policy(theme_id = nil, path_info: "/") diff --git a/spec/lib/cooked_post_processor_spec.rb b/spec/lib/cooked_post_processor_spec.rb index c703ea1909..f55ef0e689 100644 --- a/spec/lib/cooked_post_processor_spec.rb +++ b/spec/lib/cooked_post_processor_spec.rb @@ -5,15 +5,13 @@ require "file_store/s3_store" RSpec.describe CookedPostProcessor do fab!(:upload) { Fabricate(:upload) } - fab!(:large_image_upload) { Fabricate(:large_image_upload) } + fab!(:large_image_upload) { Fabricate(:large_image_upload) } let(:upload_path) { Discourse.store.upload_path } describe "#post_process" do - fab!(:post) do - Fabricate(:post, raw: <<~RAW) + fab!(:post) { Fabricate(:post, raw: <<~RAW) } RAW - end let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } let(:post_process) { sequence("post_process") } @@ -27,21 +25,16 @@ RSpec.describe CookedPostProcessor do expect(UploadReference.exists?(target: post, upload: upload)).to eq(true) end - describe 'when post contains oneboxes and inline oneboxes' do - let(:url_hostname) { 'meta.discourse.org' } + describe "when post contains oneboxes and inline oneboxes" do + let(:url_hostname) { "meta.discourse.org" } - let(:url) do - "https://#{url_hostname}/t/mini-inline-onebox-support-rfc/66400" - end + let(:url) { "https://#{url_hostname}/t/mini-inline-onebox-support-rfc/66400" } - let(:not_oneboxed_url) do - "https://#{url_hostname}/t/random-url" - end + let(:not_oneboxed_url) { "https://#{url_hostname}/t/random-url" } - let(:title) { 'some title' } + let(:title) { "some title" } - let(:post) do - Fabricate(:post, raw: <<~RAW) + let(:post) { Fabricate(:post, raw: <<~RAW) } #{url} This is a #{url} with path @@ -52,7 +45,6 @@ RSpec.describe CookedPostProcessor do #{url} RAW - end before do SiteSetting.enable_inline_onebox_on_all_domains = true @@ -71,10 +63,8 @@ RSpec.describe CookedPostProcessor do HTML Oneboxer.stubs(:cached_onebox).with(not_oneboxed_url).returns(nil) - %i{head get}.each do |method| - stub_request(method, url).to_return( - status: 200, - body: <<~RAW + %i[head get].each do |method| + stub_request(method, url).to_return(status: 200, body: <<~RAW) #{title} @@ -83,7 +73,6 @@ RSpec.describe CookedPostProcessor do RAW - ) end end @@ -92,47 +81,50 @@ RSpec.describe CookedPostProcessor do Oneboxer.invalidate(url) end - it 'should respect SiteSetting.max_oneboxes_per_post' do + it "should respect SiteSetting.max_oneboxes_per_post" do SiteSetting.max_oneboxes_per_post = 2 SiteSetting.add_rel_nofollow_to_user_content = false cpp.post_process - expect(cpp.html).to have_tag('a', - with: { href: url, class: "inline-onebox" }, + expect(cpp.html).to have_tag( + "a", + with: { + href: url, + class: "inline-onebox", + }, text: title, - count: 2 + count: 2, ) - expect(cpp.html).to have_tag('aside.onebox a', text: title, count: 1) + expect(cpp.html).to have_tag("aside.onebox a", text: title, count: 1) - expect(cpp.html).to have_tag('aside.onebox a', - text: url_hostname, - count: 1 - ) + expect(cpp.html).to have_tag("aside.onebox a", text: url_hostname, count: 1) - expect(cpp.html).to have_tag('a', - without: { class: "inline-onebox-loading" }, - text: not_oneboxed_url, - count: 1 - ) - - expect(cpp.html).to have_tag('a', + expect(cpp.html).to have_tag( + "a", without: { - class: 'onebox' + class: "inline-onebox-loading", }, text: not_oneboxed_url, - count: 1 + count: 1, + ) + + expect(cpp.html).to have_tag( + "a", + without: { + class: "onebox", + }, + text: not_oneboxed_url, + count: 1, ) end end - describe 'when post contains inline oneboxes' do - before do - SiteSetting.enable_inline_onebox_on_all_domains = true - end + describe "when post contains inline oneboxes" do + before { SiteSetting.enable_inline_onebox_on_all_domains = true } - describe 'internal links' do + describe "internal links" do fab!(:topic) { Fabricate(:topic) } fab!(:post) { Fabricate(:post, raw: "Hello #{topic.url}") } let(:url) { topic.url } @@ -140,52 +132,49 @@ RSpec.describe CookedPostProcessor do it "includes the topic title" do cpp.post_process - expect(cpp.html).to have_tag('a', - with: { href: UrlHelper.cook_url(url) }, - without: { class: "inline-onebox-loading" }, + expect(cpp.html).to have_tag( + "a", + with: { + href: UrlHelper.cook_url(url), + }, + without: { + class: "inline-onebox-loading", + }, text: topic.title, - count: 1 + count: 1, ) topic.update!(title: "Updated to something else") cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) cpp.post_process - expect(cpp.html).to have_tag('a', - with: { href: UrlHelper.cook_url(url) }, - without: { class: "inline-onebox-loading" }, + expect(cpp.html).to have_tag( + "a", + with: { + href: UrlHelper.cook_url(url), + }, + without: { + class: "inline-onebox-loading", + }, text: topic.title, - count: 1 + count: 1, ) end end - describe 'external links' do - let(:url_with_path) do - 'https://meta.discourse.org/t/mini-inline-onebox-support-rfc/66400' - end + describe "external links" do + let(:url_with_path) { "https://meta.discourse.org/t/mini-inline-onebox-support-rfc/66400" } - let(:url_with_query_param) do - 'https://meta.discourse.org?a' - end + let(:url_with_query_param) { "https://meta.discourse.org?a" } - let(:url_no_path) do - 'https://meta.discourse.org/' - end + let(:url_no_path) { "https://meta.discourse.org/" } - let(:urls) do - [ - url_with_path, - url_with_query_param, - url_no_path - ] - end + let(:urls) { [url_with_path, url_with_query_param, url_no_path] } - let(:title) { 'some title' } + let(:title) { "some title" } let(:escaped_title) { CGI.escapeHTML(title) } - let(:post) do - Fabricate(:post, raw: <<~RAW) + let(:post) { Fabricate(:post, raw: <<~RAW) } This is a #{url_with_path} topic This should not be inline #{url_no_path} oneboxed @@ -194,55 +183,65 @@ RSpec.describe CookedPostProcessor do - #{url_with_query_param} RAW - end - let(:staff_post) do - Fabricate(:post, user: Fabricate(:admin), raw: <<~RAW) + let(:staff_post) { Fabricate(:post, user: Fabricate(:admin), raw: <<~RAW) } This is a #{url_with_path} topic RAW - end before do urls.each do |url| stub_request(:get, url).to_return( status: 200, - body: "#{escaped_title}" + body: "#{escaped_title}", ) end end - after do - urls.each { |url| InlineOneboxer.invalidate(url) } - end + after { urls.each { |url| InlineOneboxer.invalidate(url) } } - it 'should convert the right links to inline oneboxes' do + it "should convert the right links to inline oneboxes" do cpp.post_process html = cpp.html - expect(html).to_not have_tag('a', - with: { href: url_no_path }, - without: { class: "inline-onebox-loading" }, - text: title + expect(html).to_not have_tag( + "a", + with: { + href: url_no_path, + }, + without: { + class: "inline-onebox-loading", + }, + text: title, ) - expect(html).to have_tag('a', - with: { href: url_with_path }, - without: { class: "inline-onebox-loading" }, + expect(html).to have_tag( + "a", + with: { + href: url_with_path, + }, + without: { + class: "inline-onebox-loading", + }, text: title, - count: 2 + count: 2, ) - expect(html).to have_tag('a', - with: { href: url_with_query_param }, - without: { class: "inline-onebox-loading" }, + expect(html).to have_tag( + "a", + with: { + href: url_with_query_param, + }, + without: { + class: "inline-onebox-loading", + }, text: title, - count: 1 + count: 1, ) expect(html).to have_tag("a[rel='noopener nofollow ugc']") end - it 'removes nofollow if user is staff/tl3' do + it "removes nofollow if user is staff/tl3" do cpp = CookedPostProcessor.new(staff_post, invalidate_oneboxes: true) cpp.post_process expect(cpp.html).to_not have_tag("a[rel='noopener nofollow ugc']") @@ -251,15 +250,13 @@ RSpec.describe CookedPostProcessor do end context "when processing images" do - before do - SiteSetting.responsive_post_image_sizes = "" - end + before { SiteSetting.responsive_post_image_sizes = "" } context "with responsive images" do before { SiteSetting.responsive_post_image_sizes = "1|1.5|3" } it "includes responsive images on demand" do - upload.update!(width: 2000, height: 1500, filesize: 10000, dominant_color: "FFFFFF") + upload.update!(width: 2000, height: 1500, filesize: 10_000, dominant_color: "FFFFFF") post = Fabricate(:post, raw: "hello ") # fake some optimized images @@ -269,9 +266,9 @@ RSpec.describe CookedPostProcessor do height: 500, upload_id: upload.id, sha1: SecureRandom.hex, - extension: '.jpg', + extension: ".jpg", filesize: 500, - version: OptimizedImage::VERSION + version: OptimizedImage::VERSION, ) # fake 3x optimized image, we lose 2 pixels here over original due to rounding on downsize @@ -281,8 +278,8 @@ RSpec.describe CookedPostProcessor do height: 1500, upload_id: upload.id, sha1: SecureRandom.hex, - extension: '.jpg', - filesize: 800 + extension: ".jpg", + filesize: 800, ) cpp = CookedPostProcessor.new(post) @@ -294,7 +291,9 @@ RSpec.describe CookedPostProcessor do expect(html).to include(%Q|data-dominant-color="FFFFFF"|) # 1.5x is skipped cause we have a missing thumb - expect(html).to include("srcset=\"//test.localhost/#{upload_path}/666x500.jpg, //test.localhost/#{upload_path}/1998x1500.jpg 3x\"") + expect(html).to include( + "srcset=\"//test.localhost/#{upload_path}/666x500.jpg, //test.localhost/#{upload_path}/1998x1500.jpg 3x\"", + ) expect(html).to include("src=\"//test.localhost/#{upload_path}/666x500.jpg\"") # works with CDN @@ -307,23 +306,25 @@ RSpec.describe CookedPostProcessor do html = cpp.html expect(html).to include(%Q|data-dominant-color="FFFFFF"|) - expect(html).to include("srcset=\"//cdn.localhost/#{upload_path}/666x500.jpg, //cdn.localhost/#{upload_path}/1998x1500.jpg 3x\"") + expect(html).to include( + "srcset=\"//cdn.localhost/#{upload_path}/666x500.jpg, //cdn.localhost/#{upload_path}/1998x1500.jpg 3x\"", + ) expect(html).to include("src=\"//cdn.localhost/#{upload_path}/666x500.jpg\"") end it "doesn't include response images for cropped images" do - upload.update!(width: 200, height: 4000, filesize: 12345) + upload.update!(width: 200, height: 4000, filesize: 12_345) post = Fabricate(:post, raw: "hello ") # fake some optimized images OptimizedImage.create!( - url: 'http://a.b.c/200x500.jpg', + url: "http://a.b.c/200x500.jpg", width: 200, height: 500, upload_id: upload.id, sha1: SecureRandom.hex, - extension: '.jpg', - filesize: 500 + extension: ".jpg", + filesize: 500, ) cpp = CookedPostProcessor.new(post) @@ -336,8 +337,8 @@ RSpec.describe CookedPostProcessor do shared_examples "leave dimensions alone" do it "doesn't use them" do - expect(cpp.html).to match(/src="http:\/\/foo.bar\/image.png" width="" height=""/) - expect(cpp.html).to match(/src="http:\/\/domain.com\/picture.jpg" width="50" height="42"/) + expect(cpp.html).to match(%r{src="http://foo.bar/image.png" width="" height=""}) + expect(cpp.html).to match(%r{src="http://domain.com/picture.jpg" width="50" height="42"}) expect(cpp).to be_dirty end end @@ -352,11 +353,15 @@ RSpec.describe CookedPostProcessor do end context "when valid" do - let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 111, "height" => 222 } } } + let(:image_sizes) do + { "http://foo.bar/image.png" => { "width" => 111, "height" => 222 } } + end it "uses them" do - expect(cpp.html).to match(/src="http:\/\/foo.bar\/image.png" width="111" height="222"/) - expect(cpp.html).to match(/src="http:\/\/domain.com\/picture.jpg" width="50" height="42"/) + expect(cpp.html).to match(%r{src="http://foo.bar/image.png" width="111" height="222"}) + expect(cpp.html).to match( + %r{src="http://domain.com/picture.jpg" width="50" height="42"}, + ) expect(cpp).to be_dirty end end @@ -375,17 +380,14 @@ RSpec.describe CookedPostProcessor do let(:image_sizes) { { "http://foo.bar/image.png" => { "width" => 0, "height" => 0 } } } include_examples "leave dimensions alone" end - end context "with unsized images" do fab!(:upload) { Fabricate(:image_upload, width: 123, height: 456) } - fab!(:post) do - Fabricate(:post, raw: <<~HTML) + fab!(:post) { Fabricate(:post, raw: <<~HTML) } HTML - end let(:cpp) { CookedPostProcessor.new(post) } @@ -394,17 +396,14 @@ RSpec.describe CookedPostProcessor do expect(cpp.html).to match(/width="123" height="456"/) expect(cpp).to be_dirty end - end context "with large images" do fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) } - fab!(:post) do - Fabricate(:post, raw: <<~HTML) + fab!(:post) { Fabricate(:post, raw: <<~HTML) } HTML - end let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } @@ -423,15 +422,20 @@ RSpec.describe CookedPostProcessor do expect(cpp).to be_dirty end - context 'when image is inside onebox' do - let(:url) { 'https://image.com/my-avatar' } + context "when image is inside onebox" do + let(:url) { "https://image.com/my-avatar" } let(:post) { Fabricate(:post, raw: url) } before do - Oneboxer.stubs(:onebox).with(url, anything).returns("") + Oneboxer + .stubs(:onebox) + .with(url, anything) + .returns( + "", + ) end - it 'should not add lightbox' do + it "should not add lightbox" do FastImage.expects(:size).returns([1750, 2000]) cpp.post_process @@ -442,12 +446,15 @@ RSpec.describe CookedPostProcessor do end end - context 'when image is an svg' do + context "when image is an svg" do fab!(:post) do - Fabricate(:post, raw: "") + Fabricate( + :post, + raw: "", + ) end - it 'should not add lightbox' do + it "should not add lightbox" do FastImage.expects(:size).returns([1750, 2000]) cpp.post_process @@ -457,17 +464,23 @@ RSpec.describe CookedPostProcessor do HTML end - context 'when image src is an URL' do + context "when image src is an URL" do let(:post) do - Fabricate(:post, raw: "") + Fabricate( + :post, + raw: + "", + ) end - it 'should not add lightbox' do + it "should not add lightbox" do FastImage.expects(:size).returns([1750, 2000]) cpp.post_process - expect(cpp.html).to match_html("

    ") + expect(cpp.html).to match_html( + "

    ", + ) end end end @@ -495,19 +508,23 @@ RSpec.describe CookedPostProcessor do Fabricate(:post, raw: "![large.png|#{optimized_size}](#{upload.short_url})") end - let(:cooked_html) do - <<~HTML + let(:cooked_html) { <<~HTML }

    HTML - end context "when the upload is attached to the correct post" do before do FastImage.expects(:size).returns([1750, 2000]) OptimizedImage.expects(:resize).returns(true) - Discourse.store.class.any_instance.expects(:has_been_uploaded?).at_least_once.returns(true) + Discourse + .store + .class + .any_instance + .expects(:has_been_uploaded?) + .at_least_once + .returns(true) upload.update(secure: true, access_control_post: post) end @@ -540,17 +557,13 @@ RSpec.describe CookedPostProcessor do context "with tall images > default aspect ratio" do fab!(:upload) { Fabricate(:image_upload, width: 500, height: 2200) } - fab!(:post) do - Fabricate(:post, raw: <<~HTML) + fab!(:post) { Fabricate(:post, raw: <<~HTML) } HTML - end let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } - before do - SiteSetting.create_thumbnails = true - end + before { SiteSetting.create_thumbnails = true } it "resizes the image instead of crop" do cpp.post_process @@ -558,23 +571,18 @@ RSpec.describe CookedPostProcessor do expect(cpp.html).to match(/width="113" height="500">/) expect(cpp).to be_dirty end - end context "with taller images < default aspect ratio" do fab!(:upload) { Fabricate(:image_upload, width: 500, height: 2300) } - fab!(:post) do - Fabricate(:post, raw: <<~HTML) + fab!(:post) { Fabricate(:post, raw: <<~HTML) } HTML - end let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } - before do - SiteSetting.create_thumbnails = true - end + before { SiteSetting.create_thumbnails = true } it "crops the image" do cpp.post_process @@ -582,23 +590,18 @@ RSpec.describe CookedPostProcessor do expect(cpp.html).to match(/width="500" height="500">/) expect(cpp).to be_dirty end - end context "with iPhone X screenshots" do fab!(:upload) { Fabricate(:image_upload, width: 1125, height: 2436) } - fab!(:post) do - Fabricate(:post, raw: <<~HTML) + fab!(:post) { Fabricate(:post, raw: <<~HTML) } HTML - end let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } - before do - SiteSetting.create_thumbnails = true - end + before { SiteSetting.create_thumbnails = true } it "crops the image" do cpp.post_process @@ -609,23 +612,23 @@ RSpec.describe CookedPostProcessor do expect(cpp).to be_dirty end - end context "with large images when using subfolders" do fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) } - fab!(:post) do - Fabricate(:post, raw: <<~HTML) + fab!(:post) { Fabricate(:post, raw: <<~HTML) } HTML - end let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } before do set_subfolder "/subfolder" - stub_request(:get, "http://#{Discourse.current_hostname}/subfolder#{upload.url}").to_return(status: 200, body: File.new(Discourse.store.path_for(upload))) + stub_request( + :get, + "http://#{Discourse.current_hostname}/subfolder#{upload.url}", + ).to_return(status: 200, body: File.new(Discourse.store.path_for(upload))) SiteSetting.max_image_height = 2000 SiteSetting.create_thumbnails = true @@ -634,7 +637,7 @@ RSpec.describe CookedPostProcessor do it "generates overlay information" do cpp.post_process - expect(cpp.html). to match_html <<~HTML + expect(cpp.html).to match_html <<~HTML

    HTML @@ -649,17 +652,14 @@ RSpec.describe CookedPostProcessor do

    HTML end - end context "with title and alt" do fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) } - fab!(:post) do - Fabricate(:post, raw: <<~HTML) + fab!(:post) { Fabricate(:post, raw: <<~HTML) } RED HTML - end let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } @@ -677,17 +677,14 @@ RSpec.describe CookedPostProcessor do expect(cpp).to be_dirty end - end context "with title only" do fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) } - fab!(:post) do - Fabricate(:post, raw: <<~HTML) + fab!(:post) { Fabricate(:post, raw: <<~HTML) } HTML - end let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } @@ -705,17 +702,14 @@ RSpec.describe CookedPostProcessor do expect(cpp).to be_dirty end - end context "with alt only" do fab!(:upload) { Fabricate(:image_upload, width: 1750, height: 2000) } - fab!(:post) do - Fabricate(:post, raw: <<~HTML) + fab!(:post) { Fabricate(:post, raw: <<~HTML) } RED HTML - end let(:cpp) { CookedPostProcessor.new(post, disable_dominant_color: true) } @@ -733,7 +727,6 @@ RSpec.describe CookedPostProcessor do expect(cpp).to be_dirty end - end context "with topic image" do @@ -839,29 +832,29 @@ RSpec.describe CookedPostProcessor do let(:cpp) { CookedPostProcessor.new(post) } it "returns the size when width and height are specified" do - img = { 'src' => 'http://foo.bar/image3.png', 'width' => 50, 'height' => 70 } + img = { "src" => "http://foo.bar/image3.png", "width" => 50, "height" => 70 } expect(cpp.get_size_from_attributes(img)).to eq([50, 70]) end it "returns the size when width and height are floats" do - img = { 'src' => 'http://foo.bar/image3.png', 'width' => 50.2, 'height' => 70.1 } + img = { "src" => "http://foo.bar/image3.png", "width" => 50.2, "height" => 70.1 } expect(cpp.get_size_from_attributes(img)).to eq([50, 70]) end it "resizes when only width is specified" do - img = { 'src' => 'http://foo.bar/image3.png', 'width' => 100 } + img = { "src" => "http://foo.bar/image3.png", "width" => 100 } FastImage.expects(:size).returns([200, 400]) expect(cpp.get_size_from_attributes(img)).to eq([100, 200]) end it "resizes when only height is specified" do - img = { 'src' => 'http://foo.bar/image3.png', 'height' => 100 } + img = { "src" => "http://foo.bar/image3.png", "height" => 100 } FastImage.expects(:size).returns([100, 300]) expect(cpp.get_size_from_attributes(img)).to eq([33, 100]) end it "doesn't raise an error with a weird url" do - img = { 'src' => nil, 'height' => 100 } + img = { "src" => nil, "height" => 100 } expect(cpp.get_size_from_attributes(img)).to be_nil end end @@ -940,7 +933,9 @@ RSpec.describe CookedPostProcessor do it "returns a generic name for pasted images" do upload = build(:upload, original_filename: "blob.png") - expect(cpp.get_filename(upload, "http://domain.com/image.png")).to eq(I18n.t('upload.pasted_image_filename')) + expect(cpp.get_filename(upload, "http://domain.com/image.png")).to eq( + I18n.t("upload.pasted_image_filename"), + ) end end @@ -952,10 +947,10 @@ RSpec.describe CookedPostProcessor do cpp = CookedPostProcessor.new(post, disable_dominant_color: true) cpp.post_process - doc = Nokogiri::HTML5::fragment(cpp.html) + doc = Nokogiri::HTML5.fragment(cpp.html) - expect(doc.css('.lightbox-wrapper').size).to eq(1) - expect(doc.css('img').first['srcset']).to_not eq(nil) + expect(doc.css(".lightbox-wrapper").size).to eq(1) + expect(doc.css("img").first["srcset"]).to_not eq(nil) end it "processes animated images correctly" do @@ -968,35 +963,52 @@ RSpec.describe CookedPostProcessor do cpp = CookedPostProcessor.new(post, disable_dominant_color: true) cpp.post_process - doc = Nokogiri::HTML5::fragment(cpp.html) - expect(doc.css('.lightbox-wrapper').size).to eq(0) - expect(doc.css('img').first['src']).to include(upload.url) - expect(doc.css('img').first['srcset']).to eq(nil) - expect(doc.css('img.animated').size).to eq(1) + doc = Nokogiri::HTML5.fragment(cpp.html) + expect(doc.css(".lightbox-wrapper").size).to eq(0) + expect(doc.css("img").first["src"]).to include(upload.url) + expect(doc.css("img").first["srcset"]).to eq(nil) + expect(doc.css("img.animated").size).to eq(1) end context "with giphy/tenor images" do before do - CookedPostProcessor.any_instance.stubs(:get_size).with("https://media2.giphy.com/media/7Oifk90VrCdNe/giphy.webp").returns([311, 280]) - CookedPostProcessor.any_instance.stubs(:get_size).with("https://media1.tenor.com/images/20c7ddd5e84c7427954f430439c5209d/tenor.gif").returns([833, 104]) + CookedPostProcessor + .any_instance + .stubs(:get_size) + .with("https://media2.giphy.com/media/7Oifk90VrCdNe/giphy.webp") + .returns([311, 280]) + CookedPostProcessor + .any_instance + .stubs(:get_size) + .with("https://media1.tenor.com/images/20c7ddd5e84c7427954f430439c5209d/tenor.gif") + .returns([833, 104]) end it "marks giphy images as animated" do - post = Fabricate(:post, raw: "![tennis-gif|311x280](https://media2.giphy.com/media/7Oifk90VrCdNe/giphy.webp)") + post = + Fabricate( + :post, + raw: "![tennis-gif|311x280](https://media2.giphy.com/media/7Oifk90VrCdNe/giphy.webp)", + ) cpp = CookedPostProcessor.new(post, disable_dominant_color: true) cpp.post_process - doc = Nokogiri::HTML5::fragment(cpp.html) - expect(doc.css('img.animated').size).to eq(1) + doc = Nokogiri::HTML5.fragment(cpp.html) + expect(doc.css("img.animated").size).to eq(1) end it "marks giphy images as animated" do - post = Fabricate(:post, raw: "![cat](https://media1.tenor.com/images/20c7ddd5e84c7427954f430439c5209d/tenor.gif)") + post = + Fabricate( + :post, + raw: + "![cat](https://media1.tenor.com/images/20c7ddd5e84c7427954f430439c5209d/tenor.gif)", + ) cpp = CookedPostProcessor.new(post, disable_dominant_color: true) cpp.post_process - doc = Nokogiri::HTML5::fragment(cpp.html) - expect(doc.css('img.animated').size).to eq(1) + doc = Nokogiri::HTML5.fragment(cpp.html) + expect(doc.css("img.animated").size).to eq(1) end end @@ -1010,24 +1022,27 @@ RSpec.describe CookedPostProcessor do cpp = CookedPostProcessor.new(post, disable_dominant_color: true) cpp.post_process - doc = Nokogiri::HTML5::fragment(cpp.html) - expect(doc.css('.lightbox-wrapper').size).to eq(0) - expect(doc.css('img').first['srcset']).to_not eq(nil) + doc = Nokogiri::HTML5.fragment(cpp.html) + expect(doc.css(".lightbox-wrapper").size).to eq(0) + expect(doc.css("img").first["srcset"]).to_not eq(nil) end it "optimizes images in Onebox" do - Oneboxer.expects(:onebox) + Oneboxer + .expects(:onebox) .with("https://discourse.org", anything) - .returns("") + .returns( + "", + ) post = Fabricate(:post, raw: "https://discourse.org") cpp = CookedPostProcessor.new(post, disable_dominant_color: true) cpp.post_process - doc = Nokogiri::HTML5::fragment(cpp.html) - expect(doc.css('.lightbox-wrapper').size).to eq(0) - expect(doc.css('img').first['srcset']).to_not eq(nil) + doc = Nokogiri::HTML5.fragment(cpp.html) + expect(doc.css(".lightbox-wrapper").size).to eq(0) + expect(doc.css("img").first["srcset"]).to_not eq(nil) end end @@ -1038,7 +1053,12 @@ RSpec.describe CookedPostProcessor do before do Oneboxer .expects(:onebox) - .with("http://www.youtube.com/watch?v=9bZkp7q19f0", invalidate_oneboxes: true, user_id: nil, category_id: post.topic.category_id) + .with( + "http://www.youtube.com/watch?v=9bZkp7q19f0", + invalidate_oneboxes: true, + user_id: nil, + category_id: post.topic.category_id, + ) .returns("
    GANGNAM STYLE
    ") cpp.post_process_oneboxes @@ -1050,29 +1070,41 @@ RSpec.describe CookedPostProcessor do end describe "replacing downloaded onebox image" do - let(:url) { 'https://image.com/my-avatar' } - let(:image_url) { 'https://image.com/avatar.png' } + let(:url) { "https://image.com/my-avatar" } + let(:image_url) { "https://image.com/avatar.png" } it "successfully replaces the image" do - Oneboxer.stubs(:onebox).with(url, anything).returns("") + Oneboxer + .stubs(:onebox) + .with(url, anything) + .returns("") post = Fabricate(:post, raw: url) upload.update!(url: "https://test.s3.amazonaws.com/something.png") - PostHotlinkedMedia.create!(url: "//image.com/avatar.png", post: post, status: 'downloaded', upload: upload) + PostHotlinkedMedia.create!( + url: "//image.com/avatar.png", + post: post, + status: "downloaded", + upload: upload, + ) cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) stub_image_size(width: 100, height: 200) cpp.post_process_oneboxes - expect(cpp.doc.to_s).to eq("

    ") + expect(cpp.doc.to_s).to eq( + "

    ", + ) upload.destroy! cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) stub_image_size(width: 100, height: 200) cpp.post_process_oneboxes - expect(cpp.doc.to_s).to eq("

    ") + expect(cpp.doc.to_s).to eq( + "

    ", + ) Oneboxer.unstub(:onebox) end @@ -1086,12 +1118,20 @@ RSpec.describe CookedPostProcessor do end it "does not use the direct URL, uses the cooked URL instead (because of the private ACL preventing w/h fetch)" do - Oneboxer.stubs(:onebox).with(url, anything).returns("") + Oneboxer + .stubs(:onebox) + .with(url, anything) + .returns("") post = Fabricate(:post, raw: url) upload.update!(url: "https://test.s3.amazonaws.com/something.png") - PostHotlinkedMedia.create!(url: "//image.com/avatar.png", post: post, status: 'downloaded', upload: upload) + PostHotlinkedMedia.create!( + url: "//image.com/avatar.png", + post: post, + status: "downloaded", + upload: upload, + ) cooked_url = "https://localhost/secure-uploads/test.png" UrlHelper.expects(:cook_url).with(upload.url, secure: true).returns(cooked_url) @@ -1100,31 +1140,38 @@ RSpec.describe CookedPostProcessor do stub_image_size(width: 100, height: 200) cpp.post_process_oneboxes - expect(cpp.doc.to_s).to eq("

    ") + expect(cpp.doc.to_s).to eq( + "

    ", + ) end end end it "replaces large image placeholder" do SiteSetting.max_image_size_kb = 4096 - url = 'https://image.com/my-avatar' - image_url = 'https://image.com/avatar.png' + url = "https://image.com/my-avatar" + image_url = "https://image.com/avatar.png" - Oneboxer.stubs(:onebox).with(url, anything).returns("") + Oneboxer + .stubs(:onebox) + .with(url, anything) + .returns("") post = Fabricate(:post, raw: url) - PostHotlinkedMedia.create!(url: "//image.com/avatar.png", post: post, status: 'too_large') + PostHotlinkedMedia.create!(url: "//image.com/avatar.png", post: post, status: "too_large") cpp = CookedPostProcessor.new(post, invalidate_oneboxes: true) cpp.post_process expect(cpp.doc.to_s).to match(/
    /) - expect(cpp.doc.to_s).to include(I18n.t("upload.placeholders.too_large_humanized", max_size: "4 MB")) + expect(cpp.doc.to_s).to include( + I18n.t("upload.placeholders.too_large_humanized", max_size: "4 MB"), + ) end it "removes large images from onebox" do - url = 'https://example.com/article' + url = "https://example.com/article" Oneboxer.stubs(:onebox).with(url, anything).returns <<~HTML
    ") + Oneboxer + .expects(:onebox) + .with( + "http://www.youtube.com/watch?v=9bZkp7q19f0", + invalidate_oneboxes: true, + user_id: nil, + category_id: post.topic.category_id, + ) + .returns( + "
    ", + ) cpp.post_process_oneboxes - expect(cpp.html).to match_html('') + expect(cpp.html).to match_html( + '', + ) end it "applies aspect ratio when wrapped in link" do - Oneboxer.expects(:onebox) - .with("http://www.youtube.com/watch?v=9bZkp7q19f0", invalidate_oneboxes: true, user_id: nil, category_id: post.topic.category_id) - .returns("
    ") + Oneboxer + .expects(:onebox) + .with( + "http://www.youtube.com/watch?v=9bZkp7q19f0", + invalidate_oneboxes: true, + user_id: nil, + category_id: post.topic.category_id, + ) + .returns( + " HTML - expect(ExcerptParser.get_excerpt(html, 100, keep_quotes: true)).to eq("This is a quoted text.") + expect(ExcerptParser.get_excerpt(html, 100, keep_quotes: true)).to eq( + "This is a quoted text.", + ) end end end diff --git a/spec/lib/feed_element_installer_spec.rb b/spec/lib/feed_element_installer_spec.rb index 4e327d41d0..3239b5befd 100644 --- a/spec/lib/feed_element_installer_spec.rb +++ b/spec/lib/feed_element_installer_spec.rb @@ -1,37 +1,37 @@ # frozen_string_literal: true -require 'feed_element_installer' +require "feed_element_installer" RSpec.describe FeedElementInstaller do - describe '#install_rss_element' do - let(:raw_feed) { file_from_fixtures('feed.rss', 'feed').read } + describe "#install_rss_element" do + let(:raw_feed) { file_from_fixtures("feed.rss", "feed").read } - it 'creates parsing for a non-standard, namespaced element' do - FeedElementInstaller.install('discourse:username', raw_feed) + it "creates parsing for a non-standard, namespaced element" do + FeedElementInstaller.install("discourse:username", raw_feed) feed = RSS::Parser.parse(raw_feed) - expect(feed.items.first.discourse_username).to eq('xrav3nz') + expect(feed.items.first.discourse_username).to eq("xrav3nz") end - it 'does not create parsing for a non-standard, non-namespaced element' do - FeedElementInstaller.install('username', raw_feed) + it "does not create parsing for a non-standard, non-namespaced element" do + FeedElementInstaller.install("username", raw_feed) feed = RSS::Parser.parse(raw_feed) expect { feed.items.first.username }.to raise_error(NoMethodError) end end - describe '#install_atom_element' do - let(:raw_feed) { file_from_fixtures('feed.atom', 'feed').read } + describe "#install_atom_element" do + let(:raw_feed) { file_from_fixtures("feed.atom", "feed").read } - it 'creates parsing for a non-standard, namespaced element' do - FeedElementInstaller.install('discourse:username', raw_feed) + it "creates parsing for a non-standard, namespaced element" do + FeedElementInstaller.install("discourse:username", raw_feed) feed = RSS::Parser.parse(raw_feed) - expect(feed.items.first.discourse_username).to eq('xrav3nz') + expect(feed.items.first.discourse_username).to eq("xrav3nz") end - it 'does not create parsing for a non-standard, non-namespaced element' do - FeedElementInstaller.install('username', raw_feed) + it "does not create parsing for a non-standard, non-namespaced element" do + FeedElementInstaller.install("username", raw_feed) feed = RSS::Parser.parse(raw_feed) expect { feed.items.first.username }.to raise_error(NoMethodError) diff --git a/spec/lib/feed_item_accessor_spec.rb b/spec/lib/feed_item_accessor_spec.rb index c86baacecc..bbaaeb7d26 100644 --- a/spec/lib/feed_item_accessor_spec.rb +++ b/spec/lib/feed_item_accessor_spec.rb @@ -1,44 +1,46 @@ # frozen_string_literal: true -require 'rss' -require 'feed_item_accessor' +require "rss" +require "feed_item_accessor" RSpec.describe FeedItemAccessor do - context 'for ATOM feed' do - let(:atom_feed) { RSS::Parser.parse(file_from_fixtures('feed.atom', 'feed'), false) } + context "for ATOM feed" do + let(:atom_feed) { RSS::Parser.parse(file_from_fixtures("feed.atom", "feed"), false) } let(:atom_feed_item) { atom_feed.items.first } let(:item_accessor) { FeedItemAccessor.new(atom_feed_item) } - describe '#element_content' do - it { expect(item_accessor.element_content('title')).to eq(atom_feed_item.title.content) } + describe "#element_content" do + it { expect(item_accessor.element_content("title")).to eq(atom_feed_item.title.content) } end - describe '#link' do + describe "#link" do it { expect(item_accessor.link).to eq(atom_feed_item.link.href) } end end - context 'for RSS feed' do - let(:rss_feed) { RSS::Parser.parse(file_from_fixtures('feed.rss', 'feed'), false) } + context "for RSS feed" do + let(:rss_feed) { RSS::Parser.parse(file_from_fixtures("feed.rss", "feed"), false) } let(:rss_feed_item) { rss_feed.items.first } let(:item_accessor) { FeedItemAccessor.new(rss_feed_item) } - describe '#element_content' do - it { expect(item_accessor.element_content('title')).to eq(rss_feed_item.title) } + describe "#element_content" do + it { expect(item_accessor.element_content("title")).to eq(rss_feed_item.title) } end - describe '#link' do + describe "#link" do it { expect(item_accessor.link).to eq(rss_feed_item.link) } end end - context 'with multiple links' do - let(:rss_feed) { RSS::Parser.parse(file_from_fixtures('multiple-links.atom', 'feed'), false) } + context "with multiple links" do + let(:rss_feed) { RSS::Parser.parse(file_from_fixtures("multiple-links.atom", "feed"), false) } let(:rss_feed_item) { rss_feed.items.first } let(:item_accessor) { FeedItemAccessor.new(rss_feed_item) } - describe '#link' do - it 'gets the web page link' do - expect(item_accessor.link).to eq('http://workspaceupdates.googleblog.com/2022/01/improved-editing-experience-in-google.html') + describe "#link" do + it "gets the web page link" do + expect(item_accessor.link).to eq( + "http://workspaceupdates.googleblog.com/2022/01/improved-editing-experience-in-google.html", + ) end end end diff --git a/spec/lib/file_helper_spec.rb b/spec/lib/file_helper_spec.rb index e8bc0c3ef5..4337ae31bc 100644 --- a/spec/lib/file_helper_spec.rb +++ b/spec/lib/file_helper_spec.rb @@ -1,14 +1,13 @@ # frozen_string_literal: true -require 'file_helper' +require "file_helper" RSpec.describe FileHelper do - let(:url) { "https://eviltrout.com/trout.png" } let(:png) { File.read("#{Rails.root}/spec/fixtures/images/cropped.png") } before do - stub_request(:any, /https:\/\/eviltrout.com/) + stub_request(:any, %r{https://eviltrout.com}) stub_request(:get, url).to_return(body: png) end @@ -21,9 +20,9 @@ RSpec.describe FileHelper do begin FileHelper.download( url, - max_file_size: 10000, - tmp_file_name: 'trouttmp', - follow_redirect: true + max_file_size: 10_000, + tmp_file_name: "trouttmp", + follow_redirect: true, ) rescue => e expect(e.io.status[0]).to eq("404") @@ -36,12 +35,13 @@ RSpec.describe FileHelper do url2 = "https://test.com/image.png" stub_request(:get, url).to_return(status: 302, body: "", headers: { location: url2 }) - missing = FileHelper.download( - url, - max_file_size: 10000, - tmp_file_name: 'trouttmp', - follow_redirect: false - ) + missing = + FileHelper.download( + url, + max_file_size: 10_000, + tmp_file_name: "trouttmp", + follow_redirect: false, + ) expect(missing).to eq(nil) end @@ -52,12 +52,13 @@ RSpec.describe FileHelper do stub_request(:get, url2).to_return(status: 200, body: "i am the body") begin - found = FileHelper.download( - url, - max_file_size: 10000, - tmp_file_name: 'trouttmp', - follow_redirect: true - ) + found = + FileHelper.download( + url, + max_file_size: 10_000, + tmp_file_name: "trouttmp", + follow_redirect: true, + ) expect(found.read).to eq("i am the body") ensure @@ -75,9 +76,9 @@ RSpec.describe FileHelper do begin FileHelper.download( url, - max_file_size: 10000, - tmp_file_name: 'trouttmp', - follow_redirect: false + max_file_size: 10_000, + tmp_file_name: "trouttmp", + follow_redirect: false, ) rescue => e expect(e.io.status[0]).to eq("404") @@ -88,11 +89,7 @@ RSpec.describe FileHelper do it "returns a file with the image" do begin - tmpfile = FileHelper.download( - url, - max_file_size: 10000, - tmp_file_name: 'trouttmp' - ) + tmpfile = FileHelper.download(url, max_file_size: 10_000, tmp_file_name: "trouttmp") expect(Base64.encode64(tmpfile.read)).to eq(Base64.encode64(png)) ensure @@ -103,11 +100,12 @@ RSpec.describe FileHelper do it "works with a protocol relative url" do begin - tmpfile = FileHelper.download( - "//eviltrout.com/trout.png", - max_file_size: 10000, - tmp_file_name: 'trouttmp' - ) + tmpfile = + FileHelper.download( + "//eviltrout.com/trout.png", + max_file_size: 10_000, + tmp_file_name: "trouttmp", + ) expect(Base64.encode64(tmpfile.read)).to eq(Base64.encode64(png)) ensure @@ -116,25 +114,27 @@ RSpec.describe FileHelper do end end - describe 'when max_file_size is exceeded' do - it 'should return nil' do - tmpfile = FileHelper.download( - "//eviltrout.com/trout.png", - max_file_size: 1, - tmp_file_name: 'trouttmp' - ) + describe "when max_file_size is exceeded" do + it "should return nil" do + tmpfile = + FileHelper.download( + "//eviltrout.com/trout.png", + max_file_size: 1, + tmp_file_name: "trouttmp", + ) expect(tmpfile).to eq(nil) end - it 'is able to retain the tmpfile' do + it "is able to retain the tmpfile" do begin - tmpfile = FileHelper.download( - "//eviltrout.com/trout.png", - max_file_size: 1, - tmp_file_name: 'trouttmp', - retain_on_max_file_size_exceeded: true - ) + tmpfile = + FileHelper.download( + "//eviltrout.com/trout.png", + max_file_size: 1, + tmp_file_name: "trouttmp", + retain_on_max_file_size_exceeded: true, + ) expect(tmpfile.closed?).to eq(false) ensure @@ -144,22 +144,16 @@ RSpec.describe FileHelper do end end - describe 'when url is a jpeg' do + describe "when url is a jpeg" do let(:url) { "https://eviltrout.com/trout.jpg" } it "should prioritize the content type returned by the response" do begin - stub_request(:get, url).to_return(body: png, headers: { - "content-type": "image/png" - }) + stub_request(:get, url).to_return(body: png, headers: { "content-type": "image/png" }) - tmpfile = FileHelper.download( - url, - max_file_size: 10000, - tmp_file_name: 'trouttmp' - ) + tmpfile = FileHelper.download(url, max_file_size: 10_000, tmp_file_name: "trouttmp") - expect(File.extname(tmpfile)).to eq('.png') + expect(File.extname(tmpfile)).to eq(".png") ensure tmpfile&.close tmpfile&.unlink @@ -167,5 +161,4 @@ RSpec.describe FileHelper do end end end - end diff --git a/spec/lib/file_store/base_store_spec.rb b/spec/lib/file_store/base_store_spec.rb index 18bf38d08c..0214ebdbc6 100644 --- a/spec/lib/file_store/base_store_spec.rb +++ b/spec/lib/file_store/base_store_spec.rb @@ -3,34 +3,32 @@ RSpec.describe FileStore::BaseStore do fab!(:upload) do Upload.delete(9999) # In case of any collisions - Fabricate(:upload, id: 9999, sha1: Digest::SHA1.hexdigest('9999')) + Fabricate(:upload, id: 9999, sha1: Digest::SHA1.hexdigest("9999")) end - describe '#get_path_for_upload' do + describe "#get_path_for_upload" do def expect_correct_path(expected_path) expect(described_class.new.get_path_for_upload(upload)).to eq(expected_path) end context "with empty URL" do - before do - upload.update!(url: "") + before { upload.update!(url: "") } + + it "should return the right path" do + expect_correct_path("original/2X/4/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png") end - it 'should return the right path' do - expect_correct_path('original/2X/4/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png') - end - - describe 'when Upload#extension has not been set' do - it 'should return the right path' do + describe "when Upload#extension has not been set" do + it "should return the right path" do upload.update!(extension: nil) - expect_correct_path('original/2X/4/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png') + expect_correct_path("original/2X/4/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png") end end - describe 'when id is negative' do - it 'should return the right depth' do + describe "when id is negative" do + it "should return the right depth" do upload.update!(id: -999) - expect_correct_path('original/1X/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png') + expect_correct_path("original/1X/4170ac2a2782a1516fe9e13d7322ae482c1bd594.png") end end end @@ -38,63 +36,92 @@ RSpec.describe FileStore::BaseStore do context "with existing URL" do context "with regular site" do it "returns the correct path for files stored on local storage" do - upload.update!(url: "/uploads/default/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") + upload.update!( + url: "/uploads/default/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg", + ) expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") - upload.update!(url: "/uploads/default/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg") + upload.update!( + url: "/uploads/default/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg", + ) expect_correct_path("original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg") end it "returns the correct path for files stored on S3" do - upload.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") + upload.update!( + url: + "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg", + ) expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") - upload.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg") + upload.update!( + url: + "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg", + ) expect_correct_path("original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg") end end context "with multisite" do it "returns the correct path for files stored on local storage" do - upload.update!(url: "/uploads/foo/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") + upload.update!( + url: "/uploads/foo/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg", + ) expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") - upload.update!(url: "/uploads/foo/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg") + upload.update!( + url: "/uploads/foo/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg", + ) expect_correct_path("original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg") end it "returns the correct path for files stored on S3" do - upload.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/foo/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") + upload.update!( + url: + "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/foo/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg", + ) expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") - upload.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/foo/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg") + upload.update!( + url: + "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/foo/original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg", + ) expect_correct_path("original/3X/63/63b76551662ccea1a594e161c37dd35188d77657.jpeg") end it "returns the correct path when the site name is 'original'" do - upload.update!(url: "/uploads/original/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") + upload.update!( + url: "/uploads/original/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg", + ) expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") - upload.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/original/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") + upload.update!( + url: + "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/original/original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg", + ) expect_correct_path("original/1X/63b76551662ccea1a594e161c37dd35188d77657.jpeg") end end end end - describe '#get_path_for_optimized_image' do + describe "#get_path_for_optimized_image" do let!(:upload) { Fabricate.build(:upload, id: 100) } let!(:optimized_path) { "optimized/1X/#{upload.sha1}_1_100x200.png" } context "with empty URL" do - it 'should return the right path' do + it "should return the right path" do optimized = Fabricate.build(:optimized_image, upload: upload, version: 1) - expect(FileStore::BaseStore.new.get_path_for_optimized_image(optimized)).to eq(optimized_path) + expect(FileStore::BaseStore.new.get_path_for_optimized_image(optimized)).to eq( + optimized_path, + ) end - it 'should return the right path for `nil` version' do + it "should return the right path for `nil` version" do optimized = Fabricate.build(:optimized_image, upload: upload, version: nil) - expect(FileStore::BaseStore.new.get_path_for_optimized_image(optimized)).to eq(optimized_path) + expect(FileStore::BaseStore.new.get_path_for_optimized_image(optimized)).to eq( + optimized_path, + ) end end @@ -113,7 +140,10 @@ RSpec.describe FileStore::BaseStore do end it "returns the correct path for files stored on S3" do - optimized.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/optimized/1X/#{upload.sha1}_1_100x200.jpg") + optimized.update!( + url: + "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/optimized/1X/#{upload.sha1}_1_100x200.jpg", + ) expect_correct_optimized_path end end @@ -125,7 +155,10 @@ RSpec.describe FileStore::BaseStore do end it "returns the correct path for files stored on S3" do - optimized.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/foo/optimized/1X/#{upload.sha1}_1_100x200.jpg") + optimized.update!( + url: + "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/foo/optimized/1X/#{upload.sha1}_1_100x200.jpg", + ) expect_correct_optimized_path end @@ -133,14 +166,17 @@ RSpec.describe FileStore::BaseStore do optimized.update!(url: "/uploads/optimized/optimized/1X/#{upload.sha1}_1_100x200.jpg") expect_correct_optimized_path - optimized.update!(url: "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/optimized/optimized/1X/#{upload.sha1}_1_100x200.jpg") + optimized.update!( + url: + "//bucket-name.s3.dualstack.us-west-2.amazonaws.com/uploads/optimized/optimized/1X/#{upload.sha1}_1_100x200.jpg", + ) expect_correct_optimized_path end end end end - describe '#download' do + describe "#download" do before do setup_s3 stub_request(:get, upload_s3.url).to_return(status: 200, body: "Hello world") @@ -169,7 +205,10 @@ RSpec.describe FileStore::BaseStore do it "should return the file when s3 cdn enabled" do SiteSetting.s3_cdn_url = "https://cdn.s3.#{SiteSetting.s3_region}.amazonaws.com" - stub_request(:get, Discourse.store.cdn_url(upload_s3.url)).to_return(status: 200, body: "Hello world") + stub_request(:get, Discourse.store.cdn_url(upload_s3.url)).to_return( + status: 200, + body: "Hello world", + ) file = store.download(upload_s3) diff --git a/spec/lib/file_store/local_store_spec.rb b/spec/lib/file_store/local_store_spec.rb index 0ed98ef01e..d020e13022 100644 --- a/spec/lib/file_store/local_store_spec.rb +++ b/spec/lib/file_store/local_store_spec.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require 'file_store/local_store' +require "file_store/local_store" RSpec.describe FileStore::LocalStore do - let(:store) { FileStore::LocalStore.new } fab!(:upload) { Fabricate(:upload) } @@ -13,25 +12,24 @@ RSpec.describe FileStore::LocalStore do fab!(:optimized_image) { Fabricate(:optimized_image) } describe "#store_upload" do - it "returns a relative url" do store.expects(:copy_file) - expect(store.store_upload(uploaded_file, upload)).to match(/\/#{upload_path}\/original\/.+#{upload.sha1}\.png/) + expect(store.store_upload(uploaded_file, upload)).to match( + %r{/#{upload_path}/original/.+#{upload.sha1}\.png}, + ) end - end describe "#store_optimized_image" do - it "returns a relative url" do store.expects(:copy_file) - expect(store.store_optimized_image({}, optimized_image)).to match(/\/#{upload_path}\/optimized\/.+#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200\.png/) + expect(store.store_optimized_image({}, optimized_image)).to match( + %r{/#{upload_path}/optimized/.+#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200\.png}, + ) end - end describe "#remove_upload" do - it "does not delete non uploaded" do FileUtils.expects(:mkdir_p).never store.remove_upload(upload) @@ -39,10 +37,10 @@ RSpec.describe FileStore::LocalStore do it "moves the file to the tombstone" do begin - upload = UploadCreator.new( - file_from_fixtures("smallest.png"), - "smallest.png" - ).create_for(Fabricate(:user).id) + upload = + UploadCreator.new(file_from_fixtures("smallest.png"), "smallest.png").create_for( + Fabricate(:user).id, + ) path = store.path_for(upload) mtime = File.mtime(path) @@ -54,21 +52,18 @@ RSpec.describe FileStore::LocalStore do expect(File.exist?(tombstone_path)).to eq(true) expect(File.mtime(tombstone_path)).to_not eq(mtime) ensure - [path, tombstone_path].each do |file_path| - File.delete(file_path) if File.exist?(file_path) - end + [path, tombstone_path].each { |file_path| File.delete(file_path) if File.exist?(file_path) } end end - end describe "#remove_optimized_image" do it "moves the file to the tombstone" do begin - upload = UploadCreator.new( - file_from_fixtures("smallest.png"), - "smallest.png" - ).create_for(Fabricate(:user).id) + upload = + UploadCreator.new(file_from_fixtures("smallest.png"), "smallest.png").create_for( + Fabricate(:user).id, + ) upload.create_thumbnail!(1, 1) upload.reload @@ -81,41 +76,47 @@ RSpec.describe FileStore::LocalStore do expect(File.exist?(tombstone_path)).to eq(true) ensure - [path, tombstone_path].each do |file_path| - File.delete(file_path) if File.exist?(file_path) - end + [path, tombstone_path].each { |file_path| File.delete(file_path) if File.exist?(file_path) } end end - end describe "#has_been_uploaded?" do - it "identifies relatives urls" do expect(store.has_been_uploaded?("/#{upload_path}/42/0123456789ABCDEF.jpg")).to eq(true) end it "identifies local urls" do Discourse.stubs(:base_url_no_prefix).returns("http://discuss.site.com") - expect(store.has_been_uploaded?("http://discuss.site.com/#{upload_path}/42/0123456789ABCDEF.jpg")).to eq(true) - expect(store.has_been_uploaded?("//discuss.site.com/#{upload_path}/42/0123456789ABCDEF.jpg")).to eq(true) + expect( + store.has_been_uploaded?("http://discuss.site.com/#{upload_path}/42/0123456789ABCDEF.jpg"), + ).to eq(true) + expect( + store.has_been_uploaded?("//discuss.site.com/#{upload_path}/42/0123456789ABCDEF.jpg"), + ).to eq(true) end it "identifies local urls when using a CDN" do Rails.configuration.action_controller.stubs(:asset_host).returns("http://my.cdn.com") - expect(store.has_been_uploaded?("http://my.cdn.com/#{upload_path}/42/0123456789ABCDEF.jpg")).to eq(true) - expect(store.has_been_uploaded?("//my.cdn.com/#{upload_path}/42/0123456789ABCDEF.jpg")).to eq(true) + expect( + store.has_been_uploaded?("http://my.cdn.com/#{upload_path}/42/0123456789ABCDEF.jpg"), + ).to eq(true) + expect(store.has_been_uploaded?("//my.cdn.com/#{upload_path}/42/0123456789ABCDEF.jpg")).to eq( + true, + ) end it "does not match dummy urls" do - expect(store.has_been_uploaded?("http://domain.com/#{upload_path}/42/0123456789ABCDEF.jpg")).to eq(false) - expect(store.has_been_uploaded?("//domain.com/#{upload_path}/42/0123456789ABCDEF.jpg")).to eq(false) + expect( + store.has_been_uploaded?("http://domain.com/#{upload_path}/42/0123456789ABCDEF.jpg"), + ).to eq(false) + expect(store.has_been_uploaded?("//domain.com/#{upload_path}/42/0123456789ABCDEF.jpg")).to eq( + false, + ) end - end describe "#absolute_base_url" do - it "is present" do expect(store.absolute_base_url).to eq("http://test.localhost/#{upload_path}") end @@ -124,11 +125,9 @@ RSpec.describe FileStore::LocalStore do set_subfolder "/forum" expect(store.absolute_base_url).to eq("http://test.localhost/forum/#{upload_path}") end - end describe "#relative_base_url" do - it "is present" do expect(store.relative_base_url).to eq("/#{upload_path}") end @@ -137,7 +136,6 @@ RSpec.describe FileStore::LocalStore do set_subfolder "/forum" expect(store.relative_base_url).to eq("/forum/#{upload_path}") end - end it "is internal" do @@ -147,22 +145,25 @@ RSpec.describe FileStore::LocalStore do describe "#get_path_for" do it "returns the correct path" do - expect(store.get_path_for("original", upload.id, upload.sha1, ".#{upload.extension}")) - .to match(%r|/#{upload_path}/original/.+#{upload.sha1}\.png|) + expect( + store.get_path_for("original", upload.id, upload.sha1, ".#{upload.extension}"), + ).to match(%r{/#{upload_path}/original/.+#{upload.sha1}\.png}) end end describe "#get_path_for_upload" do it "returns the correct path" do - expect(store.get_path_for_upload(upload)) - .to match(%r|/#{upload_path}/original/.+#{upload.sha1}\.png|) + expect(store.get_path_for_upload(upload)).to match( + %r{/#{upload_path}/original/.+#{upload.sha1}\.png}, + ) end end describe "#get_path_for_optimized_image" do it "returns the correct path" do - expect(store.get_path_for_optimized_image(optimized_image)) - .to match(%r|/#{upload_path}/optimized/.+#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200\.png|) + expect(store.get_path_for_optimized_image(optimized_image)).to match( + %r{/#{upload_path}/optimized/.+#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200\.png}, + ) end end end diff --git a/spec/lib/file_store/s3_store_spec.rb b/spec/lib/file_store/s3_store_spec.rb index 0473c5ab19..856ff53e68 100644 --- a/spec/lib/file_store/s3_store_spec.rb +++ b/spec/lib/file_store/s3_store_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'file_store/s3_store' -require 'file_store/local_store' +require "file_store/s3_store" +require "file_store/local_store" RSpec.describe FileStore::S3Store do let(:store) { FileStore::S3Store.new } @@ -15,31 +15,37 @@ RSpec.describe FileStore::S3Store do fab!(:optimized_image) { Fabricate(:optimized_image) } let(:optimized_image_file) { file_from_fixtures("logo.png") } let(:uploaded_file) { file_from_fixtures("logo.png") } - fab!(:upload) do - Fabricate(:upload, sha1: Digest::SHA1.hexdigest('secret image string')) - end + fab!(:upload) { Fabricate(:upload, sha1: Digest::SHA1.hexdigest("secret image string")) } before do setup_s3 - SiteSetting.s3_region = 'us-west-1' + SiteSetting.s3_region = "us-west-1" end - describe 'uploading to s3' do + describe "uploading to s3" do let(:etag) { "etag" } describe "#store_upload" do it "returns an absolute schemaless url" do s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once - s3_bucket.expects(:object).with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.png})).returns(s3_object) - s3_object.expects(:put).with({ - acl: "public-read", - cache_control: "max-age=31556952, public, immutable", - content_type: "image/png", - body: uploaded_file - }).returns(Aws::S3::Types::PutObjectOutput.new(etag: "\"#{etag}\"")) + s3_bucket + .expects(:object) + .with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.png})) + .returns(s3_object) + s3_object + .expects(:put) + .with( + { + acl: "public-read", + cache_control: "max-age=31556952, public, immutable", + content_type: "image/png", + body: uploaded_file, + }, + ) + .returns(Aws::S3::Types::PutObjectOutput.new(etag: "\"#{etag}\"")) expect(store.store_upload(uploaded_file, upload)).to match( - %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/original/\d+X.*/#{upload.sha1}\.png} + %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/original/\d+X.*/#{upload.sha1}\.png}, ) expect(upload.etag).to eq(etag) end @@ -53,10 +59,13 @@ RSpec.describe FileStore::S3Store do it "returns an absolute schemaless url" do s3_helper.expects(:s3_bucket).returns(s3_bucket) - s3_bucket.expects(:object).with(regexp_matches(%r{discourse-uploads/original/\d+X.*/#{upload.sha1}\.png})).returns(s3_object) + s3_bucket + .expects(:object) + .with(regexp_matches(%r{discourse-uploads/original/\d+X.*/#{upload.sha1}\.png})) + .returns(s3_object) expect(store.store_upload(uploaded_file, upload)).to match( - %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/discourse-uploads/original/\d+X.*/#{upload.sha1}\.png} + %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/discourse-uploads/original/\d+X.*/#{upload.sha1}\.png}, ) expect(upload.etag).to eq(etag) end @@ -66,20 +75,30 @@ RSpec.describe FileStore::S3Store do it "saves secure attachment using private ACL" do SiteSetting.prevent_anons_from_downloading_files = true SiteSetting.authorized_extensions = "pdf|png|jpg|gif" - upload = Fabricate(:upload, original_filename: "small.pdf", extension: "pdf", secure: true) + upload = + Fabricate(:upload, original_filename: "small.pdf", extension: "pdf", secure: true) s3_helper.expects(:s3_bucket).returns(s3_bucket) - s3_bucket.expects(:object).with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.pdf})).returns(s3_object) - s3_object.expects(:put).with({ - acl: "private", - cache_control: "max-age=31556952, public, immutable", - content_type: "application/pdf", - content_disposition: "attachment; filename=\"#{upload.original_filename}\"; filename*=UTF-8''#{upload.original_filename}", - body: uploaded_file - }).returns(Aws::S3::Types::PutObjectOutput.new(etag: "\"#{etag}\"")) + s3_bucket + .expects(:object) + .with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.pdf})) + .returns(s3_object) + s3_object + .expects(:put) + .with( + { + acl: "private", + cache_control: "max-age=31556952, public, immutable", + content_type: "application/pdf", + content_disposition: + "attachment; filename=\"#{upload.original_filename}\"; filename*=UTF-8''#{upload.original_filename}", + body: uploaded_file, + }, + ) + .returns(Aws::S3::Types::PutObjectOutput.new(etag: "\"#{etag}\"")) expect(store.store_upload(uploaded_file, upload)).to match( - %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/original/\d+X.*/#{upload.sha1}\.pdf} + %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/original/\d+X.*/#{upload.sha1}\.pdf}, ) end @@ -87,16 +106,25 @@ RSpec.describe FileStore::S3Store do SiteSetting.prevent_anons_from_downloading_files = true s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once - s3_bucket.expects(:object).with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.png})).returns(s3_object).at_least_once - s3_object.expects(:put).with({ - acl: "public-read", - cache_control: "max-age=31556952, public, immutable", - content_type: "image/png", - body: uploaded_file - }).returns(Aws::S3::Types::PutObjectOutput.new(etag: "\"#{etag}\"")) + s3_bucket + .expects(:object) + .with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.png})) + .returns(s3_object) + .at_least_once + s3_object + .expects(:put) + .with( + { + acl: "public-read", + cache_control: "max-age=31556952, public, immutable", + content_type: "image/png", + body: uploaded_file, + }, + ) + .returns(Aws::S3::Types::PutObjectOutput.new(etag: "\"#{etag}\"")) expect(store.store_upload(uploaded_file, upload)).to match( - %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/original/\d+X.*/#{upload.sha1}\.png} + %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/original/\d+X.*/#{upload.sha1}\.png}, ) expect(store.url_for(upload)).to eq(upload.url) @@ -111,29 +139,29 @@ RSpec.describe FileStore::S3Store do it "returns an absolute schemaless url" do s3_helper.expects(:s3_bucket).returns(s3_bucket) - path = %r{optimized/\d+X.*/#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200\.png} + path = + %r{optimized/\d+X.*/#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200\.png} s3_bucket.expects(:object).with(regexp_matches(path)).returns(s3_object) expect(store.store_optimized_image(optimized_image_file, optimized_image)).to match( - %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/#{path}} + %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/#{path}}, ) expect(optimized_image.etag).to eq(etag) end describe "when s3_upload_bucket includes folders path" do - before do - SiteSetting.s3_upload_bucket = "s3-upload-bucket/discourse-uploads" - end + before { SiteSetting.s3_upload_bucket = "s3-upload-bucket/discourse-uploads" } it "returns an absolute schemaless url" do s3_helper.expects(:s3_bucket).returns(s3_bucket) - path = %r{discourse-uploads/optimized/\d+X.*/#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200\.png} + path = + %r{discourse-uploads/optimized/\d+X.*/#{optimized_image.upload.sha1}_#{OptimizedImage::VERSION}_100x200\.png} s3_bucket.expects(:object).with(regexp_matches(path)).returns(s3_object) expect(store.store_optimized_image(optimized_image_file, optimized_image)).to match( - %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/#{path}} + %r{//s3-upload-bucket\.s3\.dualstack\.us-west-1\.amazonaws\.com/#{path}}, ) expect(optimized_image.etag).to eq(etag) end @@ -145,62 +173,85 @@ RSpec.describe FileStore::S3Store do let(:upload_sha1) { Digest::SHA1.hexdigest(File.read(uploaded_file)) } let(:original_filename) { "smallest.png" } let(:s3_client) { Aws::S3::Client.new(stub_responses: true) } - let(:s3_helper) { S3Helper.new(SiteSetting.s3_upload_bucket, '', client: s3_client) } + let(:s3_helper) { S3Helper.new(SiteSetting.s3_upload_bucket, "", client: s3_client) } let(:store) { FileStore::S3Store.new(s3_helper) } let(:upload_opts) do { acl: "public-read", cache_control: "max-age=31556952, public, immutable", content_type: "image/png", - apply_metadata_to_destination: true + apply_metadata_to_destination: true, } end let(:external_upload_stub) { Fabricate(:image_external_upload_stub) } let(:existing_external_upload_key) { external_upload_stub.key } - before do - SiteSetting.authorized_extensions = "pdf|png" - end + before { SiteSetting.authorized_extensions = "pdf|png" } it "does not provide a content_disposition for images" do - s3_helper.expects(:copy).with(external_upload_stub.key, kind_of(String), options: upload_opts).returns(["path", "etag"]) + s3_helper + .expects(:copy) + .with(external_upload_stub.key, kind_of(String), options: upload_opts) + .returns(%w[path etag]) s3_helper.expects(:delete_object).with(external_upload_stub.key) - upload = Fabricate(:upload, extension: "png", sha1: upload_sha1, original_filename: original_filename) + upload = + Fabricate( + :upload, + extension: "png", + sha1: upload_sha1, + original_filename: original_filename, + ) store.move_existing_stored_upload( existing_external_upload_key: external_upload_stub.key, upload: upload, - content_type: "image/png" + content_type: "image/png", ) end context "when the file is a PDF" do - let(:external_upload_stub) { Fabricate(:attachment_external_upload_stub, original_filename: original_filename) } + let(:external_upload_stub) do + Fabricate(:attachment_external_upload_stub, original_filename: original_filename) + end let(:original_filename) { "small.pdf" } let(:uploaded_file) { file_from_fixtures("small.pdf", "pdf") } it "adds an attachment content-disposition with the original filename" do - disp_opts = { content_disposition: "attachment; filename=\"#{original_filename}\"; filename*=UTF-8''#{original_filename}", content_type: "application/pdf" } - s3_helper.expects(:copy).with(external_upload_stub.key, kind_of(String), options: upload_opts.merge(disp_opts)).returns(["path", "etag"]) - upload = Fabricate(:upload, extension: "png", sha1: upload_sha1, original_filename: original_filename) + disp_opts = { + content_disposition: + "attachment; filename=\"#{original_filename}\"; filename*=UTF-8''#{original_filename}", + content_type: "application/pdf", + } + s3_helper + .expects(:copy) + .with(external_upload_stub.key, kind_of(String), options: upload_opts.merge(disp_opts)) + .returns(%w[path etag]) + upload = + Fabricate( + :upload, + extension: "png", + sha1: upload_sha1, + original_filename: original_filename, + ) store.move_existing_stored_upload( existing_external_upload_key: external_upload_stub.key, upload: upload, - content_type: "application/pdf" + content_type: "application/pdf", ) end end end end - describe 'copying files in S3' do - describe '#copy_file' do + describe "copying files in S3" do + describe "#copy_file" do it "copies the from in S3 with the right paths" do upload.update!( - url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/original/1X/#{upload.sha1}.png" + url: + "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/original/1X/#{upload.sha1}.png", ) source = "#{upload_path}/#{Discourse.store.get_path_for_upload(upload)}" - destination = source.sub('.png', '.jpg') + destination = source.sub(".png", ".jpg") bucket = prepare_fake_s3(source, upload) expect(bucket.find_object(source)).to be_present @@ -214,7 +265,7 @@ RSpec.describe FileStore::S3Store do end end - describe 'removal from s3' do + describe "removal from s3" do describe "#remove_upload" do it "removes the file from s3 with the right paths" do upload_key = Discourse.store.get_path_for_upload(upload) @@ -233,16 +284,17 @@ RSpec.describe FileStore::S3Store do end describe "when s3_upload_bucket includes folders path" do - before do - SiteSetting.s3_upload_bucket = "s3-upload-bucket/discourse-uploads" - end + before { SiteSetting.s3_upload_bucket = "s3-upload-bucket/discourse-uploads" } it "removes the file from s3 with the right paths" do upload_key = "discourse-uploads/#{Discourse.store.get_path_for_upload(upload)}" - tombstone_key = "discourse-uploads/tombstone/#{Discourse.store.get_path_for_upload(upload)}" + tombstone_key = + "discourse-uploads/tombstone/#{Discourse.store.get_path_for_upload(upload)}" bucket = prepare_fake_s3(upload_key, upload) - upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{upload_key}") + upload.update!( + url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{upload_key}", + ) expect(bucket.find_object(upload_key)).to be_present expect(bucket.find_object(tombstone_key)).to be_nil @@ -256,13 +308,15 @@ RSpec.describe FileStore::S3Store do end describe "#remove_optimized_image" do - let(:optimized_key) { FileStore::BaseStore.new.get_path_for_optimized_image(optimized_image) } + let(:optimized_key) { FileStore::BaseStore.new.get_path_for_optimized_image(optimized_image) } let(:tombstone_key) { "tombstone/#{optimized_key}" } let(:upload) { optimized_image.upload } let(:upload_key) { Discourse.store.get_path_for_upload(upload) } before do - optimized_image.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{optimized_key}") + optimized_image.update!( + url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{optimized_key}", + ) upload.update!(url: "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/#{upload_key}") end @@ -282,12 +336,10 @@ RSpec.describe FileStore::S3Store do end describe "when s3_upload_bucket includes folders path" do - before do - SiteSetting.s3_upload_bucket = "s3-upload-bucket/discourse-uploads" - end + before { SiteSetting.s3_upload_bucket = "s3-upload-bucket/discourse-uploads" } let(:image_path) { FileStore::BaseStore.new.get_path_for_optimized_image(optimized_image) } - let(:optimized_key) { "discourse-uploads/#{image_path}" } + let(:optimized_key) { "discourse-uploads/#{image_path}" } let(:tombstone_key) { "discourse-uploads/tombstone/#{image_path}" } let(:upload_key) { "discourse-uploads/#{Discourse.store.get_path_for_upload(upload)}" } @@ -315,42 +367,60 @@ RSpec.describe FileStore::S3Store do end it "doesn't crash if URL contains non-ascii characters" do - expect(store.has_been_uploaded?("//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/漢1337.png")).to eq(true) + expect( + store.has_been_uploaded?( + "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/漢1337.png", + ), + ).to eq(true) expect(store.has_been_uploaded?("//s3-upload-bucket.s3.amazonaws.com/漢1337.png")).to eq(false) end it "identifies S3 uploads" do - expect(store.has_been_uploaded?("//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/1337.png")).to eq(true) + expect( + store.has_been_uploaded?( + "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com/1337.png", + ), + ).to eq(true) end it "does not match other s3 urls" do expect(store.has_been_uploaded?("//s3-upload-bucket.s3.amazonaws.com/1337.png")).to eq(false) - expect(store.has_been_uploaded?("//s3-upload-bucket.s3-us-west-1.amazonaws.com/1337.png")).to eq(false) + expect( + store.has_been_uploaded?("//s3-upload-bucket.s3-us-west-1.amazonaws.com/1337.png"), + ).to eq(false) expect(store.has_been_uploaded?("//s3.amazonaws.com/s3-upload-bucket/1337.png")).to eq(false) expect(store.has_been_uploaded?("//s4_upload_bucket.s3.amazonaws.com/1337.png")).to eq(false) end - end describe ".absolute_base_url" do it "returns a lowercase schemaless absolute url" do - expect(store.absolute_base_url).to eq("//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com") + expect(store.absolute_base_url).to eq( + "//s3-upload-bucket.s3.dualstack.us-west-1.amazonaws.com", + ) end it "uses the proper endpoint" do SiteSetting.s3_region = "us-east-1" - expect(FileStore::S3Store.new(s3_helper).absolute_base_url).to eq("//s3-upload-bucket.s3.dualstack.us-east-1.amazonaws.com") + expect(FileStore::S3Store.new(s3_helper).absolute_base_url).to eq( + "//s3-upload-bucket.s3.dualstack.us-east-1.amazonaws.com", + ) SiteSetting.s3_region = "us-west-2" - expect(FileStore::S3Store.new(s3_helper).absolute_base_url).to eq("//s3-upload-bucket.s3.dualstack.us-west-2.amazonaws.com") + expect(FileStore::S3Store.new(s3_helper).absolute_base_url).to eq( + "//s3-upload-bucket.s3.dualstack.us-west-2.amazonaws.com", + ) SiteSetting.s3_region = "cn-north-1" - expect(FileStore::S3Store.new(s3_helper).absolute_base_url).to eq("//s3-upload-bucket.s3.cn-north-1.amazonaws.com.cn") + expect(FileStore::S3Store.new(s3_helper).absolute_base_url).to eq( + "//s3-upload-bucket.s3.cn-north-1.amazonaws.com.cn", + ) SiteSetting.s3_region = "cn-northwest-1" - expect(FileStore::S3Store.new(s3_helper).absolute_base_url).to eq("//s3-upload-bucket.s3.cn-northwest-1.amazonaws.com.cn") + expect(FileStore::S3Store.new(s3_helper).absolute_base_url).to eq( + "//s3-upload-bucket.s3.cn-northwest-1.amazonaws.com.cn", + ) end - end it "is external" do @@ -383,17 +453,18 @@ RSpec.describe FileStore::S3Store do end end - describe 'update ACL' do - before do - SiteSetting.authorized_extensions = "pdf|png" - end + describe "update ACL" do + before { SiteSetting.authorized_extensions = "pdf|png" } describe ".update_upload_ACL" do let(:upload) { Fabricate(:upload, original_filename: "small.pdf", extension: "pdf") } it "sets acl to public by default" do s3_helper.expects(:s3_bucket).returns(s3_bucket) - s3_bucket.expects(:object).with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.pdf})).returns(s3_object) + s3_bucket + .expects(:object) + .with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.pdf})) + .returns(s3_object) s3_object.expects(:acl).returns(s3_object) s3_object.expects(:put).with(acl: "public-read").returns(s3_object) @@ -403,7 +474,10 @@ RSpec.describe FileStore::S3Store do it "sets acl to private when upload is marked secure" do upload.update!(secure: true) s3_helper.expects(:s3_bucket).returns(s3_bucket) - s3_bucket.expects(:object).with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.pdf})).returns(s3_object) + s3_bucket + .expects(:object) + .with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.pdf})) + .returns(s3_object) s3_object.expects(:acl).returns(s3_object) s3_object.expects(:put).with(acl: "private").returns(s3_object) @@ -412,10 +486,10 @@ RSpec.describe FileStore::S3Store do end end - describe '.cdn_url' do - it 'supports subfolder' do - SiteSetting.s3_upload_bucket = 's3-upload-bucket/livechat' - SiteSetting.s3_cdn_url = 'https://rainbow.com' + describe ".cdn_url" do + it "supports subfolder" do + SiteSetting.s3_upload_bucket = "s3-upload-bucket/livechat" + SiteSetting.s3_cdn_url = "https://rainbow.com" # none of this should matter at all # subfolder should not leak into uploads @@ -436,10 +510,14 @@ RSpec.describe FileStore::S3Store do describe ".url_for" do it "returns signed URL with content disposition when requesting to download image" do s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once - s3_bucket.expects(:object).with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.png})).returns(s3_object) + s3_bucket + .expects(:object) + .with(regexp_matches(%r{original/\d+X.*/#{upload.sha1}\.png})) + .returns(s3_object) opts = { expires_in: SiteSetting.s3_presigned_get_url_expires_after_seconds, - response_content_disposition: %Q|attachment; filename="#{upload.original_filename}"; filename*=UTF-8''#{upload.original_filename}| + response_content_disposition: + %Q|attachment; filename="#{upload.original_filename}"; filename*=UTF-8''#{upload.original_filename}|, } s3_object.expects(:presigned_url).with(:get, opts) @@ -452,9 +530,7 @@ RSpec.describe FileStore::S3Store do it "returns signed URL for a given path" do s3_helper.expects(:s3_bucket).returns(s3_bucket).at_least_once s3_bucket.expects(:object).with("special/optimized/file.png").returns(s3_object) - opts = { - expires_in: SiteSetting.s3_presigned_get_url_expires_after_seconds - } + opts = { expires_in: SiteSetting.s3_presigned_get_url_expires_after_seconds } s3_object.expects(:presigned_url).with(:get, opts) @@ -463,14 +539,17 @@ RSpec.describe FileStore::S3Store do it "does not prefix the s3_bucket_folder_path onto temporary upload prefixed keys" do SiteSetting.s3_upload_bucket = "s3-upload-bucket/folder_path" - uri = URI.parse(store.signed_url_for_path("#{FileStore::BaseStore::TEMPORARY_UPLOAD_PREFIX}folder_path/uploads/default/blah/def.xyz")) + uri = + URI.parse( + store.signed_url_for_path( + "#{FileStore::BaseStore::TEMPORARY_UPLOAD_PREFIX}folder_path/uploads/default/blah/def.xyz", + ), + ) expect(uri.path).to eq( - "/#{FileStore::BaseStore::TEMPORARY_UPLOAD_PREFIX}folder_path/uploads/default/blah/def.xyz" + "/#{FileStore::BaseStore::TEMPORARY_UPLOAD_PREFIX}folder_path/uploads/default/blah/def.xyz", ) uri = URI.parse(store.signed_url_for_path("uploads/default/blah/def.xyz")) - expect(uri.path).to eq( - "/folder_path/uploads/default/blah/def.xyz" - ) + expect(uri.path).to eq("/folder_path/uploads/default/blah/def.xyz") end end @@ -485,7 +564,7 @@ RSpec.describe FileStore::S3Store do @fake_s3_bucket.put_object( key: upload_key, size: upload.filesize, - last_modified: upload.created_at + last_modified: upload.created_at, ) end end diff --git a/spec/lib/filter_best_posts_spec.rb b/spec/lib/filter_best_posts_spec.rb index 91aeddd55a..81133b8da6 100644 --- a/spec/lib/filter_best_posts_spec.rb +++ b/spec/lib/filter_best_posts_spec.rb @@ -1,10 +1,9 @@ # frozen_string_literal: true -require 'filter_best_posts' -require 'topic_view' +require "filter_best_posts" +require "topic_view" RSpec.describe FilterBestPosts do - fab!(:topic) { Fabricate(:topic) } fab!(:coding_horror) { Fabricate(:coding_horror) } fab!(:first_poster) { topic.user } @@ -17,14 +16,13 @@ RSpec.describe FilterBestPosts do fab!(:admin) { Fabricate(:admin) } it "can find the best responses" do - filtered_posts = TopicView.new(topic.id, coding_horror, best: 2).filtered_posts best2 = FilterBestPosts.new(topic, filtered_posts, 2) expect(best2.posts.count).to eq(2) expect(best2.posts[0].id).to eq(p2.id) expect(best2.posts[1].id).to eq(p3.id) - topic.update_status('closed', true, Fabricate(:admin)) + topic.update_status("closed", true, Fabricate(:admin)) expect(topic.posts.count).to eq(4) end @@ -32,21 +30,23 @@ RSpec.describe FilterBestPosts do before { @filtered_posts = TopicView.new(topic.id, nil, best: 99).filtered_posts } it "should not get the status post" do - best = FilterBestPosts.new(topic, @filtered_posts, 99) expect(best.filtered_posts.size).to eq(3) expect(best.posts.map(&:id)).to match_array([p2.id, p3.id]) - end it "should get no results for trust level too low" do - - best = FilterBestPosts.new(topic, @filtered_posts, 99, min_trust_level: coding_horror.trust_level + 1) + best = + FilterBestPosts.new( + topic, + @filtered_posts, + 99, + min_trust_level: coding_horror.trust_level + 1, + ) expect(best.posts.count).to eq(0) end it "should filter out the posts with a score that is too low" do - best = FilterBestPosts.new(topic, @filtered_posts, 99, min_score: 99) expect(best.posts.count).to eq(0) end @@ -59,12 +59,26 @@ RSpec.describe FilterBestPosts do it "should punch through posts if the score is high enough" do p2.update_column(:score, 100) - best = FilterBestPosts.new(topic, @filtered_posts, 99, bypass_trust_level_score: 100, min_trust_level: coding_horror.trust_level + 1) + best = + FilterBestPosts.new( + topic, + @filtered_posts, + 99, + bypass_trust_level_score: 100, + min_trust_level: coding_horror.trust_level + 1, + ) expect(best.posts.count).to eq(1) end it "should bypass trust level score" do - best = FilterBestPosts.new(topic, @filtered_posts, 99, bypass_trust_level_score: 0, min_trust_level: coding_horror.trust_level + 1) + best = + FilterBestPosts.new( + topic, + @filtered_posts, + 99, + bypass_trust_level_score: 0, + min_trust_level: coding_horror.trust_level + 1, + ) expect(best.posts.count).to eq(0) end @@ -84,6 +98,5 @@ RSpec.describe FilterBestPosts do best = FilterBestPosts.new(topic, @filtered_posts, 99, only_moderator_liked: true) expect(best.posts.count).to eq(1) end - end end diff --git a/spec/lib/final_destination/http_spec.rb b/spec/lib/final_destination/http_spec.rb index fa9a7706e6..3388a2c835 100644 --- a/spec/lib/final_destination/http_spec.rb +++ b/spec/lib/final_destination/http_spec.rb @@ -9,9 +9,7 @@ describe FinalDestination::HTTP do Addrinfo.stubs(:getaddrinfo).never end - after do - WebMock.enable! - end + after { WebMock.enable! } def expect_tcp_and_abort(stub_addr, &blk) success = Class.new(StandardError) @@ -102,7 +100,10 @@ describe FinalDestination::HTTP do stub_ip_lookup("example.com", %w[1.1.1.1 2.2.2.2 3.3.3.3 4.4.4.4]) TCPSocket.stubs(:open).with { |addr| addr == "1.1.1.1" }.raises(Errno::ECONNREFUSED) TCPSocket.stubs(:open).with { |addr| addr == "2.2.2.2" }.raises(Errno::ECONNREFUSED) - TCPSocket.stubs(:open).with { |*args, **kwargs| kwargs[:open_timeout] == 0 }.raises(Errno::ETIMEDOUT) + TCPSocket + .stubs(:open) + .with { |*args, **kwargs| kwargs[:open_timeout] == 0 } + .raises(Errno::ETIMEDOUT) FinalDestination::HTTP.any_instance.stubs(:current_time).returns(0, 1, 5) expect do FinalDestination::HTTP.start("example.com", 80, open_timeout: 5) {} diff --git a/spec/lib/final_destination/resolver_spec.rb b/spec/lib/final_destination/resolver_spec.rb index 20ce673cd7..cdaa3d6c0d 100644 --- a/spec/lib/final_destination/resolver_spec.rb +++ b/spec/lib/final_destination/resolver_spec.rb @@ -28,9 +28,7 @@ describe FinalDestination::Resolver do expect(alive_thread_count).to eq(start_thread_count) - expect(FinalDestination::Resolver.lookup("example.com")).to eq( - %w[1.1.1.1 2.2.2.2], - ) + expect(FinalDestination::Resolver.lookup("example.com")).to eq(%w[1.1.1.1 2.2.2.2]) # Thread available for reuse after successful lookup expect(alive_thread_count).to eq(start_thread_count + 1) diff --git a/spec/lib/final_destination_spec.rb b/spec/lib/final_destination_spec.rb index a81be718ce..37ab6fef07 100644 --- a/spec/lib/final_destination_spec.rb +++ b/spec/lib/final_destination_spec.rb @@ -1,38 +1,21 @@ # frozen_string_literal: true -require 'final_destination' +require "final_destination" RSpec.describe FinalDestination do let(:opts) do { - ignore_redirects: ['https://ignore-me.com'], - - force_get_hosts: ['https://force.get.com', 'https://*.ihaveawildcard.com/'], - - preserve_fragment_url_hosts: ['https://eviltrout.com'], + ignore_redirects: ["https://ignore-me.com"], + force_get_hosts: %w[https://force.get.com https://*.ihaveawildcard.com/], + preserve_fragment_url_hosts: ["https://eviltrout.com"], } end - let(:doc_response) do - { - status: 200, - headers: { "Content-Type" => "text/html" } - } - end + let(:doc_response) { { status: 200, headers: { "Content-Type" => "text/html" } } } - let(:image_response) do - { - status: 200, - headers: { "Content-Type" => "image/jpeg" } - } - end + let(:image_response) { { status: 200, headers: { "Content-Type" => "image/jpeg" } } } - let(:body_response) do - { - status: 200, - body: "test" - } - end + let(:body_response) { { status: 200, body: "test" } } def fd_stub_request(method, url) uri = URI.parse(url) @@ -43,10 +26,11 @@ RSpec.describe FinalDestination do # In Excon we pass the IP in the URL, so we need to stub # that version as well uri.hostname = "HOSTNAME_PLACEHOLDER" - matcher = Regexp.escape(uri.to_s).sub( - "HOSTNAME_PLACEHOLDER", - "(#{Regexp.escape(host)}|#{Regexp.escape(ip)})" - ) + matcher = + Regexp.escape(uri.to_s).sub( + "HOSTNAME_PLACEHOLDER", + "(#{Regexp.escape(host)}|#{Regexp.escape(ip)})", + ) stub_request(method, /\A#{matcher}\z/).with(headers: { "Host" => host }) end @@ -54,37 +38,36 @@ RSpec.describe FinalDestination do def canonical_follow(from, dest) fd_stub_request(:get, from).to_return( status: 200, - body: "" + body: "", ) end def redirect_response(from, dest) - fd_stub_request(:head, from).to_return( - status: 302, - headers: { "Location" => dest } - ) + fd_stub_request(:head, from).to_return(status: 302, headers: { "Location" => dest }) end def fd(url) FinalDestination.new(url, opts) end - it 'correctly parses ignored hostnames' do - fd = FinalDestination.new('https://meta.discourse.org', - ignore_redirects: ['http://google.com', 'youtube.com', 'https://meta.discourse.org', '://bing.com'] - ) + it "correctly parses ignored hostnames" do + fd = + FinalDestination.new( + "https://meta.discourse.org", + ignore_redirects: %w[http://google.com youtube.com https://meta.discourse.org ://bing.com], + ) - expect(fd.ignored).to eq(['test.localhost', 'google.com', 'meta.discourse.org']) + expect(fd.ignored).to eq(%w[test.localhost google.com meta.discourse.org]) end - describe '.resolve' do + describe ".resolve" do it "has a ready status code before anything happens" do - expect(fd('https://eviltrout.com').status).to eq(:ready) + expect(fd("https://eviltrout.com").status).to eq(:ready) end it "returns nil for an invalid url" do expect(fd(nil).resolve).to be_nil - expect(fd('asdf').resolve).to be_nil + expect(fd("asdf").resolve).to be_nil end it "returns nil for unresolvable url" do @@ -100,37 +83,33 @@ RSpec.describe FinalDestination do it "returns nil when read timeouts" do Excon.expects(:public_send).raises(Excon::Errors::Timeout) - expect(fd('https://discourse.org').resolve).to eq(nil) + expect(fd("https://discourse.org").resolve).to eq(nil) end context "without redirects" do - before do - fd_stub_request(:head, "https://eviltrout.com/").to_return(doc_response) - end + before { fd_stub_request(:head, "https://eviltrout.com/").to_return(doc_response) } it "returns the final url" do - final = FinalDestination.new('https://eviltrout.com', opts) - expect(final.resolve.to_s).to eq('https://eviltrout.com') + final = FinalDestination.new("https://eviltrout.com", opts) + expect(final.resolve.to_s).to eq("https://eviltrout.com") expect(final.redirected?).to eq(false) expect(final.status).to eq(:resolved) end end it "ignores redirects" do - final = FinalDestination.new('https://ignore-me.com/some-url', opts) - expect(final.resolve.to_s).to eq('https://ignore-me.com/some-url') + final = FinalDestination.new("https://ignore-me.com/some-url", opts) + expect(final.resolve.to_s).to eq("https://ignore-me.com/some-url") expect(final.redirected?).to eq(false) expect(final.status).to eq(:resolved) end context "with underscores in URLs" do - before do - fd_stub_request(:head, 'https://some_thing.example.com').to_return(doc_response) - end + before { fd_stub_request(:head, "https://some_thing.example.com").to_return(doc_response) } it "doesn't raise errors with underscores in urls" do - final = FinalDestination.new('https://some_thing.example.com', opts) - expect(final.resolve.to_s).to eq('https://some_thing.example.com') + final = FinalDestination.new("https://some_thing.example.com", opts) + expect(final.resolve.to_s).to eq("https://some_thing.example.com") expect(final.redirected?).to eq(false) expect(final.status).to eq(:resolved) end @@ -144,8 +123,8 @@ RSpec.describe FinalDestination do end it "returns the final url" do - final = FinalDestination.new('https://eviltrout.com', opts) - expect(final.resolve.to_s).to eq('https://discourse.org') + final = FinalDestination.new("https://eviltrout.com", opts) + expect(final.resolve.to_s).to eq("https://discourse.org") expect(final.redirected?).to eq(true) expect(final.status).to eq(:resolved) end @@ -159,7 +138,7 @@ RSpec.describe FinalDestination do end it "returns the final url" do - final = FinalDestination.new('https://eviltrout.com', opts.merge(max_redirects: 1)) + final = FinalDestination.new("https://eviltrout.com", opts.merge(max_redirects: 1)) expect(final.resolve).to be_nil expect(final.redirected?).to eq(true) expect(final.status).to eq(:too_many_redirects) @@ -169,12 +148,18 @@ RSpec.describe FinalDestination do context "with a redirect to an internal IP" do before do redirect_response("https://eviltrout.com", "https://private-host.com") - FinalDestination::SSRFDetector.stubs(:lookup_and_filter_ips).with("eviltrout.com").returns(["1.2.3.4"]) - FinalDestination::SSRFDetector.stubs(:lookup_and_filter_ips).with("private-host.com").raises(FinalDestination::SSRFDetector::DisallowedIpError) + FinalDestination::SSRFDetector + .stubs(:lookup_and_filter_ips) + .with("eviltrout.com") + .returns(["1.2.3.4"]) + FinalDestination::SSRFDetector + .stubs(:lookup_and_filter_ips) + .with("private-host.com") + .raises(FinalDestination::SSRFDetector::DisallowedIpError) end it "returns the final url" do - final = FinalDestination.new('https://eviltrout.com', opts) + final = FinalDestination.new("https://eviltrout.com", opts) expect(final.resolve).to be_nil expect(final.redirected?).to eq(true) expect(final.status).to eq(:invalid_address) @@ -182,45 +167,56 @@ RSpec.describe FinalDestination do end context "with a redirect to login path" do - before do - redirect_response("https://eviltrout.com/t/xyz/1", "https://eviltrout.com/login") - end + before { redirect_response("https://eviltrout.com/t/xyz/1", "https://eviltrout.com/login") } it "does not follow redirect" do - final = FinalDestination.new('https://eviltrout.com/t/xyz/1', opts) - expect(final.resolve.to_s).to eq('https://eviltrout.com/t/xyz/1') + final = FinalDestination.new("https://eviltrout.com/t/xyz/1", opts) + expect(final.resolve.to_s).to eq("https://eviltrout.com/t/xyz/1") expect(final.redirected?).to eq(false) expect(final.status).to eq(:resolved) end end - it 'raises error when response is too big' do + it "raises error when response is too big" do stub_const(described_class, "MAX_REQUEST_SIZE_BYTES", 1) do fd_stub_request(:get, "https://codinghorror.com/blog").to_return(body_response) - final = FinalDestination.new('https://codinghorror.com/blog', opts.merge(follow_canonical: true)) - expect { final.resolve }.to raise_error(Excon::Errors::ExpectationFailed, "response size too big: https://codinghorror.com/blog") + final = + FinalDestination.new("https://codinghorror.com/blog", opts.merge(follow_canonical: true)) + expect { final.resolve }.to raise_error( + Excon::Errors::ExpectationFailed, + "response size too big: https://codinghorror.com/blog", + ) end end - it 'raises error when response is too slow' do - fd_stub_request(:get, "https://codinghorror.com/blog").to_return(lambda { |request| freeze_time(11.seconds.from_now) ; body_response }) - final = FinalDestination.new('https://codinghorror.com/blog', opts.merge(follow_canonical: true)) - expect { final.resolve }.to raise_error(Excon::Errors::ExpectationFailed, "connect timeout reached: https://codinghorror.com/blog") + it "raises error when response is too slow" do + fd_stub_request(:get, "https://codinghorror.com/blog").to_return( + lambda do |request| + freeze_time(11.seconds.from_now) + body_response + end, + ) + final = + FinalDestination.new("https://codinghorror.com/blog", opts.merge(follow_canonical: true)) + expect { final.resolve }.to raise_error( + Excon::Errors::ExpectationFailed, + "connect timeout reached: https://codinghorror.com/blog", + ) end - context 'when following canonical links' do - it 'resolves the canonical link as the final destination' do + context "when following canonical links" do + it "resolves the canonical link as the final destination" do canonical_follow("https://eviltrout.com", "https://codinghorror.com/blog") fd_stub_request(:head, "https://codinghorror.com/blog").to_return(doc_response) - final = FinalDestination.new('https://eviltrout.com', opts.merge(follow_canonical: true)) + final = FinalDestination.new("https://eviltrout.com", opts.merge(follow_canonical: true)) expect(final.resolve.to_s).to eq("https://codinghorror.com/blog") expect(final.redirected?).to eq(false) expect(final.status).to eq(:resolved) end - it 'resolves the canonical link when the URL is relative' do + it "resolves the canonical link when the URL is relative" do host = "https://codinghorror.com" canonical_follow("#{host}/blog", "/blog/canonical") @@ -233,7 +229,7 @@ RSpec.describe FinalDestination do expect(final.status).to eq(:resolved) end - it 'resolves the canonical link when the URL is relative and does not start with the / symbol' do + it "resolves the canonical link when the URL is relative and does not start with the / symbol" do host = "https://codinghorror.com" canonical_follow("#{host}/blog", "blog/canonical") fd_stub_request(:head, "#{host}/blog/canonical").to_return(doc_response) @@ -248,7 +244,7 @@ RSpec.describe FinalDestination do it "does not follow the canonical link if it's the same as the current URL" do canonical_follow("https://eviltrout.com", "https://eviltrout.com") - final = FinalDestination.new('https://eviltrout.com', opts.merge(follow_canonical: true)) + final = FinalDestination.new("https://eviltrout.com", opts.merge(follow_canonical: true)) expect(final.resolve.to_s).to eq("https://eviltrout.com") expect(final.redirected?).to eq(false) @@ -258,7 +254,7 @@ RSpec.describe FinalDestination do it "does not follow the canonical link if it's invalid" do canonical_follow("https://eviltrout.com", "") - final = FinalDestination.new('https://eviltrout.com', opts.merge(follow_canonical: true)) + final = FinalDestination.new("https://eviltrout.com", opts.merge(follow_canonical: true)) expect(final.resolve.to_s).to eq("https://eviltrout.com") expect(final.redirected?).to eq(false) @@ -268,7 +264,7 @@ RSpec.describe FinalDestination do context "when forcing GET" do it "will do a GET when forced" do - url = 'https://force.get.com/posts?page=4' + url = "https://force.get.com/posts?page=4" get_stub = fd_stub_request(:get, url) head_stub = fd_stub_request(:head, url) @@ -280,7 +276,7 @@ RSpec.describe FinalDestination do end it "will do a HEAD if not forced" do - url = 'https://eviltrout.com/posts?page=2' + url = "https://eviltrout.com/posts?page=2" get_stub = fd_stub_request(:get, url) head_stub = fd_stub_request(:head, url) @@ -292,7 +288,7 @@ RSpec.describe FinalDestination do end it "will do a GET when forced on a wildcard subdomain" do - url = 'https://any-subdomain.ihaveawildcard.com/some/other/content' + url = "https://any-subdomain.ihaveawildcard.com/some/other/content" get_stub = fd_stub_request(:get, url) head_stub = fd_stub_request(:head, url) @@ -304,7 +300,7 @@ RSpec.describe FinalDestination do end it "will do a HEAD if on a subdomain of a forced get domain without a wildcard" do - url = 'https://particularly.eviltrout.com/has/a/secret/plan' + url = "https://particularly.eviltrout.com/has/a/secret/plan" get_stub = fd_stub_request(:get, url) head_stub = fd_stub_request(:head, url) @@ -314,60 +310,63 @@ RSpec.describe FinalDestination do expect(get_stub).to_not have_been_requested expect(head_stub).to have_been_requested end - end context "when HEAD not supported" do before do - fd_stub_request(:get, 'https://eviltrout.com').to_return( + fd_stub_request(:get, "https://eviltrout.com").to_return( status: 301, headers: { - "Location" => 'https://discourse.org', - 'Set-Cookie' => 'evil=trout' - } + "Location" => "https://discourse.org", + "Set-Cookie" => "evil=trout", + }, ) - fd_stub_request(:head, 'https://discourse.org') + fd_stub_request(:head, "https://discourse.org") end context "when the status code is 405" do - before do - fd_stub_request(:head, 'https://eviltrout.com').to_return(status: 405) - end + before { fd_stub_request(:head, "https://eviltrout.com").to_return(status: 405) } it "will try a GET" do - final = FinalDestination.new('https://eviltrout.com', opts) - expect(final.resolve.to_s).to eq('https://discourse.org') + final = FinalDestination.new("https://eviltrout.com", opts) + expect(final.resolve.to_s).to eq("https://discourse.org") expect(final.status).to eq(:resolved) - expect(final.cookie).to eq('evil=trout') + expect(final.cookie).to eq("evil=trout") end end context "when the status code is 501" do - before do - fd_stub_request(:head, 'https://eviltrout.com').to_return(status: 501) - end + before { fd_stub_request(:head, "https://eviltrout.com").to_return(status: 501) } it "will try a GET" do - final = FinalDestination.new('https://eviltrout.com', opts) - expect(final.resolve.to_s).to eq('https://discourse.org') + final = FinalDestination.new("https://eviltrout.com", opts) + expect(final.resolve.to_s).to eq("https://discourse.org") expect(final.status).to eq(:resolved) - expect(final.cookie).to eq('evil=trout') + expect(final.cookie).to eq("evil=trout") end end it "correctly extracts cookies during GET" do fd_stub_request(:head, "https://eviltrout.com").to_return(status: 405) - fd_stub_request(:get, "https://eviltrout.com") - .to_return(status: 302, body: "" , headers: { + fd_stub_request(:get, "https://eviltrout.com").to_return( + status: 302, + body: "", + headers: { "Location" => "https://eviltrout.com", - "Set-Cookie" => ["foo=219ffwef9w0f; expires=Mon, 19-Feb-2018 10:44:24 GMT; path=/; domain=eviltrout.com", - "bar=1", - "baz=2; expires=Tue, 19-Feb-2019 10:14:24 GMT; path=/; domain=eviltrout.com"] - }) + "Set-Cookie" => [ + "foo=219ffwef9w0f; expires=Mon, 19-Feb-2018 10:44:24 GMT; path=/; domain=eviltrout.com", + "bar=1", + "baz=2; expires=Tue, 19-Feb-2019 10:14:24 GMT; path=/; domain=eviltrout.com", + ], + }, + ) - fd_stub_request(:head, "https://eviltrout.com") - .with(headers: { "Cookie" => "bar=1; baz=2; foo=219ffwef9w0f" }) + fd_stub_request(:head, "https://eviltrout.com").with( + headers: { + "Cookie" => "bar=1; baz=2; foo=219ffwef9w0f", + }, + ) final = FinalDestination.new("https://eviltrout.com", opts) expect(final.resolve.to_s).to eq("https://eviltrout.com") @@ -377,14 +376,20 @@ RSpec.describe FinalDestination do end it "should use the correct format for cookies when there is only one cookie" do - fd_stub_request(:head, "https://eviltrout.com") - .to_return(status: 302, headers: { + fd_stub_request(:head, "https://eviltrout.com").to_return( + status: 302, + headers: { "Location" => "https://eviltrout.com", - "Set-Cookie" => "foo=219ffwef9w0f; expires=Mon, 19-Feb-2018 10:44:24 GMT; path=/; domain=eviltrout.com" - }) + "Set-Cookie" => + "foo=219ffwef9w0f; expires=Mon, 19-Feb-2018 10:44:24 GMT; path=/; domain=eviltrout.com", + }, + ) - fd_stub_request(:head, "https://eviltrout.com") - .with(headers: { "Cookie" => "foo=219ffwef9w0f" }) + fd_stub_request(:head, "https://eviltrout.com").with( + headers: { + "Cookie" => "foo=219ffwef9w0f", + }, + ) final = FinalDestination.new("https://eviltrout.com", opts) expect(final.resolve.to_s).to eq("https://eviltrout.com") @@ -393,16 +398,23 @@ RSpec.describe FinalDestination do end it "should use the correct format for cookies when there are multiple cookies" do - fd_stub_request(:head, "https://eviltrout.com") - .to_return(status: 302, headers: { + fd_stub_request(:head, "https://eviltrout.com").to_return( + status: 302, + headers: { "Location" => "https://eviltrout.com", - "Set-Cookie" => ["foo=219ffwef9w0f; expires=Mon, 19-Feb-2018 10:44:24 GMT; path=/; domain=eviltrout.com", - "bar=1", - "baz=2; expires=Tue, 19-Feb-2019 10:14:24 GMT; path=/; domain=eviltrout.com"] - }) + "Set-Cookie" => [ + "foo=219ffwef9w0f; expires=Mon, 19-Feb-2018 10:44:24 GMT; path=/; domain=eviltrout.com", + "bar=1", + "baz=2; expires=Tue, 19-Feb-2019 10:14:24 GMT; path=/; domain=eviltrout.com", + ], + }, + ) - fd_stub_request(:head, "https://eviltrout.com") - .with(headers: { "Cookie" => "bar=1; baz=2; foo=219ffwef9w0f" }) + fd_stub_request(:head, "https://eviltrout.com").with( + headers: { + "Cookie" => "bar=1; baz=2; foo=219ffwef9w0f", + }, + ) final = FinalDestination.new("https://eviltrout.com", opts) expect(final.resolve.to_s).to eq("https://eviltrout.com") @@ -436,32 +448,38 @@ RSpec.describe FinalDestination do end end - describe '#get' do + describe "#get" do let(:fd) { FinalDestination.new("http://wikipedia.com", opts.merge(verbose: true)) } - before do - described_class.clear_https_cache!("wikipedia.com") - end + before { described_class.clear_https_cache!("wikipedia.com") } context "when there is a redirect" do before do - stub_request(:get, "http://wikipedia.com/"). - to_return(status: 302, body: "" , headers: { "location" => "https://wikipedia.com/" }) + stub_request(:get, "http://wikipedia.com/").to_return( + status: 302, + body: "", + headers: { + "location" => "https://wikipedia.com/", + }, + ) # webmock does not do chunks - stub_request(:get, "https://wikipedia.com/"). - to_return(status: 200, body: "" , headers: {}) + stub_request(:get, "https://wikipedia.com/").to_return( + status: 200, + body: "", + headers: { + }, + ) end - after do - WebMock.reset! - end + after { WebMock.reset! } it "correctly streams" do chunk = nil - result = fd.get do |resp, c| - chunk = c - throw :done - end + result = + fd.get do |resp, c| + chunk = c + throw :done + end expect(result).to eq("https://wikipedia.com/") expect(chunk).to eq("") @@ -471,12 +489,13 @@ RSpec.describe FinalDestination do context "when there is a timeout" do subject(:get) { fd.get {} } - before do - fd.stubs(:safe_session).raises(Timeout::Error) - end + before { fd.stubs(:safe_session).raises(Timeout::Error) } it "logs the exception" do - Rails.logger.expects(:warn).with(regexp_matches(/FinalDestination could not resolve URL \(timeout\)/)) + Rails + .logger + .expects(:warn) + .with(regexp_matches(/FinalDestination could not resolve URL \(timeout\)/)) get end @@ -488,9 +507,7 @@ RSpec.describe FinalDestination do context "when there is an SSL error" do subject(:get) { fd.get {} } - before do - fd.stubs(:safe_session).raises(OpenSSL::SSL::SSLError) - end + before { fd.stubs(:safe_session).raises(OpenSSL::SSL::SSLError) } it "logs the exception" do Rails.logger.expects(:warn).with(regexp_matches(/an error with ssl occurred/i)) @@ -505,24 +522,24 @@ RSpec.describe FinalDestination do describe ".validate_url_format" do it "supports http urls" do - expect(fd('http://eviltrout.com').validate_uri_format).to eq(true) + expect(fd("http://eviltrout.com").validate_uri_format).to eq(true) end it "supports https urls" do - expect(fd('https://eviltrout.com').validate_uri_format).to eq(true) + expect(fd("https://eviltrout.com").validate_uri_format).to eq(true) end it "doesn't support ftp urls" do - expect(fd('ftp://eviltrout.com').validate_uri_format).to eq(false) + expect(fd("ftp://eviltrout.com").validate_uri_format).to eq(false) end it "doesn't support IP urls" do - expect(fd('http://104.25.152.10').validate_uri_format).to eq(false) - expect(fd('https://[2001:abc:de:01:0:3f0:6a65:c2bf]').validate_uri_format).to eq(false) + expect(fd("http://104.25.152.10").validate_uri_format).to eq(false) + expect(fd("https://[2001:abc:de:01:0:3f0:6a65:c2bf]").validate_uri_format).to eq(false) end it "returns false for schemeless URL" do - expect(fd('eviltrout.com').validate_uri_format).to eq(false) + expect(fd("eviltrout.com").validate_uri_format).to eq(false) end it "returns false for nil URL" do @@ -530,50 +547,60 @@ RSpec.describe FinalDestination do end it "returns false for invalid ports" do - expect(fd('http://eviltrout.com:21').validate_uri_format).to eq(false) - expect(fd('https://eviltrout.com:8000').validate_uri_format).to eq(false) + expect(fd("http://eviltrout.com:21").validate_uri_format).to eq(false) + expect(fd("https://eviltrout.com:8000").validate_uri_format).to eq(false) end it "returns true for valid ports" do - expect(fd('http://eviltrout.com:80').validate_uri_format).to eq(true) - expect(fd('https://eviltrout.com:443').validate_uri_format).to eq(true) + expect(fd("http://eviltrout.com:80").validate_uri_format).to eq(true) + expect(fd("https://eviltrout.com:443").validate_uri_format).to eq(true) end end describe "https cache" do - it 'will cache https lookups' do - + it "will cache https lookups" do FinalDestination.clear_https_cache!("wikipedia.com") - fd_stub_request(:head, "http://wikipedia.com/image.png") - .to_return(status: 302, body: "", headers: { location: 'https://wikipedia.com/image.png' }) + fd_stub_request(:head, "http://wikipedia.com/image.png").to_return( + status: 302, + body: "", + headers: { + location: "https://wikipedia.com/image.png", + }, + ) fd_stub_request(:head, "https://wikipedia.com/image.png") - fd('http://wikipedia.com/image.png').resolve + fd("http://wikipedia.com/image.png").resolve fd_stub_request(:head, "https://wikipedia.com/image2.png") - fd('http://wikipedia.com/image2.png').resolve + fd("http://wikipedia.com/image2.png").resolve end end describe "#normalized_url" do it "correctly normalizes url" do - fragment_url = "https://eviltrout.com/2016/02/25/fixing-android-performance.html#discourse-comments" + fragment_url = + "https://eviltrout.com/2016/02/25/fixing-android-performance.html#discourse-comments" expect(fd(fragment_url).normalized_url.to_s).to eq(fragment_url) - expect(fd("https://eviltrout.com?s=180&d=mm&r=g").normalized_url.to_s) - .to eq("https://eviltrout.com?s=180&d=mm&%23038;r=g") + expect(fd("https://eviltrout.com?s=180&d=mm&r=g").normalized_url.to_s).to eq( + "https://eviltrout.com?s=180&d=mm&%23038;r=g", + ) - expect(fd("http://example.com/?a=\11\15").normalized_url.to_s).to eq("http://example.com/?a=%09%0D") + expect(fd("http://example.com/?a=\11\15").normalized_url.to_s).to eq( + "http://example.com/?a=%09%0D", + ) - expect(fd("https://ru.wikipedia.org/wiki/%D0%A1%D0%B2%D0%BE%D0%B1%D0%BE").normalized_url.to_s) - .to eq('https://ru.wikipedia.org/wiki/%D0%A1%D0%B2%D0%BE%D0%B1%D0%BE') + expect( + fd("https://ru.wikipedia.org/wiki/%D0%A1%D0%B2%D0%BE%D0%B1%D0%BE").normalized_url.to_s, + ).to eq("https://ru.wikipedia.org/wiki/%D0%A1%D0%B2%D0%BE%D0%B1%D0%BE") - expect(fd('https://ru.wikipedia.org/wiki/Свобо').normalized_url.to_s) - .to eq('https://ru.wikipedia.org/wiki/%D0%A1%D0%B2%D0%BE%D0%B1%D0%BE') + expect(fd("https://ru.wikipedia.org/wiki/Свобо").normalized_url.to_s).to eq( + "https://ru.wikipedia.org/wiki/%D0%A1%D0%B2%D0%BE%D0%B1%D0%BE", + ) end end end diff --git a/spec/lib/flag_settings_spec.rb b/spec/lib/flag_settings_spec.rb index 6dd895ede8..2d34e194d6 100644 --- a/spec/lib/flag_settings_spec.rb +++ b/spec/lib/flag_settings_spec.rb @@ -1,13 +1,12 @@ # frozen_string_literal: true -require 'flag_settings' +require "flag_settings" RSpec.describe FlagSettings do - let(:settings) { FlagSettings.new } - describe 'add' do - it 'will add a type' do + describe "add" do + it "will add a type" do settings.add(3, :off_topic) expect(settings.flag_types).to include(:off_topic) expect(settings.is_flag?(:off_topic)).to eq(true) @@ -18,26 +17,26 @@ RSpec.describe FlagSettings do expect(settings.auto_action_types).to be_empty end - it 'will add a topic type' do + it "will add a topic type" do settings.add(4, :inappropriate, topic_type: true) expect(settings.flag_types).to include(:inappropriate) expect(settings.topic_flag_types).to include(:inappropriate) expect(settings.without_custom_types).to include(:inappropriate) end - it 'will add a notify type' do + it "will add a notify type" do settings.add(3, :off_topic, notify_type: true) expect(settings.flag_types).to include(:off_topic) expect(settings.notify_types).to include(:off_topic) end - it 'will add an auto action type' do + it "will add an auto action type" do settings.add(7, :notify_moderators, auto_action_type: true) expect(settings.flag_types).to include(:notify_moderators) expect(settings.auto_action_types).to include(:notify_moderators) end - it 'will add a custom type' do + it "will add a custom type" do settings.add(7, :notify_user, custom_type: true) expect(settings.flag_types).to include(:notify_user) expect(settings.custom_types).to include(:notify_user) diff --git a/spec/lib/freedom_patches/schema_migration_details_spec.rb b/spec/lib/freedom_patches/schema_migration_details_spec.rb index 8a8317d703..7c8aba43d9 100644 --- a/spec/lib/freedom_patches/schema_migration_details_spec.rb +++ b/spec/lib/freedom_patches/schema_migration_details_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true RSpec.describe FreedomPatches::SchemaMigrationDetails do - # we usually don't really need this model so lets not clutter up with it class SchemaMigrationDetail < ActiveRecord::Base end diff --git a/spec/lib/freedom_patches/translate_accelerator_spec.rb b/spec/lib/freedom_patches/translate_accelerator_spec.rb index c3a6592577..16c0c9e1ac 100644 --- a/spec/lib/freedom_patches/translate_accelerator_spec.rb +++ b/spec/lib/freedom_patches/translate_accelerator_spec.rb @@ -19,41 +19,39 @@ RSpec.describe "translate accelerator" do end it "supports raising if requested, and cache bypasses" do - expect { I18n.t('i_am_an_unknown_key99', raise: true) }.to raise_error(I18n::MissingTranslationData) + expect { I18n.t("i_am_an_unknown_key99", raise: true) }.to raise_error( + I18n::MissingTranslationData, + ) - orig = I18n.t('i_am_an_unknown_key99') + orig = I18n.t("i_am_an_unknown_key99") - expect(I18n.t('i_am_an_unknown_key99').object_id).to eq(orig.object_id) - expect(I18n.t('i_am_an_unknown_key99')).to eq("translation missing: en.i_am_an_unknown_key99") + expect(I18n.t("i_am_an_unknown_key99").object_id).to eq(orig.object_id) + expect(I18n.t("i_am_an_unknown_key99")).to eq("translation missing: en.i_am_an_unknown_key99") end it "returns the correct language" do - expect(I18n.t('foo', locale: :en)).to eq('Foo in :en') - expect(I18n.t('foo', locale: :de)).to eq('Foo in :de') + expect(I18n.t("foo", locale: :en)).to eq("Foo in :en") + expect(I18n.t("foo", locale: :de)).to eq("Foo in :de") - I18n.with_locale(:en) do - expect(I18n.t('foo')).to eq('Foo in :en') - end + I18n.with_locale(:en) { expect(I18n.t("foo")).to eq("Foo in :en") } - I18n.with_locale(:de) do - expect(I18n.t('foo')).to eq('Foo in :de') - end + I18n.with_locale(:de) { expect(I18n.t("foo")).to eq("Foo in :de") } end it "converts language keys to symbols" do - expect(I18n.t('foo', locale: :en)).to eq('Foo in :en') - expect(I18n.t('foo', locale: "en")).to eq('Foo in :en') + expect(I18n.t("foo", locale: :en)).to eq("Foo in :en") + expect(I18n.t("foo", locale: "en")).to eq("Foo in :en") expect(I18n.instance_variable_get(:@loaded_locales)).to contain_exactly(:en) end it "overrides for both string and symbol keys" do - key = 'user.email.not_allowed' - text_overridden = 'foobar' + key = "user.email.not_allowed" + text_overridden = "foobar" expect(I18n.t(key)).to be_present - override_translation('en', key, text_overridden) + override_translation("en", key, text_overridden) expect(I18n.t(key)).to eq(text_overridden) expect(I18n.t(key.to_sym)).to eq(text_overridden) @@ -61,17 +59,21 @@ RSpec.describe "translate accelerator" do describe ".overrides_by_locale" do it "should cache overrides for each locale" do - override_translation('en', 'got', 'summer') - override_translation('zh_TW', 'got', '冬季') + override_translation("en", "got", "summer") + override_translation("zh_TW", "got", "冬季") - I18n.overrides_by_locale('en') - I18n.overrides_by_locale('zh_TW') + I18n.overrides_by_locale("en") + I18n.overrides_by_locale("zh_TW") expect(I18n.instance_variable_get(:@overrides_by_site)).to eq( - 'default' => { - en: { 'got' => 'summer' }, - zh_TW: { 'got' => '冬季' } - } + "default" => { + en: { + "got" => "summer", + }, + zh_TW: { + "got" => "冬季", + }, + }, ) end end @@ -79,17 +81,18 @@ RSpec.describe "translate accelerator" do describe "plugins" do before do DiscoursePluginRegistry.register_locale( - 'foo', - name: 'Foo', - nativeName: 'Foo Bar', + "foo", + name: "Foo", + nativeName: "Foo Bar", plural: { - keys: [:one, :few, :other], - rule: lambda do |n| - return :one if n == 1 - return :few if n < 10 - :other - end - } + keys: %i[one few other], + rule: + lambda do |n| + return :one if n == 1 + return :few if n < 10 + :other + end, + }, ) LocaleSiteSetting.reset! @@ -104,10 +107,10 @@ RSpec.describe "translate accelerator" do it "loads plural rules from plugins" do I18n.locale = :foo - expect(I18n.t('i18n.plural.keys')).to eq([:one, :few, :other]) - expect(I18n.t('items', count: 1)).to eq('one item') - expect(I18n.t('items', count: 3)).to eq('some items') - expect(I18n.t('items', count: 20)).to eq('20 items') + expect(I18n.t("i18n.plural.keys")).to eq(%i[one few other]) + expect(I18n.t("items", count: 1)).to eq("one item") + expect(I18n.t("items", count: 3)).to eq("some items") + expect(I18n.t("items", count: 20)).to eq("20 items") end end @@ -115,124 +118,131 @@ RSpec.describe "translate accelerator" do before { I18n.locale = :en } it "returns the overridden key" do - override_translation('en', 'foo', 'Overwritten foo') - expect(I18n.t('foo')).to eq('Overwritten foo') + override_translation("en", "foo", "Overwritten foo") + expect(I18n.t("foo")).to eq("Overwritten foo") - override_translation('en', 'foo', 'new value') - expect(I18n.t('foo')).to eq('new value') + override_translation("en", "foo", "new value") + expect(I18n.t("foo")).to eq("new value") end it "returns the overridden key after switching the locale" do - override_translation('en', 'foo', 'Overwritten foo in EN') - override_translation('de', 'foo', 'Overwritten foo in DE') + override_translation("en", "foo", "Overwritten foo in EN") + override_translation("de", "foo", "Overwritten foo in DE") - expect(I18n.t('foo')).to eq('Overwritten foo in EN') + expect(I18n.t("foo")).to eq("Overwritten foo in EN") I18n.locale = :de - expect(I18n.t('foo')).to eq('Overwritten foo in DE') + expect(I18n.t("foo")).to eq("Overwritten foo in DE") end it "can be searched" do - override_translation('en', 'wat', 'Overwritten value') - expect(I18n.search('wat')).to include('wat' => 'Overwritten value') - expect(I18n.search('Overwritten')).to include('wat' => 'Overwritten value') + override_translation("en", "wat", "Overwritten value") + expect(I18n.search("wat")).to include("wat" => "Overwritten value") + expect(I18n.search("Overwritten")).to include("wat" => "Overwritten value") - override_translation('en', 'wat', 'Overwritten with (parentheses)') - expect(I18n.search('Overwritten with (')).to include('wat' => 'Overwritten with (parentheses)') + override_translation("en", "wat", "Overwritten with (parentheses)") + expect(I18n.search("Overwritten with (")).to include( + "wat" => "Overwritten with (parentheses)", + ) end it "supports disabling" do - orig_title = I18n.t('title') - override_translation('en', 'title', 'overridden title') + orig_title = I18n.t("title") + override_translation("en", "title", "overridden title") - I18n.overrides_disabled do - expect(I18n.t('title')).to eq(orig_title) - end + I18n.overrides_disabled { expect(I18n.t("title")).to eq(orig_title) } - expect(I18n.t('title')).to eq('overridden title') + expect(I18n.t("title")).to eq("overridden title") end it "supports interpolation" do - override_translation('en', 'world', 'my %{world}') - expect(I18n.t('world', world: 'foo')).to eq('my foo') + override_translation("en", "world", "my %{world}") + expect(I18n.t("world", world: "foo")).to eq("my foo") end it "supports interpolation named count" do - override_translation('en', 'wat', 'goodbye %{count}') - expect(I18n.t('wat', count: 123)).to eq('goodbye 123') + override_translation("en", "wat", "goodbye %{count}") + expect(I18n.t("wat", count: 123)).to eq("goodbye 123") end it "ignores interpolation named count if it is not applicable" do - override_translation('en', 'wat', 'bar') - expect(I18n.t('wat', count: 1)).to eq('bar') + override_translation("en", "wat", "bar") + expect(I18n.t("wat", count: 1)).to eq("bar") end it "supports one and other" do - override_translation('en', 'items.one', 'one fish') - override_translation('en', 'items.other', '%{count} fishies') - expect(I18n.t('items', count: 13)).to eq('13 fishies') - expect(I18n.t('items', count: 1)).to eq('one fish') + override_translation("en", "items.one", "one fish") + override_translation("en", "items.other", "%{count} fishies") + expect(I18n.t("items", count: 13)).to eq("13 fishies") + expect(I18n.t("items", count: 1)).to eq("one fish") end it "works with strings and symbols for non-pluralized string when count is given" do - override_translation('en', 'fish', 'trout') - expect(I18n.t(:fish, count: 1)).to eq('trout') - expect(I18n.t('fish', count: 1)).to eq('trout') + override_translation("en", "fish", "trout") + expect(I18n.t(:fish, count: 1)).to eq("trout") + expect(I18n.t("fish", count: 1)).to eq("trout") end it "supports one and other with fallback locale" do - override_translation('en_GB', 'items.one', 'one fish') - override_translation('en_GB', 'items.other', '%{count} fishies') + override_translation("en_GB", "items.one", "one fish") + override_translation("en_GB", "items.other", "%{count} fishies") I18n.with_locale(:en_GB) do - expect(I18n.t('items', count: 13)).to eq('13 fishies') - expect(I18n.t('items', count: 1)).to eq('one fish') + expect(I18n.t("items", count: 13)).to eq("13 fishies") + expect(I18n.t("items", count: 1)).to eq("one fish") end end it "supports one and other when only a single pluralization key is overridden" do - override_translation('en', 'keys.magic.other', 'no magic keys') - expect(I18n.t('keys.magic', count: 1)).to eq('one magic key') - expect(I18n.t('keys.magic', count: 2)).to eq('no magic keys') + override_translation("en", "keys.magic.other", "no magic keys") + expect(I18n.t("keys.magic", count: 1)).to eq("one magic key") + expect(I18n.t("keys.magic", count: 2)).to eq("no magic keys") end it "returns the overridden text when falling back" do - override_translation('en', 'got', 'summer') - expect(I18n.t('got')).to eq('summer') - expect(I18n.with_locale(:zh_TW) { I18n.t('got') }).to eq('summer') + override_translation("en", "got", "summer") + expect(I18n.t("got")).to eq("summer") + expect(I18n.with_locale(:zh_TW) { I18n.t("got") }).to eq("summer") - override_translation('en', 'throne', '%{title} is the new queen') - expect(I18n.t('throne', title: 'snow')).to eq('snow is the new queen') - expect(I18n.with_locale(:en) { I18n.t('throne', title: 'snow') }) - .to eq('snow is the new queen') + override_translation("en", "throne", "%{title} is the new queen") + expect(I18n.t("throne", title: "snow")).to eq("snow is the new queen") + expect(I18n.with_locale(:en) { I18n.t("throne", title: "snow") }).to eq( + "snow is the new queen", + ) end it "returns override if it exists before falling back" do - expect(I18n.t('got', default: '')).to eq('winter') - expect(I18n.with_locale(:ru) { I18n.t('got', default: '') }).to eq('winter') + expect(I18n.t("got", default: "")).to eq("winter") + expect(I18n.with_locale(:ru) { I18n.t("got", default: "") }).to eq("winter") - override_translation('ru', 'got', 'summer') - expect(I18n.t('got', default: '')).to eq('winter') - expect(I18n.with_locale(:ru) { I18n.t('got', default: '') }).to eq('summer') + override_translation("ru", "got", "summer") + expect(I18n.t("got", default: "")).to eq("winter") + expect(I18n.with_locale(:ru) { I18n.t("got", default: "") }).to eq("summer") end it "does not affect ActiveModel::Naming#human" do Fish = Class.new(ActiveRecord::Base) - override_translation('en', 'fish', 'fake fish') - expect(Fish.model_name.human).to eq('Fish') + override_translation("en", "fish", "fake fish") + expect(Fish.model_name.human).to eq("Fish") end it "works when the override contains an interpolation key" do expect(I18n.t("foo_with_variable")).to eq("Foo in :en with %{variable}") - I18n.with_locale(:de) { expect(I18n.t("foo_with_variable")).to eq("Foo in :de with %{variable}") } + I18n.with_locale(:de) do + expect(I18n.t("foo_with_variable")).to eq("Foo in :de with %{variable}") + end override_translation("en", "foo_with_variable", "Override in :en with %{variable}") expect(I18n.t("foo_with_variable")).to eq("Override in :en with %{variable}") - I18n.with_locale(:de) { expect(I18n.t("foo_with_variable")).to eq("Foo in :de with %{variable}") } + I18n.with_locale(:de) do + expect(I18n.t("foo_with_variable")).to eq("Foo in :de with %{variable}") + end override_translation("de", "foo_with_variable", "Override in :de with %{variable}") expect(I18n.t("foo_with_variable")).to eq("Override in :en with %{variable}") - I18n.with_locale(:de) { expect(I18n.t("foo_with_variable")).to eq("Override in :de with %{variable}") } + I18n.with_locale(:de) do + expect(I18n.t("foo_with_variable")).to eq("Override in :de with %{variable}") + end end end diff --git a/spec/lib/gaps_spec.rb b/spec/lib/gaps_spec.rb index aeab2f87e5..974886d4a1 100644 --- a/spec/lib/gaps_spec.rb +++ b/spec/lib/gaps_spec.rb @@ -1,24 +1,24 @@ # frozen_string_literal: true -require 'cache' +require "cache" RSpec.describe Gaps do - it 'returns no gaps for empty data' do + it "returns no gaps for empty data" do expect(Gaps.new(nil, nil)).to be_blank end - it 'returns no gaps with one element' do + it "returns no gaps with one element" do expect(Gaps.new([1], [1])).to be_blank end - it 'returns no gaps when all elements are present' do + it "returns no gaps when all elements are present" do expect(Gaps.new([1, 2, 3], [1, 2, 3])).to be_blank end describe "single element gap" do let(:gap) { Gaps.new([1, 3], [1, 2, 3]) } - it 'has a gap for post 3' do + it "has a gap for post 3" do expect(gap).not_to be_blank expect(gap.before[3]).to eq([2]) expect(gap.after).to be_blank @@ -28,7 +28,7 @@ RSpec.describe Gaps do describe "larger gap" do let(:gap) { Gaps.new([1, 2, 3, 6, 7], [1, 2, 3, 4, 5, 6, 7]) } - it 'has a gap for post 6' do + it "has a gap for post 6" do expect(gap).not_to be_blank expect(gap.before[6]).to eq([4, 5]) expect(gap.after).to be_blank @@ -38,7 +38,7 @@ RSpec.describe Gaps do describe "multiple gaps" do let(:gap) { Gaps.new([1, 5, 6, 7, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) } - it 'has both gaps' do + it "has both gaps" do expect(gap).not_to be_blank expect(gap.before[5]).to eq([2, 3, 4]) expect(gap.before[10]).to eq([8, 9]) @@ -49,7 +49,7 @@ RSpec.describe Gaps do describe "a gap in the beginning" do let(:gap) { Gaps.new([2, 3, 4], [1, 2, 3, 4]) } - it 'has the gap' do + it "has the gap" do expect(gap).not_to be_blank expect(gap.before[2]).to eq([1]) expect(gap.after).to be_blank @@ -59,7 +59,7 @@ RSpec.describe Gaps do describe "a gap in the ending" do let(:gap) { Gaps.new([1, 2, 3], [1, 2, 3, 4]) } - it 'has the gap' do + it "has the gap" do expect(gap).not_to be_blank expect(gap.before).to be_blank expect(gap.after[3]).to eq([4]) @@ -69,7 +69,7 @@ RSpec.describe Gaps do describe "a large gap in the ending" do let(:gap) { Gaps.new([1, 2, 3], [1, 2, 3, 4, 5, 6]) } - it 'has the gap' do + it "has the gap" do expect(gap).not_to be_blank expect(gap.before).to be_blank expect(gap.after[3]).to eq([4, 5, 6]) diff --git a/spec/lib/git_url_spec.rb b/spec/lib/git_url_spec.rb index 5d7ab3f964..a6cd1bca59 100644 --- a/spec/lib/git_url_spec.rb +++ b/spec/lib/git_url_spec.rb @@ -3,13 +3,13 @@ RSpec.describe GitUrl do it "handles the discourse github repo by ssh" do expect(GitUrl.normalize("git@github.com:discourse/discourse.git")).to eq( - "ssh://git@github.com/discourse/discourse.git" + "ssh://git@github.com/discourse/discourse.git", ) end it "handles the discourse github repo by https" do expect(GitUrl.normalize("https://github.com/discourse/discourse.git")).to eq( - "https://github.com/discourse/discourse.git" + "https://github.com/discourse/discourse.git", ) end end diff --git a/spec/lib/global_path_spec.rb b/spec/lib/global_path_spec.rb index 9846dca06f..61c0cecd7c 100644 --- a/spec/lib/global_path_spec.rb +++ b/spec/lib/global_path_spec.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require 'global_path' +require "global_path" class GlobalPathInstance extend GlobalPath end RSpec.describe GlobalPath do - describe '.cdn_relative_path' do + describe ".cdn_relative_path" do def cdn_relative_path(p) GlobalPathInstance.cdn_relative_path(p) end @@ -27,16 +27,19 @@ RSpec.describe GlobalPath do end end - describe '.upload_cdn_path' do - it 'generates correctly when S3 bucket has a folder' do - global_setting :s3_access_key_id, 's3_access_key_id' - global_setting :s3_secret_access_key, 's3_secret_access_key' - global_setting :s3_bucket, 'file-uploads/folder' - global_setting :s3_region, 'us-west-2' - global_setting :s3_cdn_url, 'https://cdn-aws.com/folder' + describe ".upload_cdn_path" do + it "generates correctly when S3 bucket has a folder" do + global_setting :s3_access_key_id, "s3_access_key_id" + global_setting :s3_secret_access_key, "s3_secret_access_key" + global_setting :s3_bucket, "file-uploads/folder" + global_setting :s3_region, "us-west-2" + global_setting :s3_cdn_url, "https://cdn-aws.com/folder" - expect(GlobalPathInstance.upload_cdn_path("#{Discourse.store.absolute_base_url}/folder/upload.jpg")) - .to eq("https://cdn-aws.com/folder/upload.jpg") + expect( + GlobalPathInstance.upload_cdn_path( + "#{Discourse.store.absolute_base_url}/folder/upload.jpg", + ), + ).to eq("https://cdn-aws.com/folder/upload.jpg") end end end diff --git a/spec/lib/group_email_credentials_check_spec.rb b/spec/lib/group_email_credentials_check_spec.rb index 074b540285..603c1a44df 100644 --- a/spec/lib/group_email_credentials_check_spec.rb +++ b/spec/lib/group_email_credentials_check_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'net/smtp' -require 'net/imap' +require "net/smtp" +require "net/imap" RSpec.describe GroupEmailCredentialsCheck do fab!(:group1) { Fabricate(:group) } @@ -29,35 +29,43 @@ RSpec.describe GroupEmailCredentialsCheck do end it "returns an error message and the group ID if the group's SMTP settings error" do - EmailSettingsValidator.expects(:validate_smtp).raises( - Net::SMTPAuthenticationError.new("bad credentials") - ).then.returns(true).at_least_once + EmailSettingsValidator + .expects(:validate_smtp) + .raises(Net::SMTPAuthenticationError.new("bad credentials")) + .then + .returns(true) + .at_least_once EmailSettingsValidator.stubs(:validate_imap).returns(true) - expect(described_class.run).to eq([ - { - group_full_name: group2.full_name, - group_name: group2.name, - group_id: group2.id, - message: I18n.t("email_settings.smtp_authentication_error") - } - ]) + expect(described_class.run).to eq( + [ + { + group_full_name: group2.full_name, + group_name: group2.name, + group_id: group2.id, + message: I18n.t("email_settings.smtp_authentication_error"), + }, + ], + ) end it "returns an error message and the group ID if the group's IMAP settings error" do EmailSettingsValidator.stubs(:validate_smtp).returns(true) - EmailSettingsValidator.expects(:validate_imap).raises( - Net::IMAP::NoResponseError.new(stub(data: stub(text: "Invalid credentials"))) - ).once + EmailSettingsValidator + .expects(:validate_imap) + .raises(Net::IMAP::NoResponseError.new(stub(data: stub(text: "Invalid credentials")))) + .once - expect(described_class.run).to eq([ - { - group_full_name: group3.full_name, - group_name: group3.name, - group_id: group3.id, - message: I18n.t("email_settings.imap_authentication_error") - } - ]) + expect(described_class.run).to eq( + [ + { + group_full_name: group3.full_name, + group_name: group3.name, + group_id: group3.id, + message: I18n.t("email_settings.imap_authentication_error"), + }, + ], + ) end it "returns no imap errors if imap is disabled for the site" do diff --git a/spec/lib/guardian/topic_guardian_spec.rb b/spec/lib/guardian/topic_guardian_spec.rb index e014482120..94ff370848 100644 --- a/spec/lib/guardian/topic_guardian_spec.rb +++ b/spec/lib/guardian/topic_guardian_spec.rb @@ -12,109 +12,105 @@ RSpec.describe TopicGuardian do fab!(:private_topic) { Fabricate(:topic, category: private_category) } fab!(:private_message_topic) { Fabricate(:private_message_topic) } - before do - Guardian.enable_topic_can_see_consistency_check - end + before { Guardian.enable_topic_can_see_consistency_check } - after do - Guardian.disable_topic_can_see_consistency_check - end + after { Guardian.disable_topic_can_see_consistency_check } - describe '#can_create_shared_draft?' do - it 'when shared_drafts are disabled' do - SiteSetting.shared_drafts_min_trust_level = 'admin' + describe "#can_create_shared_draft?" do + it "when shared_drafts are disabled" do + SiteSetting.shared_drafts_min_trust_level = "admin" expect(Guardian.new(admin).can_create_shared_draft?).to eq(false) end - it 'when user is a moderator and access is set to admin' do + it "when user is a moderator and access is set to admin" do SiteSetting.shared_drafts_category = category.id - SiteSetting.shared_drafts_min_trust_level = 'admin' + SiteSetting.shared_drafts_min_trust_level = "admin" expect(Guardian.new(moderator).can_create_shared_draft?).to eq(false) end - it 'when user is a moderator and access is set to staff' do + it "when user is a moderator and access is set to staff" do SiteSetting.shared_drafts_category = category.id - SiteSetting.shared_drafts_min_trust_level = 'staff' + SiteSetting.shared_drafts_min_trust_level = "staff" expect(Guardian.new(moderator).can_create_shared_draft?).to eq(true) end - it 'when user is TL3 and access is set to TL2' do + it "when user is TL3 and access is set to TL2" do SiteSetting.shared_drafts_category = category.id - SiteSetting.shared_drafts_min_trust_level = '2' + SiteSetting.shared_drafts_min_trust_level = "2" expect(Guardian.new(tl3_user).can_create_shared_draft?).to eq(true) end end - describe '#can_see_shared_draft?' do - it 'when shared_drafts are disabled (existing shared drafts)' do - SiteSetting.shared_drafts_min_trust_level = 'admin' + describe "#can_see_shared_draft?" do + it "when shared_drafts are disabled (existing shared drafts)" do + SiteSetting.shared_drafts_min_trust_level = "admin" expect(Guardian.new(admin).can_see_shared_draft?).to eq(true) end - it 'when user is a moderator and access is set to admin' do + it "when user is a moderator and access is set to admin" do SiteSetting.shared_drafts_category = category.id - SiteSetting.shared_drafts_min_trust_level = 'admin' + SiteSetting.shared_drafts_min_trust_level = "admin" expect(Guardian.new(moderator).can_see_shared_draft?).to eq(false) end - it 'when user is a moderator and access is set to staff' do + it "when user is a moderator and access is set to staff" do SiteSetting.shared_drafts_category = category.id - SiteSetting.shared_drafts_min_trust_level = 'staff' + SiteSetting.shared_drafts_min_trust_level = "staff" expect(Guardian.new(moderator).can_see_shared_draft?).to eq(true) end - it 'when user is TL3 and access is set to TL2' do + it "when user is TL3 and access is set to TL2" do SiteSetting.shared_drafts_category = category.id - SiteSetting.shared_drafts_min_trust_level = '2' + SiteSetting.shared_drafts_min_trust_level = "2" expect(Guardian.new(tl3_user).can_see_shared_draft?).to eq(true) end end - describe '#can_edit_topic?' do - context 'when the topic is a shared draft' do - let(:tl2_user) { Fabricate(:user, trust_level: TrustLevel[2]) } + describe "#can_edit_topic?" do + context "when the topic is a shared draft" do + let(:tl2_user) { Fabricate(:user, trust_level: TrustLevel[2]) } before do SiteSetting.shared_drafts_category = category.id - SiteSetting.shared_drafts_min_trust_level = '2' + SiteSetting.shared_drafts_min_trust_level = "2" end - it 'returns false if the topic is a PM' do + it "returns false if the topic is a PM" do pm_with_draft = Fabricate(:private_message_topic, category: category) Fabricate(:shared_draft, topic: pm_with_draft) expect(Guardian.new(tl2_user).can_edit_topic?(pm_with_draft)).to eq(false) end - it 'returns false if the topic is archived' do + it "returns false if the topic is archived" do archived_topic = Fabricate(:topic, archived: true, category: category) Fabricate(:shared_draft, topic: archived_topic) expect(Guardian.new(tl2_user).can_edit_topic?(archived_topic)).to eq(false) end - it 'returns true if a shared draft exists' do + it "returns true if a shared draft exists" do Fabricate(:shared_draft, topic: topic) expect(Guardian.new(tl2_user).can_edit_topic?(topic)).to eq(true) end - it 'returns false if the user has a lower trust level' do + it "returns false if the user has a lower trust level" do tl1_user = Fabricate(:user, trust_level: TrustLevel[1]) Fabricate(:shared_draft, topic: topic) expect(Guardian.new(tl1_user).can_edit_topic?(topic)).to eq(false) end - it 'returns true if the shared_draft is from a different category' do + it "returns true if the shared_draft is from a different category" do topic = Fabricate(:topic, category: Fabricate(:category)) Fabricate(:shared_draft, topic: topic) @@ -123,8 +119,8 @@ RSpec.describe TopicGuardian do end end - describe '#can_review_topic?' do - it 'returns false for TL4 users' do + describe "#can_review_topic?" do + it "returns false for TL4 users" do tl4_user = Fabricate(:user, trust_level: TrustLevel[4]) topic = Fabricate(:topic) @@ -151,27 +147,26 @@ RSpec.describe TopicGuardian do # The test cases here are intentionally kept brief because majority of the cases are already handled by # `TopicGuardianCanSeeConsistencyCheck` which we run to ensure that the implementation between `TopicGuardian#can_see_topic_ids` # and `TopicGuardian#can_see_topic?` is consistent. - describe '#can_see_topic_ids' do - it 'returns the topic ids for the topics which a user is allowed to see' do - expect(Guardian.new.can_see_topic_ids(topic_ids: [topic.id, private_message_topic.id])).to contain_exactly( - topic.id - ) + describe "#can_see_topic_ids" do + it "returns the topic ids for the topics which a user is allowed to see" do + expect( + Guardian.new.can_see_topic_ids(topic_ids: [topic.id, private_message_topic.id]), + ).to contain_exactly(topic.id) - expect(Guardian.new(user).can_see_topic_ids(topic_ids: [topic.id, private_message_topic.id])).to contain_exactly( - topic.id - ) + expect( + Guardian.new(user).can_see_topic_ids(topic_ids: [topic.id, private_message_topic.id]), + ).to contain_exactly(topic.id) - expect(Guardian.new(moderator).can_see_topic_ids(topic_ids: [topic.id, private_message_topic.id])).to contain_exactly( - topic.id, - ) + expect( + Guardian.new(moderator).can_see_topic_ids(topic_ids: [topic.id, private_message_topic.id]), + ).to contain_exactly(topic.id) - expect(Guardian.new(admin).can_see_topic_ids(topic_ids: [topic.id, private_message_topic.id])).to contain_exactly( - topic.id, - private_message_topic.id - ) + expect( + Guardian.new(admin).can_see_topic_ids(topic_ids: [topic.id, private_message_topic.id]), + ).to contain_exactly(topic.id, private_message_topic.id) end - it 'returns the topic ids for topics which are deleted but user is a category moderator of' do + it "returns the topic ids for topics which are deleted but user is a category moderator of" do SiteSetting.enable_category_group_moderation = true category.update!(reviewable_by_group_id: group.id) @@ -182,20 +177,18 @@ RSpec.describe TopicGuardian do topic2 = Fabricate(:topic) user2 = Fabricate(:user) - expect(Guardian.new(user).can_see_topic_ids(topic_ids: [topic.id, topic2.id])).to contain_exactly( - topic.id, - topic2.id - ) + expect( + Guardian.new(user).can_see_topic_ids(topic_ids: [topic.id, topic2.id]), + ).to contain_exactly(topic.id, topic2.id) - expect(Guardian.new(user2).can_see_topic_ids(topic_ids: [topic.id, topic2.id])).to contain_exactly( - topic2.id, - ) + expect( + Guardian.new(user2).can_see_topic_ids(topic_ids: [topic.id, topic2.id]), + ).to contain_exactly(topic2.id) end end - describe '#filter_allowed_categories' do - - it 'allows admin access to categories without explicit access' do + describe "#filter_allowed_categories" do + it "allows admin access to categories without explicit access" do guardian = Guardian.new(admin) list = Topic.where(id: private_topic.id) list = guardian.filter_allowed_categories(list) @@ -203,12 +196,10 @@ RSpec.describe TopicGuardian do expect(list.count).to eq(1) end - context 'when SiteSetting.suppress_secured_categories_from_admin is true' do - before do - SiteSetting.suppress_secured_categories_from_admin = true - end + context "when SiteSetting.suppress_secured_categories_from_admin is true" do + before { SiteSetting.suppress_secured_categories_from_admin = true } - it 'does not allow admin access to categories without explicit access' do + it "does not allow admin access to categories without explicit access" do guardian = Guardian.new(admin) list = Topic.where(id: private_topic.id) list = guardian.filter_allowed_categories(list) @@ -216,6 +207,5 @@ RSpec.describe TopicGuardian do expect(list.count).to eq(0) end end - end end diff --git a/spec/lib/guardian/user_guardian_spec.rb b/spec/lib/guardian/user_guardian_spec.rb index 3b10a6fbe1..b6b0ab0e80 100644 --- a/spec/lib/guardian/user_guardian_spec.rb +++ b/spec/lib/guardian/user_guardian_spec.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true RSpec.describe UserGuardian do - let :user do Fabricate(:user) end @@ -14,9 +13,7 @@ RSpec.describe UserGuardian do Fabricate(:admin) end - let(:user_avatar) do - Fabricate(:user_avatar, user: user) - end + let(:user_avatar) { Fabricate(:user_avatar, user: user) } let :users_upload do Upload.new(user_id: user_avatar.user_id, id: 1) @@ -32,20 +29,17 @@ RSpec.describe UserGuardian do Upload.new(user_id: 9999, id: 3) end - let(:moderator_upload) do - Upload.new(user_id: moderator.id, id: 4) - end + let(:moderator_upload) { Upload.new(user_id: moderator.id, id: 4) } let(:trust_level_1) { build(:user, trust_level: 1) } let(:trust_level_2) { build(:user, trust_level: 2) } - describe '#can_pick_avatar?' do - + describe "#can_pick_avatar?" do let :guardian do Guardian.new(user) end - context 'with anon user' do + context "with anon user" do let(:guardian) { Guardian.new } it "should return the right value" do @@ -53,32 +47,25 @@ RSpec.describe UserGuardian do end end - context 'with current user' do + context "with current user" do it "can not set uploads not owned by current user" do expect(guardian.can_pick_avatar?(user_avatar, users_upload)).to eq(true) expect(guardian.can_pick_avatar?(user_avatar, already_uploaded)).to eq(true) - UserUpload.create!( - upload_id: not_my_upload.id, - user_id: not_my_upload.user_id - ) + UserUpload.create!(upload_id: not_my_upload.id, user_id: not_my_upload.user_id) expect(guardian.can_pick_avatar?(user_avatar, not_my_upload)).to eq(false) expect(guardian.can_pick_avatar?(user_avatar, nil)).to eq(true) end it "can handle uploads that are associated but not directly owned" do - UserUpload.create!( - upload_id: not_my_upload.id, - user_id: user_avatar.user_id - ) + UserUpload.create!(upload_id: not_my_upload.id, user_id: user_avatar.user_id) - expect(guardian.can_pick_avatar?(user_avatar, not_my_upload)) - .to eq(true) + expect(guardian.can_pick_avatar?(user_avatar, not_my_upload)).to eq(true) end end - context 'with moderator' do + context "with moderator" do let :guardian do Guardian.new(moderator) end @@ -92,7 +79,7 @@ RSpec.describe UserGuardian do end end - context 'with admin' do + context "with admin" do let :guardian do Guardian.new(admin) end @@ -153,7 +140,7 @@ RSpec.describe UserGuardian do Fabricate(:user_field), Fabricate(:user_field, show_on_profile: true), Fabricate(:user_field, show_on_user_card: true), - Fabricate(:user_field, show_on_user_card: true, show_on_profile: true) + Fabricate(:user_field, show_on_user_card: true, show_on_profile: true), ] end @@ -198,9 +185,7 @@ RSpec.describe UserGuardian do end context "when user created too many posts" do - before do - (User::MAX_STAFF_DELETE_POST_COUNT + 1).times { Fabricate(:post, user: user) } - end + before { (User::MAX_STAFF_DELETE_POST_COUNT + 1).times { Fabricate(:post, user: user) } } it "is allowed when user created the first post within delete_user_max_post_age days" do SiteSetting.delete_user_max_post_age = 2 @@ -214,9 +199,7 @@ RSpec.describe UserGuardian do end context "when user didn't create many posts" do - before do - (User::MAX_STAFF_DELETE_POST_COUNT - 1).times { Fabricate(:post, user: user) } - end + before { (User::MAX_STAFF_DELETE_POST_COUNT - 1).times { Fabricate(:post, user: user) } } it "is allowed when even when user created the first post before delete_user_max_post_age days" do SiteSetting.delete_user_max_post_age = 2 @@ -258,10 +241,15 @@ RSpec.describe UserGuardian do end it "is allowed when user responded to PM from system user" do - topic = Fabricate(:private_message_topic, user: Discourse.system_user, topic_allowed_users: [ - Fabricate.build(:topic_allowed_user, user: Discourse.system_user), - Fabricate.build(:topic_allowed_user, user: user) - ]) + topic = + Fabricate( + :private_message_topic, + user: Discourse.system_user, + topic_allowed_users: [ + Fabricate.build(:topic_allowed_user, user: Discourse.system_user), + Fabricate.build(:topic_allowed_user, user: user), + ], + ) Fabricate(:post, user: user, topic: topic) expect(guardian.can_delete_user?(user)).to eq(true) @@ -271,9 +259,12 @@ RSpec.describe UserGuardian do end it "is allowed when user created multiple posts in PMs to themselves" do - topic = Fabricate(:private_message_topic, user: user, topic_allowed_users: [ - Fabricate.build(:topic_allowed_user, user: user) - ]) + topic = + Fabricate( + :private_message_topic, + user: user, + topic_allowed_users: [Fabricate.build(:topic_allowed_user, user: user)], + ) Fabricate(:post, user: user, topic: topic) Fabricate(:post, user: user, topic: topic) @@ -281,10 +272,15 @@ RSpec.describe UserGuardian do end it "isn't allowed when user created multiple posts in PMs sent to other users" do - topic = Fabricate(:private_message_topic, user: user, topic_allowed_users: [ - Fabricate.build(:topic_allowed_user, user: user), - Fabricate.build(:topic_allowed_user, user: Fabricate(:user)) - ]) + topic = + Fabricate( + :private_message_topic, + user: user, + topic_allowed_users: [ + Fabricate.build(:topic_allowed_user, user: user), + Fabricate.build(:topic_allowed_user, user: Fabricate(:user)), + ], + ) Fabricate(:post, user: user, topic: topic) expect(guardian.can_delete_user?(user)).to eq(true) @@ -294,12 +290,16 @@ RSpec.describe UserGuardian do end it "isn't allowed when user created multiple posts in PMs sent to groups" do - topic = Fabricate(:private_message_topic, user: user, topic_allowed_users: [ - Fabricate.build(:topic_allowed_user, user: user) - ], topic_allowed_groups: [ - Fabricate.build(:topic_allowed_group, group: Fabricate(:group)), - Fabricate.build(:topic_allowed_group, group: Fabricate(:group)) - ]) + topic = + Fabricate( + :private_message_topic, + user: user, + topic_allowed_users: [Fabricate.build(:topic_allowed_user, user: user)], + topic_allowed_groups: [ + Fabricate.build(:topic_allowed_group, group: Fabricate(:group)), + Fabricate.build(:topic_allowed_group, group: Fabricate(:group)), + ], + ) Fabricate(:post, user: user, topic: topic) expect(guardian.can_delete_user?(user)).to eq(true) @@ -372,12 +372,12 @@ RSpec.describe UserGuardian do end describe "#can_see_review_queue?" do - it 'returns true when the user is a staff member' do + it "returns true when the user is a staff member" do guardian = Guardian.new(moderator) expect(guardian.can_see_review_queue?).to eq(true) end - it 'returns false for a regular user' do + it "returns false for a regular user" do guardian = Guardian.new(user) expect(guardian.can_see_review_queue?).to eq(false) end @@ -393,7 +393,7 @@ RSpec.describe UserGuardian do expect(guardian.can_see_review_queue?).to eq(true) end - it 'returns false if category group review is disabled' do + it "returns false if category group review is disabled" do group = Fabricate(:group) group.add(user) guardian = Guardian.new(user) @@ -404,7 +404,7 @@ RSpec.describe UserGuardian do expect(guardian.can_see_review_queue?).to eq(false) end - it 'returns false if the reviewable is under a read restricted category' do + it "returns false if the reviewable is under a read restricted category" do group = Fabricate(:group) group.add(user) guardian = Guardian.new(user) @@ -417,38 +417,38 @@ RSpec.describe UserGuardian do end end - describe 'can_upload_profile_header' do - it 'returns true if it is an admin' do + describe "can_upload_profile_header" do + it "returns true if it is an admin" do guardian = Guardian.new(admin) expect(guardian.can_upload_profile_header?(admin)).to eq(true) end - it 'returns true if the trust level of user matches site setting' do + it "returns true if the trust level of user matches site setting" do guardian = Guardian.new(trust_level_2) SiteSetting.min_trust_level_to_allow_profile_background = 2 expect(guardian.can_upload_profile_header?(trust_level_2)).to eq(true) end - it 'returns false if the trust level of user does not matches site setting' do + it "returns false if the trust level of user does not matches site setting" do guardian = Guardian.new(trust_level_1) SiteSetting.min_trust_level_to_allow_profile_background = 2 expect(guardian.can_upload_profile_header?(trust_level_1)).to eq(false) end end - describe 'can_upload_user_card_background' do - it 'returns true if it is an admin' do + describe "can_upload_user_card_background" do + it "returns true if it is an admin" do guardian = Guardian.new(admin) expect(guardian.can_upload_user_card_background?(admin)).to eq(true) end - it 'returns true if the trust level of user matches site setting' do + it "returns true if the trust level of user matches site setting" do guardian = Guardian.new(trust_level_2) SiteSetting.min_trust_level_to_allow_user_card_background = 2 expect(guardian.can_upload_user_card_background?(trust_level_2)).to eq(true) end - it 'returns false if the trust level of user does not matches site setting' do + it "returns false if the trust level of user does not matches site setting" do guardian = Guardian.new(trust_level_1) SiteSetting.min_trust_level_to_allow_user_card_background = 2 expect(guardian.can_upload_user_card_background?(trust_level_1)).to eq(false) diff --git a/spec/lib/guardian_spec.rb b/spec/lib/guardian_spec.rb index 25a0066f74..4e5bf8d88f 100644 --- a/spec/lib/guardian_spec.rb +++ b/spec/lib/guardian_spec.rb @@ -18,7 +18,7 @@ RSpec.describe Guardian do fab!(:trust_level_1) { Fabricate(:user, trust_level: 1) } fab!(:trust_level_2) { Fabricate(:user, trust_level: 2) } fab!(:trust_level_3) { Fabricate(:user, trust_level: 3) } - fab!(:trust_level_4) { Fabricate(:user, trust_level: 4) } + fab!(:trust_level_4) { Fabricate(:user, trust_level: 4) } fab!(:another_admin) { Fabricate(:admin) } fab!(:coding_horror) { Fabricate(:coding_horror) } @@ -30,38 +30,36 @@ RSpec.describe Guardian do Guardian.enable_topic_can_see_consistency_check end - after do - Guardian.disable_topic_can_see_consistency_check - end + after { Guardian.disable_topic_can_see_consistency_check } - it 'can be created without a user (not logged in)' do + it "can be created without a user (not logged in)" do expect { Guardian.new }.not_to raise_error end - it 'can be instantiated with a user instance' do + it "can be instantiated with a user instance" do expect { Guardian.new(user) }.not_to raise_error end describe "link_posting_access" do it "is none for anonymous users" do - expect(Guardian.new.link_posting_access).to eq('none') + expect(Guardian.new.link_posting_access).to eq("none") end it "is full for regular users" do - expect(Guardian.new(user).link_posting_access).to eq('full') + expect(Guardian.new(user).link_posting_access).to eq("full") end it "is none for a user of a low trust level" do user.trust_level = 0 SiteSetting.min_trust_to_post_links = 1 - expect(Guardian.new(user).link_posting_access).to eq('none') + expect(Guardian.new(user).link_posting_access).to eq("none") end it "is limited for a user of a low trust level with a allowlist" do - SiteSetting.allowed_link_domains = 'example.com' + SiteSetting.allowed_link_domains = "example.com" user.trust_level = 0 SiteSetting.min_trust_to_post_links = 1 - expect(Guardian.new(user).link_posting_access).to eq('limited') + expect(Guardian.new(user).link_posting_access).to eq("limited") end end @@ -85,20 +83,18 @@ RSpec.describe Guardian do end describe "allowlisted host" do - before do - SiteSetting.allowed_link_domains = host - end + before { SiteSetting.allowed_link_domains = host } it "allows a new user to post the link to the host" do user.trust_level = 0 SiteSetting.min_trust_to_post_links = 1 expect(Guardian.new(user).can_post_link?(host: host)).to eq(true) - expect(Guardian.new(user).can_post_link?(host: 'another-host.com')).to eq(false) + expect(Guardian.new(user).can_post_link?(host: "another-host.com")).to eq(false) end end end - describe '#post_can_act?' do + describe "#post_can_act?" do fab!(:user) { Fabricate(:user) } fab!(:post) { Fabricate(:post) } @@ -145,10 +141,8 @@ RSpec.describe Guardian do expect(Guardian.new(user).post_can_act?(staff_post, :spam)).to be_truthy end - describe 'when allow_flagging_staff is false' do - before do - SiteSetting.allow_flagging_staff = false - end + describe "when allow_flagging_staff is false" do + before { SiteSetting.allow_flagging_staff = false } it "doesn't allow flagging of staff posts" do expect(Guardian.new(user).post_can_act?(staff_post, :spam)).to eq(false) @@ -170,16 +164,32 @@ RSpec.describe Guardian do end it "returns false when you've already done it" do - expect(Guardian.new(user).post_can_act?(post, :like, opts: { - taken_actions: { PostActionType.types[:like] => 1 } - })).to be_falsey + expect( + Guardian.new(user).post_can_act?( + post, + :like, + opts: { + taken_actions: { + PostActionType.types[:like] => 1, + }, + }, + ), + ).to be_falsey end it "returns false when you already flagged a post" do PostActionType.notify_flag_types.each do |type, _id| - expect(Guardian.new(user).post_can_act?(post, :off_topic, opts: { - taken_actions: { PostActionType.types[type] => 1 } - })).to be_falsey + expect( + Guardian.new(user).post_can_act?( + post, + :off_topic, + opts: { + taken_actions: { + PostActionType.types[type] => 1, + }, + }, + ), + ).to be_falsey end end @@ -231,9 +241,7 @@ RSpec.describe Guardian do fab!(:moderator) { Fabricate(:moderator) } context "when enabled" do - before do - SiteSetting.enable_safe_mode = true - end + before { SiteSetting.enable_safe_mode = true } it "can be performed" do expect(Guardian.new.can_enable_safe_mode?).to eq(true) @@ -243,9 +251,7 @@ RSpec.describe Guardian do end context "when disabled" do - before do - SiteSetting.enable_safe_mode = false - end + before { SiteSetting.enable_safe_mode = false } it "can be performed" do expect(Guardian.new.can_enable_safe_mode?).to eq(false) @@ -255,8 +261,10 @@ RSpec.describe Guardian do end end - describe 'can_send_private_message' do - fab!(:suspended_user) { Fabricate(:user, suspended_till: 1.week.from_now, suspended_at: 1.day.ago) } + describe "can_send_private_message" do + fab!(:suspended_user) do + Fabricate(:user, suspended_till: 1.week.from_now, suspended_at: 1.day.ago) + end it "returns false when the user is nil" do expect(Guardian.new(nil).can_send_private_message?(user)).to be_falsey @@ -291,9 +299,7 @@ RSpec.describe Guardian do context "when personal_message_enabled_groups does not contain the user" do let(:group) { Fabricate(:group) } - before do - SiteSetting.personal_message_enabled_groups = group.id - end + before { SiteSetting.personal_message_enabled_groups = group.id } it "returns false if user is not staff member" do expect(Guardian.new(trust_level_4).can_send_private_message?(another_user)).to be_falsey @@ -312,7 +318,9 @@ RSpec.describe Guardian do end it "returns true for system user" do - expect(Guardian.new(Discourse.system_user).can_send_private_message?(another_user)).to be_truthy + expect( + Guardian.new(Discourse.system_user).can_send_private_message?(another_user), + ).to be_truthy end end @@ -356,7 +364,7 @@ RSpec.describe Guardian do group.update!(messageable_level: Group::ALIAS_LEVELS[level]) user_output = level == :everyone ? true : false admin_output = level != :nobody - mod_output = [:nobody, :only_admins].exclude?(level) + mod_output = %i[nobody only_admins].exclude?(level) expect(Guardian.new(user).can_send_private_message?(group)).to eq(user_output) expect(Guardian.new(admin).can_send_private_message?(group)).to eq(admin_output) @@ -398,30 +406,29 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_send_private_message?(group)).to eq(true) end - context 'when target user has private message disabled' do - before do - another_user.user_option.update!(allow_private_messages: false) - end + context "when target user has private message disabled" do + before { another_user.user_option.update!(allow_private_messages: false) } - context 'for a normal user' do - it 'should return false' do + context "for a normal user" do + it "should return false" do expect(Guardian.new(user).can_send_private_message?(another_user)).to eq(false) end end - context 'for a staff user' do - it 'should return true' do + context "for a staff user" do + it "should return true" do [admin, moderator].each do |staff_user| - expect(Guardian.new(staff_user).can_send_private_message?(another_user)) - .to eq(true) + expect(Guardian.new(staff_user).can_send_private_message?(another_user)).to eq(true) end end end end end - describe 'can_send_private_messages' do - fab!(:suspended_user) { Fabricate(:user, suspended_till: 1.week.from_now, suspended_at: 1.day.ago) } + describe "can_send_private_messages" do + fab!(:suspended_user) do + Fabricate(:user, suspended_till: 1.week.from_now, suspended_at: 1.day.ago) + end it "returns false when the user is nil" do expect(Guardian.new(nil).can_send_private_messages?).to be_falsey @@ -441,9 +448,7 @@ RSpec.describe Guardian do context "when personal_message_enabled_groups does contain the user" do let(:group) { Fabricate(:group) } - before do - SiteSetting.personal_message_enabled_groups = group.id - end + before { SiteSetting.personal_message_enabled_groups = group.id } it "returns true" do expect(Guardian.new(user).can_send_private_messages?).to be_falsey @@ -455,9 +460,7 @@ RSpec.describe Guardian do context "when personal_message_enabled_groups does not contain the user" do let(:group) { Fabricate(:group) } - before do - SiteSetting.personal_message_enabled_groups = group.id - end + before { SiteSetting.personal_message_enabled_groups = group.id } it "returns false if user is not staff member" do expect(Guardian.new(trust_level_4).can_send_private_messages?).to be_falsey @@ -492,7 +495,7 @@ RSpec.describe Guardian do end end - describe 'can_reply_as_new_topic' do + describe "can_reply_as_new_topic" do fab!(:topic) { Fabricate(:topic) } fab!(:private_message) { Fabricate(:private_message_topic) } @@ -518,11 +521,10 @@ RSpec.describe Guardian do end end - describe 'can_see_post_actors?' do - + describe "can_see_post_actors?" do let(:topic) { Fabricate(:topic, user: coding_horror) } - it 'displays visibility correctly' do + it "displays visibility correctly" do guardian = Guardian.new(user) expect(guardian.can_see_post_actors?(nil, PostActionType.types[:like])).to be_falsey expect(guardian.can_see_post_actors?(topic, PostActionType.types[:like])).to be_truthy @@ -530,13 +532,14 @@ RSpec.describe Guardian do expect(guardian.can_see_post_actors?(topic, PostActionType.types[:spam])).to be_falsey expect(guardian.can_see_post_actors?(topic, PostActionType.types[:notify_user])).to be_falsey - expect(Guardian.new(moderator).can_see_post_actors?(topic, PostActionType.types[:notify_user])).to be_truthy + expect( + Guardian.new(moderator).can_see_post_actors?(topic, PostActionType.types[:notify_user]), + ).to be_truthy end - end - describe 'can_impersonate?' do - it 'allows impersonation correctly' do + describe "can_impersonate?" do + it "allows impersonation correctly" do expect(Guardian.new(admin).can_impersonate?(nil)).to be_falsey expect(Guardian.new.can_impersonate?(user)).to be_falsey expect(Guardian.new(coding_horror).can_impersonate?(user)).to be_falsey @@ -551,30 +554,30 @@ RSpec.describe Guardian do end describe "can_view_action_logs?" do - it 'is false for non-staff acting user' do + it "is false for non-staff acting user" do expect(Guardian.new(user).can_view_action_logs?(moderator)).to be_falsey end - it 'is false without a target user' do + it "is false without a target user" do expect(Guardian.new(moderator).can_view_action_logs?(nil)).to be_falsey end - it 'is true when target user is present' do + it "is true when target user is present" do expect(Guardian.new(moderator).can_view_action_logs?(user)).to be_truthy end end - describe 'can_invite_to_forum?' do + describe "can_invite_to_forum?" do fab!(:user) { Fabricate(:user) } fab!(:moderator) { Fabricate(:moderator) } - it 'returns true if user has sufficient trust level' do + it "returns true if user has sufficient trust level" do SiteSetting.min_trust_level_to_allow_invite = 2 expect(Guardian.new(trust_level_2).can_invite_to_forum?).to be_truthy expect(Guardian.new(moderator).can_invite_to_forum?).to be_truthy end - it 'returns false if user trust level does not have sufficient trust level' do + it "returns false if user trust level does not have sufficient trust level" do SiteSetting.min_trust_level_to_allow_invite = 2 expect(Guardian.new(trust_level_1).can_invite_to_forum?).to be_falsey end @@ -583,12 +586,12 @@ RSpec.describe Guardian do expect(Guardian.new.can_invite_to_forum?).to be_falsey end - it 'returns true when the site requires approving users' do + it "returns true when the site requires approving users" do SiteSetting.must_approve_users = true expect(Guardian.new(trust_level_2).can_invite_to_forum?).to be_truthy end - it 'returns false when max_invites_per_day is 0' do + it "returns false when max_invites_per_day is 0" do # let's also break it while here SiteSetting.max_invites_per_day = "a" @@ -597,7 +600,7 @@ RSpec.describe Guardian do expect(Guardian.new(moderator).can_invite_to_forum?).to be_truthy end - context 'with groups' do + context "with groups" do let(:groups) { [group, another_group] } before do @@ -605,14 +608,13 @@ RSpec.describe Guardian do group.add_owner(user) end - it 'returns false when user is not allowed to edit a group' do + it "returns false when user is not allowed to edit a group" do expect(Guardian.new(user).can_invite_to_forum?(groups)).to eq(false) - expect(Guardian.new(admin).can_invite_to_forum?(groups)) - .to eq(true) + expect(Guardian.new(admin).can_invite_to_forum?(groups)).to eq(true) end - it 'returns true when user is allowed to edit groups' do + it "returns true when user is allowed to edit groups" do another_group.add_owner(user) expect(Guardian.new(user).can_invite_to_forum?(groups)).to eq(true) @@ -620,8 +622,7 @@ RSpec.describe Guardian do end end - describe 'can_invite_to?' do - + describe "can_invite_to?" do describe "regular topics" do before do SiteSetting.min_trust_level_to_allow_invite = 2 @@ -631,11 +632,11 @@ RSpec.describe Guardian do fab!(:topic) { Fabricate(:topic) } fab!(:private_topic) { Fabricate(:topic, category: category) } fab!(:user) { topic.user } - let(:private_category) { Fabricate(:private_category, group: group) } + let(:private_category) { Fabricate(:private_category, group: group) } let(:group_private_topic) { Fabricate(:topic, category: private_category) } let(:group_owner) { group_private_topic.user.tap { |u| group.add_owner(u) } } - it 'handles invitation correctly' do + it "handles invitation correctly" do expect(Guardian.new(nil).can_invite_to?(topic)).to be_falsey expect(Guardian.new(moderator).can_invite_to?(nil)).to be_falsey expect(Guardian.new(moderator).can_invite_to?(topic)).to be_truthy @@ -648,26 +649,26 @@ RSpec.describe Guardian do expect(Guardian.new(moderator).can_invite_to?(topic)).to be_truthy end - it 'returns false for normal user on private topic' do + it "returns false for normal user on private topic" do expect(Guardian.new(user).can_invite_to?(private_topic)).to be_falsey end - it 'returns false for admin on private topic' do + it "returns false for admin on private topic" do expect(Guardian.new(admin).can_invite_to?(private_topic)).to be(false) end - it 'returns true for a group owner' do + it "returns true for a group owner" do group_owner.update!(trust_level: SiteSetting.min_trust_level_to_allow_invite) expect(Guardian.new(group_owner).can_invite_to?(group_private_topic)).to be_truthy end - it 'return true for normal users even if must_approve_users' do + it "return true for normal users even if must_approve_users" do SiteSetting.must_approve_users = true expect(Guardian.new(user).can_invite_to?(topic)).to be_truthy expect(Guardian.new(admin).can_invite_to?(topic)).to be_truthy end - describe 'for a private category for automatic and non-automatic group' do + describe "for a private category for automatic and non-automatic group" do let(:category) do Fabricate(:category, read_restricted: true).tap do |category| category.groups << automatic_group @@ -677,21 +678,21 @@ RSpec.describe Guardian do let(:topic) { Fabricate(:topic, category: category) } - it 'should return true for an admin user' do + it "should return true for an admin user" do expect(Guardian.new(admin).can_invite_to?(topic)).to eq(true) end - it 'should return true for a group owner' do + it "should return true for a group owner" do group_owner.update!(trust_level: SiteSetting.min_trust_level_to_allow_invite) expect(Guardian.new(group_owner).can_invite_to?(topic)).to eq(true) end - it 'should return false for a normal user' do + it "should return false for a normal user" do expect(Guardian.new(user).can_invite_to?(topic)).to eq(false) end end - describe 'for a private category for automatic groups' do + describe "for a private category for automatic groups" do let(:category) do Fabricate(:private_category, group: automatic_group, read_restricted: true) end @@ -699,7 +700,7 @@ RSpec.describe Guardian do let(:group_owner) { Fabricate(:user).tap { |user| automatic_group.add_owner(user) } } let(:topic) { Fabricate(:topic, category: category) } - it 'should return false for all type of users' do + it "should return false for all type of users" do expect(Guardian.new(admin).can_invite_to?(topic)).to eq(false) expect(Guardian.new(group_owner).can_invite_to?(topic)).to eq(false) expect(Guardian.new(user).can_invite_to?(topic)).to eq(false) @@ -724,9 +725,7 @@ RSpec.describe Guardian do end context "when user does not belong to personal_message_enabled_groups" do - before do - SiteSetting.personal_message_enabled_groups = Group::AUTO_GROUPS[:staff] - end + before { SiteSetting.personal_message_enabled_groups = Group::AUTO_GROUPS[:staff] } it "doesn't allow a regular user to invite" do expect(Guardian.new(admin).can_invite_to?(pm)).to be_truthy @@ -735,9 +734,7 @@ RSpec.describe Guardian do end context "when PM has reached the maximum number of recipients" do - before do - SiteSetting.max_allowed_message_recipients = 2 - end + before { SiteSetting.max_allowed_message_recipients = 2 } it "doesn't allow a regular user to invite" do expect(Guardian.new(user).can_invite_to?(pm)).to be_falsey @@ -752,14 +749,14 @@ RSpec.describe Guardian do end end - describe 'can_invite_via_email?' do - it 'returns true for all (tl2 and above) users when sso is disabled, local logins are enabled, user approval is not required' do + describe "can_invite_via_email?" do + it "returns true for all (tl2 and above) users when sso is disabled, local logins are enabled, user approval is not required" do expect(Guardian.new(trust_level_2).can_invite_via_email?(topic)).to be_truthy expect(Guardian.new(moderator).can_invite_via_email?(topic)).to be_truthy expect(Guardian.new(admin).can_invite_via_email?(topic)).to be_truthy end - it 'returns true for all users when sso is enabled' do + it "returns true for all users when sso is enabled" do SiteSetting.discourse_connect_url = "https://www.example.com/sso" SiteSetting.enable_discourse_connect = true @@ -768,7 +765,7 @@ RSpec.describe Guardian do expect(Guardian.new(admin).can_invite_via_email?(topic)).to be_truthy end - it 'returns false for all users when local logins are disabled' do + it "returns false for all users when local logins are disabled" do SiteSetting.enable_local_logins = false expect(Guardian.new(trust_level_2).can_invite_via_email?(topic)).to be_falsey @@ -776,7 +773,7 @@ RSpec.describe Guardian do expect(Guardian.new(admin).can_invite_via_email?(topic)).to be_falsey end - it 'returns correct values when user approval is required' do + it "returns correct values when user approval is required" do SiteSetting.must_approve_users = true expect(Guardian.new(trust_level_2).can_invite_via_email?(topic)).to be_falsey @@ -788,9 +785,7 @@ RSpec.describe Guardian do describe "can_see_deleted_post?" do fab!(:post) { Fabricate(:post) } - before do - post.trash!(user) - end + before { post.trash!(user) } it "returns false for post that is not deleted" do post.recover! @@ -825,23 +820,21 @@ RSpec.describe Guardian do end end - describe 'can_see?' do - - it 'returns false with a nil object' do + describe "can_see?" do + it "returns false with a nil object" do expect(Guardian.new.can_see?(nil)).to be_falsey end - describe 'a Category' do - - it 'allows public categories' do + describe "a Category" do + it "allows public categories" do public_category = Fabricate(:category, read_restricted: false) expect(Guardian.new.can_see?(public_category)).to be_truthy end - it 'correctly handles secure categories' do + it "correctly handles secure categories" do normal_user = Fabricate(:user) staged_user = Fabricate(:user, staged: true) - admin_user = Fabricate(:user, admin: true) + admin_user = Fabricate(:user, admin: true) secure_category = Fabricate(:category, read_restricted: true) expect(Guardian.new(normal_user).can_see?(secure_category)).to be_falsey @@ -853,18 +846,25 @@ RSpec.describe Guardian do expect(Guardian.new(staged_user).can_see?(secure_category)).to be_falsey expect(Guardian.new(admin_user).can_see?(secure_category)).to be_truthy - secure_category = Fabricate(:category, read_restricted: true, email_in_allow_strangers: true) + secure_category = + Fabricate(:category, read_restricted: true, email_in_allow_strangers: true) expect(Guardian.new(normal_user).can_see?(secure_category)).to be_falsey expect(Guardian.new(staged_user).can_see?(secure_category)).to be_falsey expect(Guardian.new(admin_user).can_see?(secure_category)).to be_truthy - secure_category = Fabricate(:category, read_restricted: true, email_in: "foo2@bar.com", email_in_allow_strangers: true) + secure_category = + Fabricate( + :category, + read_restricted: true, + email_in: "foo2@bar.com", + email_in_allow_strangers: true, + ) expect(Guardian.new(normal_user).can_see?(secure_category)).to be_falsey expect(Guardian.new(staged_user).can_see?(secure_category)).to be_truthy expect(Guardian.new(admin_user).can_see?(secure_category)).to be_truthy end - it 'allows members of an authorized group' do + it "allows members of an authorized group" do secure_category = plain_category secure_category.set_permissions(group => :readonly) secure_category.save @@ -876,15 +876,14 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_see?(secure_category)).to be_truthy end - end - describe 'a Topic' do - it 'allows non logged in users to view topics' do + describe "a Topic" do + it "allows non logged in users to view topics" do expect(Guardian.new.can_see?(topic)).to be_truthy end - it 'correctly handles groups' do + it "correctly handles groups" do category = Fabricate(:category, read_restricted: true) category.set_permissions(group => :full) category.save @@ -954,7 +953,7 @@ RSpec.describe Guardian do expect(Guardian.new(admin).can_banner_topic?(topic)).to be_truthy end - it 'respects category group moderator settings' do + it "respects category group moderator settings" do group_user = Fabricate(:group_user) user_gm = group_user.user group = group_user.group @@ -974,8 +973,14 @@ RSpec.describe Guardian do expect(Guardian.new(user_gm).can_see?(topic)).to be_truthy end - it 'allows staged user to view own topic in restricted category when Category#email_in and Category#email_in_allow_strangers is set' do - secure_category = Fabricate(:category, read_restricted: true, email_in: "foo2@bar.com", email_in_allow_strangers: true) + it "allows staged user to view own topic in restricted category when Category#email_in and Category#email_in_allow_strangers is set" do + secure_category = + Fabricate( + :category, + read_restricted: true, + email_in: "foo2@bar.com", + email_in_allow_strangers: true, + ) topic_in_secure_category = Fabricate(:topic, category: secure_category, user: user) expect(Guardian.new(user).can_see?(topic_in_secure_category)).to eq(false) @@ -986,11 +991,11 @@ RSpec.describe Guardian do end end - describe 'a Post' do + describe "a Post" do fab!(:post) { Fabricate(:post) } fab!(:another_admin) { Fabricate(:admin) } - it 'correctly handles post visibility' do + it "correctly handles post visibility" do topic = post.topic expect(Guardian.new(user).can_see?(post)).to be_truthy @@ -1008,7 +1013,7 @@ RSpec.describe Guardian do expect(Guardian.new(admin).can_see?(post)).to be_truthy end - it 'respects category group moderator settings' do + it "respects category group moderator settings" do group_user = Fabricate(:group_user) user_gm = group_user.user group = group_user.group @@ -1025,7 +1030,7 @@ RSpec.describe Guardian do expect(Guardian.new(user_gm).can_see?(post)).to be_truthy end - it 'TL4 users can see their deleted posts' do + it "TL4 users can see their deleted posts" do user = Fabricate(:user, trust_level: 4) user2 = Fabricate(:user, trust_level: 4) post = Fabricate(:post, user: user, topic: Fabricate(:post).topic) @@ -1036,7 +1041,7 @@ RSpec.describe Guardian do expect(Guardian.new(user2).can_see?(post)).to eq(false) end - it 'respects whispers' do + it "respects whispers" do SiteSetting.whispers_allowed_groups = "#{Group::AUTO_GROUPS[:staff]}|#{group.id}" regular_post = post @@ -1069,34 +1074,34 @@ RSpec.describe Guardian do end end - describe 'a PostRevision' do + describe "a PostRevision" do fab!(:post_revision) { Fabricate(:post_revision) } - context 'when edit_history_visible_to_public is true' do + context "when edit_history_visible_to_public is true" do before { SiteSetting.edit_history_visible_to_public = true } - it 'is false for nil' do + it "is false for nil" do expect(Guardian.new.can_see?(nil)).to be_falsey end - it 'is true if not logged in' do + it "is true if not logged in" do expect(Guardian.new.can_see?(post_revision)).to be_truthy end - it 'is true when logged in' do + it "is true when logged in" do expect(Guardian.new(user).can_see?(post_revision)).to be_truthy end end - context 'when edit_history_visible_to_public is false' do + context "when edit_history_visible_to_public is false" do before { SiteSetting.edit_history_visible_to_public = false } - it 'is true for staff' do + it "is true for staff" do expect(Guardian.new(admin).can_see?(post_revision)).to be_truthy expect(Guardian.new(moderator).can_see?(post_revision)).to be_truthy end - it 'is false for trust level equal or lower than 4' do + it "is false for trust level equal or lower than 4" do expect(Guardian.new(trust_level_3).can_see?(post_revision)).to be_falsey expect(Guardian.new(trust_level_4).can_see?(post_revision)).to be_falsey end @@ -1104,29 +1109,27 @@ RSpec.describe Guardian do end end - describe 'can_create?' do - - describe 'a Category' do - - it 'returns false when not logged in' do + describe "can_create?" do + describe "a Category" do + it "returns false when not logged in" do expect(Guardian.new.can_create?(Category)).to be_falsey end - it 'returns false when a regular user' do + it "returns false when a regular user" do expect(Guardian.new(user).can_create?(Category)).to be_falsey end - it 'returns false when a moderator' do + it "returns false when a moderator" do expect(Guardian.new(moderator).can_create?(Category)).to be_falsey end - it 'returns true when an admin' do + it "returns true when an admin" do expect(Guardian.new(admin).can_create?(Category)).to be_truthy end end - describe 'a Topic' do - it 'does not allow moderators to create topics in readonly categories' do + describe "a Topic" do + it "does not allow moderators to create topics in readonly categories" do category = plain_category category.set_permissions(everyone: :read) category.save @@ -1134,7 +1137,7 @@ RSpec.describe Guardian do expect(Guardian.new(moderator).can_create?(Topic, category)).to be_falsey end - it 'should check for full permissions' do + it "should check for full permissions" do category = plain_category category.set_permissions(everyone: :create_post) category.save @@ -1147,20 +1150,29 @@ RSpec.describe Guardian do it "is false if user has not met minimum trust level" do SiteSetting.min_trust_to_create_topic = 1 - expect(Guardian.new(Fabricate(:user, trust_level: 0)).can_create?(Topic, plain_category)).to be_falsey + expect( + Guardian.new(Fabricate(:user, trust_level: 0)).can_create?(Topic, plain_category), + ).to be_falsey end it "is true if user has met or exceeded the minimum trust level" do SiteSetting.min_trust_to_create_topic = 1 - expect(Guardian.new(Fabricate(:user, trust_level: 1)).can_create?(Topic, plain_category)).to be_truthy - expect(Guardian.new(Fabricate(:user, trust_level: 2)).can_create?(Topic, plain_category)).to be_truthy - expect(Guardian.new(Fabricate(:admin, trust_level: 0)).can_create?(Topic, plain_category)).to be_truthy - expect(Guardian.new(Fabricate(:moderator, trust_level: 0)).can_create?(Topic, plain_category)).to be_truthy + expect( + Guardian.new(Fabricate(:user, trust_level: 1)).can_create?(Topic, plain_category), + ).to be_truthy + expect( + Guardian.new(Fabricate(:user, trust_level: 2)).can_create?(Topic, plain_category), + ).to be_truthy + expect( + Guardian.new(Fabricate(:admin, trust_level: 0)).can_create?(Topic, plain_category), + ).to be_truthy + expect( + Guardian.new(Fabricate(:moderator, trust_level: 0)).can_create?(Topic, plain_category), + ).to be_truthy end end - describe 'a Post' do - + describe "a Post" do it "is false on readonly categories" do category = plain_category topic.category = category @@ -1175,7 +1187,7 @@ RSpec.describe Guardian do expect(Guardian.new.can_create?(Post, topic)).to be_falsey end - it 'is true for a regular user' do + it "is true for a regular user" do expect(Guardian.new(topic.user).can_create?(Post, topic)).to be_truthy end @@ -1184,16 +1196,14 @@ RSpec.describe Guardian do expect(Guardian.new(topic.user).can_create?(Post, topic)).to be_falsey end - context 'with closed topic' do - before do - topic.closed = true - end + context "with closed topic" do + before { topic.closed = true } it "doesn't allow new posts from regular users" do expect(Guardian.new(topic.user).can_create?(Post, topic)).to be_falsey end - it 'allows editing of posts' do + it "allows editing of posts" do expect(Guardian.new(topic.user).can_edit?(post)).to be_truthy end @@ -1210,17 +1220,15 @@ RSpec.describe Guardian do end end - context 'with archived topic' do - before do - topic.archived = true - end + context "with archived topic" do + before { topic.archived = true } - context 'with regular users' do + context "with regular users" do it "doesn't allow new posts from regular users" do expect(Guardian.new(coding_horror).can_create?(Post, topic)).to be_falsey end - it 'does not allow editing of posts' do + it "does not allow editing of posts" do expect(Guardian.new(coding_horror).can_edit?(post)).to be_falsey end end @@ -1235,9 +1243,7 @@ RSpec.describe Guardian do end context "with trashed topic" do - before do - topic.trash!(admin) - end + before { topic.trash!(admin) } it "doesn't allow new posts from regular users" do expect(Guardian.new(coding_horror).can_create?(Post, topic)).to be_falsey @@ -1253,14 +1259,14 @@ RSpec.describe Guardian do end context "with system message" do - fab!(:private_message) { + fab!(:private_message) do Fabricate( :topic, archetype: Archetype.private_message, - subtype: 'system_message', - category_id: nil + subtype: "system_message", + category_id: nil, ) - } + end before { user.save! } it "allows the user to reply to system messages" do @@ -1268,11 +1274,12 @@ RSpec.describe Guardian do SiteSetting.enable_system_message_replies = false expect(Guardian.new(user).can_create_post?(private_message)).to eq(false) end - end context "with private message" do - fab!(:private_message) { Fabricate(:topic, archetype: Archetype.private_message, category_id: nil) } + fab!(:private_message) do + Fabricate(:topic, archetype: Archetype.private_message, category_id: nil) + end before { user.save! } @@ -1297,17 +1304,14 @@ RSpec.describe Guardian do end end end # can_create? a Post - end - describe 'post_can_act?' do - + describe "post_can_act?" do it "isn't allowed on nil" do expect(Guardian.new(user).post_can_act?(nil, nil)).to be_falsey end - describe 'a Post' do - + describe "a Post" do let (:guardian) do Guardian.new(user) end @@ -1343,8 +1347,8 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_recover_topic?(topic)).to be_falsey end - context 'as a moderator' do - describe 'when post has been deleted' do + context "as a moderator" do + describe "when post has been deleted" do it "should return the right value" do expect(Guardian.new(moderator).can_recover_topic?(topic)).to be_falsey @@ -1355,7 +1359,7 @@ RSpec.describe Guardian do end describe "when post's user has been deleted" do - it 'should return the right value' do + it "should return the right value" do PostDestroyer.new(moderator, topic.first_post).destroy topic.first_post.user.destroy! @@ -1364,7 +1368,7 @@ RSpec.describe Guardian do end end - context 'when category group moderation is enabled' do + context "when category group moderation is enabled" do fab!(:group_user) { Fabricate(:group_user) } before do @@ -1386,7 +1390,6 @@ RSpec.describe Guardian do end describe "can_recover_post?" do - it "returns false for a nil user" do expect(Guardian.new(nil).can_recover_post?(post)).to be_falsey end @@ -1399,11 +1402,11 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_recover_post?(post)).to be_falsey end - context 'as a moderator' do + context "as a moderator" do fab!(:topic) { Fabricate(:topic, user: user) } fab!(:post) { Fabricate(:post, user: user, topic: topic) } - describe 'when post has been deleted' do + describe "when post has been deleted" do it "should return the right value" do expect(Guardian.new(moderator).can_recover_post?(post)).to be_falsey @@ -1413,7 +1416,7 @@ RSpec.describe Guardian do end describe "when post's user has been deleted" do - it 'should return the right value' do + it "should return the right value" do PostDestroyer.new(moderator, post).destroy post.user.destroy! @@ -1422,69 +1425,66 @@ RSpec.describe Guardian do end end end - end - describe '#can_convert_topic?' do - it 'returns false with a nil object' do + describe "#can_convert_topic?" do + it "returns false with a nil object" do expect(Guardian.new(user).can_convert_topic?(nil)).to be_falsey end - it 'returns false when not logged in' do + it "returns false when not logged in" do expect(Guardian.new.can_convert_topic?(topic)).to be_falsey end - it 'returns false when not staff' do + it "returns false when not staff" do expect(Guardian.new(trust_level_4).can_convert_topic?(topic)).to be_falsey end - it 'returns false for category definition topics' do + it "returns false for category definition topics" do c = plain_category topic = Topic.find_by(id: c.topic_id) expect(Guardian.new(admin).can_convert_topic?(topic)).to be_falsey end - it 'returns true when a moderator' do + it "returns true when a moderator" do expect(Guardian.new(moderator).can_convert_topic?(topic)).to be_truthy end - it 'returns true when an admin' do + it "returns true when an admin" do expect(Guardian.new(admin).can_convert_topic?(topic)).to be_truthy end - it 'returns false when user is not in personal_message_enabled_groups' do + it "returns false when user is not in personal_message_enabled_groups" do SiteSetting.personal_message_enabled_groups = Group::AUTO_GROUPS[:trust_level_4] expect(Guardian.new(user).can_convert_topic?(topic)).to be_falsey end end - describe 'can_edit?' do - - it 'returns false with a nil object' do + describe "can_edit?" do + it "returns false with a nil object" do expect(Guardian.new(user).can_edit?(nil)).to be_falsey end - describe 'a Post' do - - it 'returns false for silenced users' do + describe "a Post" do + it "returns false for silenced users" do post.user.silenced_till = 1.day.from_now expect(Guardian.new(post.user).can_edit?(post)).to be_falsey end - it 'returns false when not logged in' do + it "returns false when not logged in" do expect(Guardian.new.can_edit?(post)).to be_falsey end - it 'returns false when not logged in also for wiki post' do + it "returns false when not logged in also for wiki post" do post.wiki = true expect(Guardian.new.can_edit?(post)).to be_falsey end - it 'returns true if you want to edit your own post' do + it "returns true if you want to edit your own post" do expect(Guardian.new(post.user).can_edit?(post)).to be_truthy end - it 'returns false if you try to edit a locked post' do + it "returns false if you try to edit a locked post" do post.locked_by_id = moderator.id expect(Guardian.new(post.user).can_edit?(post)).to be_falsey end @@ -1514,33 +1514,33 @@ RSpec.describe Guardian do expect(Guardian.new(post.user).can_edit?(post)).to be_truthy end - it 'returns false if you are trying to edit a post you soft deleted' do + it "returns false if you are trying to edit a post you soft deleted" do post.user_deleted = true expect(Guardian.new(post.user).can_edit?(post)).to be_falsey end - it 'returns false if another regular user tries to edit a soft deleted wiki post' do + it "returns false if another regular user tries to edit a soft deleted wiki post" do post.wiki = true post.user_deleted = true expect(Guardian.new(coding_horror).can_edit?(post)).to be_falsey end - it 'returns false if you are trying to edit a deleted post' do + it "returns false if you are trying to edit a deleted post" do post.deleted_at = 1.day.ago expect(Guardian.new(post.user).can_edit?(post)).to be_falsey end - it 'returns false if another regular user tries to edit a deleted wiki post' do + it "returns false if another regular user tries to edit a deleted wiki post" do post.wiki = true post.deleted_at = 1.day.ago expect(Guardian.new(coding_horror).can_edit?(post)).to be_falsey end - it 'returns false if another regular user tries to edit your post' do + it "returns false if another regular user tries to edit your post" do expect(Guardian.new(coding_horror).can_edit?(post)).to be_falsey end - it 'returns true if another regular user tries to edit wiki post' do + it "returns true if another regular user tries to edit wiki post" do post.wiki = true expect(Guardian.new(coding_horror).can_edit?(post)).to be_truthy end @@ -1557,50 +1557,50 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_edit?(post)).to eq(false) end - it 'returns true as a moderator' do + it "returns true as a moderator" do expect(Guardian.new(moderator).can_edit?(post)).to be_truthy end - it 'returns true as a moderator, even if locked' do + it "returns true as a moderator, even if locked" do post.locked_by_id = admin.id expect(Guardian.new(moderator).can_edit?(post)).to be_truthy end - it 'returns true as an admin' do + it "returns true as an admin" do expect(Guardian.new(admin).can_edit?(post)).to be_truthy end - it 'returns true as a trust level 4 user' do + it "returns true as a trust level 4 user" do expect(Guardian.new(trust_level_4).can_edit?(post)).to be_truthy end - it 'returns false as a TL4 user if trusted_users_can_edit_others is false' do + it "returns false as a TL4 user if trusted_users_can_edit_others is false" do SiteSetting.trusted_users_can_edit_others = false expect(Guardian.new(trust_level_4).can_edit?(post)).to eq(false) end - it 'returns false when trying to edit a topic with no trust' do + it "returns false when trying to edit a topic with no trust" do SiteSetting.min_trust_to_edit_post = 2 post.user.trust_level = 1 expect(Guardian.new(topic.user).can_edit?(topic)).to be_falsey end - it 'returns false when trying to edit a post with no trust' do + it "returns false when trying to edit a post with no trust" do SiteSetting.min_trust_to_edit_post = 2 post.user.trust_level = 1 expect(Guardian.new(post.user).can_edit?(post)).to be_falsey end - it 'returns true when trying to edit a post with trust' do + it "returns true when trying to edit a post with trust" do SiteSetting.min_trust_to_edit_post = 1 post.user.trust_level = 1 expect(Guardian.new(post.user).can_edit?(post)).to be_truthy end - it 'returns false when another user has too low trust level to edit wiki post' do + it "returns false when another user has too low trust level to edit wiki post" do SiteSetting.min_trust_to_edit_wiki_post = 2 post.wiki = true coding_horror.trust_level = 1 @@ -1608,7 +1608,7 @@ RSpec.describe Guardian do expect(Guardian.new(coding_horror).can_edit?(post)).to be_falsey end - it 'returns true when another user has adequate trust level to edit wiki post' do + it "returns true when another user has adequate trust level to edit wiki post" do SiteSetting.min_trust_to_edit_wiki_post = 2 post.wiki = true coding_horror.trust_level = 2 @@ -1616,7 +1616,7 @@ RSpec.describe Guardian do expect(Guardian.new(coding_horror).can_edit?(post)).to be_truthy end - it 'returns true for post author even when he has too low trust level to edit wiki post' do + it "returns true for post author even when he has too low trust level to edit wiki post" do SiteSetting.min_trust_to_edit_wiki_post = 2 post.wiki = true post.user.trust_level = 1 @@ -1632,26 +1632,26 @@ RSpec.describe Guardian do before do SiteSetting.shared_drafts_category = category.id - SiteSetting.shared_drafts_min_trust_level = '2' + SiteSetting.shared_drafts_min_trust_level = "2" Fabricate(:shared_draft, topic: topic) end - it 'returns true if a shared draft exists' do + it "returns true if a shared draft exists" do expect(Guardian.new(trust_level_2).can_edit_post?(post_with_draft)).to eq(true) end - it 'returns false if the user has a lower trust level' do + it "returns false if the user has a lower trust level" do expect(Guardian.new(trust_level_1).can_edit_post?(post_with_draft)).to eq(false) end - it 'returns false if the draft is from a different category' do + it "returns false if the draft is from a different category" do topic.update!(category: Fabricate(:category)) expect(Guardian.new(trust_level_2).can_edit_post?(post_with_draft)).to eq(false) end end - context 'when category group moderation is enabled' do + context "when category group moderation is enabled" do fab!(:cat_mod_user) { Fabricate(:user) } before do @@ -1660,43 +1660,44 @@ RSpec.describe Guardian do post.topic.category.update!(reviewable_by_group_id: group.id) end - it 'returns true as a category group moderator user' do + it "returns true as a category group moderator user" do expect(Guardian.new(cat_mod_user).can_edit?(post)).to eq(true) end - it 'returns false for a regular user' do + it "returns false for a regular user" do expect(Guardian.new(another_user).can_edit?(post)).to eq(false) end end - describe 'post edit time limits' do - - context 'when post is older than post_edit_time_limit' do + describe "post edit time limits" do + context "when post is older than post_edit_time_limit" do let(:topic) { Fabricate(:topic) } - let(:old_post) { Fabricate(:post, topic: topic, user: topic.user, created_at: 6.minutes.ago) } + let(:old_post) do + Fabricate(:post, topic: topic, user: topic.user, created_at: 6.minutes.ago) + end before do - topic.user.update_columns(trust_level: 1) + topic.user.update_columns(trust_level: 1) SiteSetting.post_edit_time_limit = 5 end - it 'returns false to the author of the post' do + it "returns false to the author of the post" do expect(Guardian.new(old_post.user).can_edit?(old_post)).to be_falsey end - it 'returns true as a moderator' do + it "returns true as a moderator" do expect(Guardian.new(moderator).can_edit?(old_post)).to eq(true) end - it 'returns true as an admin' do + it "returns true as an admin" do expect(Guardian.new(admin).can_edit?(old_post)).to eq(true) end - it 'returns false for another regular user trying to edit your post' do + it "returns false for another regular user trying to edit your post" do expect(Guardian.new(coding_horror).can_edit?(old_post)).to be_falsey end - it 'returns true for another regular user trying to edit a wiki post' do + it "returns true for another regular user trying to edit a wiki post" do old_post.wiki = true expect(Guardian.new(coding_horror).can_edit?(old_post)).to be_truthy end @@ -1716,7 +1717,8 @@ RSpec.describe Guardian do it "returns false when the post topic's category allow_unlimited_owner_edits_on_first_post but the post is not the first in the topic" do old_post.topic.category.update(allow_unlimited_owner_edits_on_first_post: true) - new_post = Fabricate(:post, user: owner, topic: old_post.topic, created_at: 6.minutes.ago) + new_post = + Fabricate(:post, user: owner, topic: old_post.topic, created_at: 6.minutes.ago) expect(Guardian.new(owner).can_edit?(new_post)).to be_falsey end @@ -1727,31 +1729,33 @@ RSpec.describe Guardian do end end - context 'when post is older than tl2_post_edit_time_limit' do - let(:old_post) { Fabricate(:post, topic: topic, user: topic.user, created_at: 12.minutes.ago) } + context "when post is older than tl2_post_edit_time_limit" do + let(:old_post) do + Fabricate(:post, topic: topic, user: topic.user, created_at: 12.minutes.ago) + end before do topic.user.update_columns(trust_level: 2) SiteSetting.tl2_post_edit_time_limit = 10 end - it 'returns false to the author of the post' do + it "returns false to the author of the post" do expect(Guardian.new(old_post.user).can_edit?(old_post)).to be_falsey end - it 'returns true as a moderator' do + it "returns true as a moderator" do expect(Guardian.new(moderator).can_edit?(old_post)).to eq(true) end - it 'returns true as an admin' do + it "returns true as an admin" do expect(Guardian.new(admin).can_edit?(old_post)).to eq(true) end - it 'returns false for another regular user trying to edit your post' do + it "returns false for another regular user trying to edit your post" do expect(Guardian.new(coding_horror).can_edit?(old_post)).to be_falsey end - it 'returns true for another regular user trying to edit a wiki post' do + it "returns true for another regular user trying to edit a wiki post" do old_post.wiki = true expect(Guardian.new(coding_horror).can_edit?(old_post)).to be_truthy end @@ -1771,25 +1775,26 @@ RSpec.describe Guardian do end end - describe 'a Topic' do - - it 'returns false when not logged in' do + describe "a Topic" do + it "returns false when not logged in" do expect(Guardian.new.can_edit?(topic)).to be_falsey end - it 'returns true for editing your own post' do + it "returns true for editing your own post" do expect(Guardian.new(topic.user).can_edit?(topic)).to eq(true) end - it 'returns false as a regular user' do + it "returns false as a regular user" do expect(Guardian.new(coding_horror).can_edit?(topic)).to be_falsey end - context 'when first post is hidden' do + context "when first post is hidden" do let!(:topic) { Fabricate(:topic, user: user) } - let!(:post) { Fabricate(:post, topic: topic, user: topic.user, hidden: true, hidden_at: Time.zone.now) } + let!(:post) do + Fabricate(:post, topic: topic, user: topic.user, hidden: true, hidden_at: Time.zone.now) + end - it 'returns false for editing your own post while inside the cooldown window' do + it "returns false for editing your own post while inside the cooldown window" do SiteSetting.cooldown_minutes_after_hiding_posts = 30 expect(Guardian.new(topic.user).can_edit?(topic)).to eq(false) @@ -1806,20 +1811,20 @@ RSpec.describe Guardian do end end - context 'when not archived' do - it 'returns true as a moderator' do + context "when not archived" do + it "returns true as a moderator" do expect(Guardian.new(moderator).can_edit?(topic)).to eq(true) end - it 'returns true as an admin' do + it "returns true as an admin" do expect(Guardian.new(admin).can_edit?(topic)).to eq(true) end - it 'returns true at trust level 3' do + it "returns true at trust level 3" do expect(Guardian.new(trust_level_3).can_edit?(topic)).to eq(true) end - it 'is false at TL3, if `trusted_users_can_edit_others` is false' do + it "is false at TL3, if `trusted_users_can_edit_others` is false" do SiteSetting.trusted_users_can_edit_others = false expect(Guardian.new(trust_level_3).can_edit?(topic)).to eq(false) end @@ -1846,150 +1851,148 @@ RSpec.describe Guardian do end end - context 'with private message' do + context "with private message" do fab!(:private_message) { Fabricate(:private_message_topic) } - it 'returns false at trust level 3' do + it "returns false at trust level 3" do expect(Guardian.new(trust_level_3).can_edit?(private_message)).to eq(false) end - it 'returns false at trust level 4' do + it "returns false at trust level 4" do expect(Guardian.new(trust_level_4).can_edit?(private_message)).to eq(false) end end - context 'when archived' do + context "when archived" do let(:archived_topic) { Fabricate(:topic, user: user, archived: true) } - it 'returns true as a moderator' do + it "returns true as a moderator" do expect(Guardian.new(moderator).can_edit?(archived_topic)).to be_truthy end - it 'returns true as an admin' do + it "returns true as an admin" do expect(Guardian.new(admin).can_edit?(archived_topic)).to be_truthy end - it 'returns true at trust level 4' do + it "returns true at trust level 4" do expect(Guardian.new(trust_level_4).can_edit?(archived_topic)).to be_truthy end - it 'is false at TL4, if `trusted_users_can_edit_others` is false' do + it "is false at TL4, if `trusted_users_can_edit_others` is false" do SiteSetting.trusted_users_can_edit_others = false expect(Guardian.new(trust_level_4).can_edit?(archived_topic)).to eq(false) end - it 'returns false at trust level 3' do + it "returns false at trust level 3" do expect(Guardian.new(trust_level_3).can_edit?(archived_topic)).to be_falsey end - it 'returns false as a topic creator' do + it "returns false as a topic creator" do expect(Guardian.new(user).can_edit?(archived_topic)).to be_falsey end end - context 'when very old' do + context "when very old" do let(:old_topic) { Fabricate(:topic, user: user, created_at: 6.minutes.ago) } before { SiteSetting.post_edit_time_limit = 5 } - it 'returns true as a moderator' do + it "returns true as a moderator" do expect(Guardian.new(moderator).can_edit?(old_topic)).to be_truthy end - it 'returns true as an admin' do + it "returns true as an admin" do expect(Guardian.new(admin).can_edit?(old_topic)).to be_truthy end - it 'returns true at trust level 3' do + it "returns true at trust level 3" do expect(Guardian.new(trust_level_3).can_edit?(old_topic)).to be_truthy end - it 'returns false as a topic creator' do + it "returns false as a topic creator" do expect(Guardian.new(user).can_edit?(old_topic)).to be_falsey end end end - describe 'a Category' do - it 'returns false when not logged in' do + describe "a Category" do + it "returns false when not logged in" do expect(Guardian.new.can_edit?(plain_category)).to be_falsey end - it 'returns false as a regular user' do + it "returns false as a regular user" do expect(Guardian.new(plain_category.user).can_edit?(plain_category)).to be_falsey end - it 'returns false as a moderator' do + it "returns false as a moderator" do expect(Guardian.new(moderator).can_edit?(plain_category)).to be_falsey end - it 'returns true as an admin' do + it "returns true as an admin" do expect(Guardian.new(admin).can_edit?(plain_category)).to be_truthy end end - describe 'a User' do - - it 'returns false when not logged in' do + describe "a User" do + it "returns false when not logged in" do expect(Guardian.new.can_edit?(user)).to be_falsey end - it 'returns false as a different user' do + it "returns false as a different user" do expect(Guardian.new(coding_horror).can_edit?(user)).to be_falsey end - it 'returns true when trying to edit yourself' do + it "returns true when trying to edit yourself" do expect(Guardian.new(user).can_edit?(user)).to be_truthy end - it 'returns true as a moderator' do + it "returns true as a moderator" do expect(Guardian.new(moderator).can_edit?(user)).to be_truthy end - it 'returns true as an admin' do + it "returns true as an admin" do expect(Guardian.new(admin).can_edit?(user)).to be_truthy end end - end - describe '#can_moderate?' do - it 'returns false with a nil object' do + describe "#can_moderate?" do + it "returns false with a nil object" do expect(Guardian.new(user).can_moderate?(nil)).to be_falsey end - context 'when user is silenced' do - it 'returns false' do + context "when user is silenced" do + it "returns false" do user.update_column(:silenced_till, 1.year.from_now) expect(Guardian.new(user).can_moderate?(post)).to be(false) expect(Guardian.new(user).can_moderate?(topic)).to be(false) end end - context 'with a Topic' do - it 'returns false when not logged in' do + context "with a Topic" do + it "returns false when not logged in" do expect(Guardian.new.can_moderate?(topic)).to be_falsey end - it 'returns false when not a moderator' do + it "returns false when not a moderator" do expect(Guardian.new(user).can_moderate?(topic)).to be_falsey end - it 'returns true when a moderator' do + it "returns true when a moderator" do expect(Guardian.new(moderator).can_moderate?(topic)).to be_truthy end - it 'returns true when an admin' do + it "returns true when an admin" do expect(Guardian.new(admin).can_moderate?(topic)).to be_truthy end - it 'returns true when trust level 4' do + it "returns true when trust level 4" do expect(Guardian.new(trust_level_4).can_moderate?(topic)).to be_truthy end end end - describe '#can_see_flags?' do + describe "#can_see_flags?" do it "returns false when there is no post" do expect(Guardian.new(moderator).can_see_flags?(nil)).to be_falsey end @@ -2012,19 +2015,19 @@ RSpec.describe Guardian do end describe "#can_review_topic?" do - it 'returns false with a nil object' do + it "returns false with a nil object" do expect(Guardian.new(user).can_review_topic?(nil)).to eq(false) end - it 'returns true for a staff user' do + it "returns true for a staff user" do expect(Guardian.new(moderator).can_review_topic?(topic)).to eq(true) end - it 'returns false for a regular user' do + it "returns false for a regular user" do expect(Guardian.new(user).can_review_topic?(topic)).to eq(false) end - it 'returns true for a group member with reviewable status' do + it "returns true for a group member with reviewable status" do SiteSetting.enable_category_group_moderation = true GroupUser.create!(group_id: group.id, user_id: user.id) topic.category.update!(reviewable_by_group_id: group.id) @@ -2033,19 +2036,19 @@ RSpec.describe Guardian do end describe "#can_close_topic?" do - it 'returns false with a nil object' do + it "returns false with a nil object" do expect(Guardian.new(user).can_close_topic?(nil)).to eq(false) end - it 'returns true for a staff user' do + it "returns true for a staff user" do expect(Guardian.new(moderator).can_close_topic?(topic)).to eq(true) end - it 'returns false for a regular user' do + it "returns false for a regular user" do expect(Guardian.new(user).can_close_topic?(topic)).to eq(false) end - it 'returns true for a group member with reviewable status' do + it "returns true for a group member with reviewable status" do SiteSetting.enable_category_group_moderation = true GroupUser.create!(group_id: group.id, user_id: user.id) topic.category.update!(reviewable_by_group_id: group.id) @@ -2054,19 +2057,19 @@ RSpec.describe Guardian do end describe "#can_archive_topic?" do - it 'returns false with a nil object' do + it "returns false with a nil object" do expect(Guardian.new(user).can_archive_topic?(nil)).to eq(false) end - it 'returns true for a staff user' do + it "returns true for a staff user" do expect(Guardian.new(moderator).can_archive_topic?(topic)).to eq(true) end - it 'returns false for a regular user' do + it "returns false for a regular user" do expect(Guardian.new(user).can_archive_topic?(topic)).to eq(false) end - it 'returns true for a group member with reviewable status' do + it "returns true for a group member with reviewable status" do SiteSetting.enable_category_group_moderation = true GroupUser.create!(group_id: group.id, user_id: user.id) topic.category.update!(reviewable_by_group_id: group.id) @@ -2075,19 +2078,19 @@ RSpec.describe Guardian do end describe "#can_edit_staff_notes?" do - it 'returns false with a nil object' do + it "returns false with a nil object" do expect(Guardian.new(user).can_edit_staff_notes?(nil)).to eq(false) end - it 'returns true for a staff user' do + it "returns true for a staff user" do expect(Guardian.new(moderator).can_edit_staff_notes?(topic)).to eq(true) end - it 'returns false for a regular user' do + it "returns false for a regular user" do expect(Guardian.new(user).can_edit_staff_notes?(topic)).to eq(false) end - it 'returns true for a group member with reviewable status' do + it "returns true for a group member with reviewable status" do SiteSetting.enable_category_group_moderation = true GroupUser.create!(group_id: group.id, user_id: user.id) topic.category.update!(reviewable_by_group_id: group.id) @@ -2096,16 +2099,16 @@ RSpec.describe Guardian do end describe "#can_create_topic?" do - it 'returns true for staff user' do + it "returns true for staff user" do expect(Guardian.new(moderator).can_create_topic?(topic)).to eq(true) end - it 'returns false for user with insufficient trust level' do + it "returns false for user with insufficient trust level" do SiteSetting.min_trust_to_create_topic = 3 expect(Guardian.new(user).can_create_topic?(topic)).to eq(false) end - it 'returns true for user with sufficient trust level' do + it "returns true for user with sufficient trust level" do SiteSetting.min_trust_to_create_topic = 3 expect(Guardian.new(trust_level_4).can_create_topic?(topic)).to eq(true) end @@ -2118,7 +2121,7 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_create_topic?(topic)).to eq(false) end - it 'returns true when there is a category available for posting' do + it "returns true when there is a category available for posting" do SiteSetting.allow_uncategorized_topics = false plain_category.set_permissions(group => :full) @@ -2129,53 +2132,53 @@ RSpec.describe Guardian do end end - describe '#can_move_posts?' do - it 'returns false with a nil object' do + describe "#can_move_posts?" do + it "returns false with a nil object" do expect(Guardian.new(user).can_move_posts?(nil)).to be_falsey end - context 'with a Topic' do - it 'returns false when not logged in' do + context "with a Topic" do + it "returns false when not logged in" do expect(Guardian.new.can_move_posts?(topic)).to be_falsey end - it 'returns false when not a moderator' do + it "returns false when not a moderator" do expect(Guardian.new(user).can_move_posts?(topic)).to be_falsey end - it 'returns true when a moderator' do + it "returns true when a moderator" do expect(Guardian.new(moderator).can_move_posts?(topic)).to be_truthy end - it 'returns true when an admin' do + it "returns true when an admin" do expect(Guardian.new(admin).can_move_posts?(topic)).to be_truthy end end end - describe '#can_delete?' do - it 'returns false with a nil object' do + describe "#can_delete?" do + it "returns false with a nil object" do expect(Guardian.new(user).can_delete?(nil)).to be_falsey end - context 'with a Topic' do - it 'returns false when not logged in' do + context "with a Topic" do + it "returns false when not logged in" do expect(Guardian.new.can_delete?(topic)).to be_falsey end - it 'returns false when not a moderator' do + it "returns false when not a moderator" do expect(Guardian.new(Fabricate(:user)).can_delete?(topic)).to be_falsey end - it 'returns true when a moderator' do + it "returns true when a moderator" do expect(Guardian.new(moderator).can_delete?(topic)).to be_truthy end - it 'returns true when an admin' do + it "returns true when an admin" do expect(Guardian.new(admin).can_delete?(topic)).to be_truthy end - it 'returns false for static doc topics' do + it "returns false for static doc topics" do tos_topic = Fabricate(:topic, user: Discourse.system_user) SiteSetting.tos_topic_id = tos_topic.id expect(Guardian.new(admin).can_delete?(tos_topic)).to be_falsey @@ -2197,12 +2200,10 @@ RSpec.describe Guardian do expect(Guardian.new(topic.user).can_delete?(topic)).to be_falsey end - context 'when category group moderation is enabled' do + context "when category group moderation is enabled" do fab!(:group_user) { Fabricate(:group_user) } - before do - SiteSetting.enable_category_group_moderation = true - end + before { SiteSetting.enable_category_group_moderation = true } it "returns false if user is not a member of the appropriate group" do expect(Guardian.new(group_user.user).can_delete?(topic)).to be_falsey @@ -2216,12 +2217,10 @@ RSpec.describe Guardian do end end - context 'with a Post' do - before do - post.post_number = 2 - end + context "with a Post" do + before { post.post_number = 2 } - it 'returns false when not logged in' do + it "returns false when not logged in" do expect(Guardian.new.can_delete?(post)).to be_falsey end @@ -2232,7 +2231,7 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_delete?(post)).to be_falsey end - it 'returns true when trying to delete your own post' do + it "returns true when trying to delete your own post" do expect(Guardian.new(user).can_delete?(post)).to be_truthy expect(Guardian.new(trust_level_0).can_delete?(post)).to be_falsey @@ -2242,7 +2241,7 @@ RSpec.describe Guardian do expect(Guardian.new(trust_level_4).can_delete?(post)).to be_falsey end - it 'returns false when self deletions are disabled' do + it "returns false when self deletions are disabled" do SiteSetting.max_post_deletions_per_day = 0 expect(Guardian.new(user).can_delete?(post)).to be_falsey end @@ -2257,11 +2256,11 @@ RSpec.describe Guardian do expect(Guardian.new(moderator).can_delete?(post)).to be_falsey end - it 'returns true when a moderator' do + it "returns true when a moderator" do expect(Guardian.new(moderator).can_delete?(post)).to be_truthy end - it 'returns true when an admin' do + it "returns true when an admin" do expect(Guardian.new(admin).can_delete?(post)).to be_truthy end @@ -2274,7 +2273,7 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_delete?(post)).to eq(true) end - it 'returns false when post is first in a static doc topic' do + it "returns false when post is first in a static doc topic" do tos_topic = Fabricate(:topic, user: Discourse.system_user) SiteSetting.tos_topic_id = tos_topic.id post.update_attribute :post_number, 1 @@ -2282,10 +2281,8 @@ RSpec.describe Guardian do expect(Guardian.new(admin).can_delete?(post)).to be_falsey end - context 'when the topic is archived' do - before do - post.topic.archived = true - end + context "when the topic is archived" do + before { post.topic.archived = true } it "allows a staff member to delete it" do expect(Guardian.new(moderator).can_delete?(post)).to be_truthy @@ -2297,22 +2294,22 @@ RSpec.describe Guardian do end end - context 'with a Category' do + context "with a Category" do let(:category) { Fabricate(:category, user: moderator) } - it 'returns false when not logged in' do + it "returns false when not logged in" do expect(Guardian.new.can_delete?(category)).to be_falsey end - it 'returns false when a regular user' do + it "returns false when a regular user" do expect(Guardian.new(user).can_delete?(category)).to be_falsey end - it 'returns false when a moderator' do + it "returns false when a moderator" do expect(Guardian.new(moderator).can_delete?(category)).to be_falsey end - it 'returns true when an admin' do + it "returns true when an admin" do expect(Guardian.new(admin).can_delete?(category)).to be_truthy end @@ -2333,39 +2330,39 @@ RSpec.describe Guardian do end end - describe '#can_suspend?' do - it 'returns false when a user tries to suspend another user' do + describe "#can_suspend?" do + it "returns false when a user tries to suspend another user" do expect(Guardian.new(user).can_suspend?(coding_horror)).to be_falsey end - it 'returns true when an admin tries to suspend another user' do + it "returns true when an admin tries to suspend another user" do expect(Guardian.new(admin).can_suspend?(coding_horror)).to be_truthy end - it 'returns true when a moderator tries to suspend another user' do + it "returns true when a moderator tries to suspend another user" do expect(Guardian.new(moderator).can_suspend?(coding_horror)).to be_truthy end - it 'returns false when staff tries to suspend staff' do + it "returns false when staff tries to suspend staff" do expect(Guardian.new(admin).can_suspend?(moderator)).to be_falsey end end - context 'with a PostAction' do - let(:post_action) { + context "with a PostAction" do + let(:post_action) do user.id = 1 post.id = 1 a = PostAction.new(user: user, post: post, post_action_type_id: 2) a.created_at = 1.minute.ago a - } + end - it 'returns false when not logged in' do + it "returns false when not logged in" do expect(Guardian.new.can_delete?(post_action)).to be_falsey end - it 'returns false when not the user who created it' do + it "returns false when not the user who created it" do expect(Guardian.new(coding_horror).can_delete?(post_action)).to be_falsey end @@ -2387,7 +2384,7 @@ RSpec.describe Guardian do end end - describe '#can_approve?' do + describe "#can_approve?" do it "wont allow a non-logged in user to approve" do expect(Guardian.new.can_approve?(user)).to be_falsey end @@ -2415,7 +2412,7 @@ RSpec.describe Guardian do end end - describe '#can_grant_admin?' do + describe "#can_grant_admin?" do it "wont allow a non logged in user to grant an admin's access" do expect(Guardian.new.can_grant_admin?(another_admin)).to be_falsey end @@ -2424,7 +2421,7 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_grant_admin?(another_admin)).to be_falsey end - it 'wont allow an admin to grant their own access' do + it "wont allow an admin to grant their own access" do expect(Guardian.new(admin).can_grant_admin?(admin)).to be_falsey end @@ -2434,7 +2431,7 @@ RSpec.describe Guardian do expect(Guardian.new(admin).can_grant_admin?(user)).to be_truthy end - it 'should not allow an admin to grant admin access to a non real user' do + it "should not allow an admin to grant admin access to a non real user" do begin Discourse.system_user.update!(admin: false) expect(Guardian.new(admin).can_grant_admin?(Discourse.system_user)).to be(false) @@ -2444,7 +2441,7 @@ RSpec.describe Guardian do end end - describe '#can_revoke_admin?' do + describe "#can_revoke_admin?" do it "wont allow a non logged in user to revoke an admin's access" do expect(Guardian.new.can_revoke_admin?(another_admin)).to be_falsey end @@ -2453,7 +2450,7 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_revoke_admin?(another_admin)).to be_falsey end - it 'wont allow an admin to revoke their own access' do + it "wont allow an admin to revoke their own access" do expect(Guardian.new(admin).can_revoke_admin?(admin)).to be_falsey end @@ -2469,7 +2466,7 @@ RSpec.describe Guardian do end end - describe '#can_grant_moderation?' do + describe "#can_grant_moderation?" do it "wont allow a non logged in user to grant an moderator's access" do expect(Guardian.new.can_grant_moderation?(user)).to be_falsey end @@ -2478,11 +2475,11 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_grant_moderation?(moderator)).to be_falsey end - it 'will allow an admin to grant their own moderator access' do + it "will allow an admin to grant their own moderator access" do expect(Guardian.new(admin).can_grant_moderation?(admin)).to be_truthy end - it 'wont allow an admin to grant it to an already moderator' do + it "wont allow an admin to grant it to an already moderator" do expect(Guardian.new(admin).can_grant_moderation?(moderator)).to be_falsey end @@ -2500,7 +2497,7 @@ RSpec.describe Guardian do end end - describe '#can_revoke_moderation?' do + describe "#can_revoke_moderation?" do it "wont allow a non logged in user to revoke an moderator's access" do expect(Guardian.new.can_revoke_moderation?(moderator)).to be_falsey end @@ -2509,7 +2506,7 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_revoke_moderation?(moderator)).to be_falsey end - it 'wont allow a moderator to revoke their own moderator' do + it "wont allow a moderator to revoke their own moderator" do expect(Guardian.new(moderator).can_revoke_moderation?(moderator)).to be_falsey end @@ -2532,15 +2529,15 @@ RSpec.describe Guardian do end describe "#can_see_invite_details?" do - it 'is false without a logged in user' do + it "is false without a logged in user" do expect(Guardian.new(nil).can_see_invite_details?(user)).to be_falsey end - it 'is false without a user to look at' do + it "is false without a user to look at" do expect(Guardian.new(user).can_see_invite_details?(nil)).to be_falsey end - it 'is true when looking at your own invites' do + it "is true when looking at your own invites" do expect(Guardian.new(user).can_see_invite_details?(user)).to be_truthy end end @@ -2549,9 +2546,7 @@ RSpec.describe Guardian do let(:unapproved_user) { Fabricate(:user) } context "when must_approve_users is false" do - before do - SiteSetting.must_approve_users = false - end + before { SiteSetting.must_approve_users = false } it "returns true for a nil user" do expect(Guardian.new(nil).can_access_forum?).to be_truthy @@ -2562,10 +2557,8 @@ RSpec.describe Guardian do end end - context 'when must_approve_users is true' do - before do - SiteSetting.must_approve_users = true - end + context "when must_approve_users is true" do + before { SiteSetting.must_approve_users = true } it "returns false for a nil user" do expect(Guardian.new(nil).can_access_forum?).to be_falsey @@ -2605,7 +2598,9 @@ RSpec.describe Guardian do it "is true if user has no posts" do SiteSetting.delete_user_max_post_age = 10 - expect(Guardian.new(actor).can_delete_all_posts?(Fabricate(:user, created_at: 100.days.ago))).to be_truthy + expect( + Guardian.new(actor).can_delete_all_posts?(Fabricate(:user, created_at: 100.days.ago)), + ).to be_truthy end it "is true if user's first post is newer than delete_user_max_post_age days old" do @@ -2646,7 +2641,9 @@ RSpec.describe Guardian do it "is true if user has no posts" do SiteSetting.delete_user_max_post_age = 10 - expect(Guardian.new(actor).can_delete_all_posts?(Fabricate(:user, created_at: 100.days.ago))).to be_truthy + expect( + Guardian.new(actor).can_delete_all_posts?(Fabricate(:user, created_at: 100.days.ago)), + ).to be_truthy end it "is true if user's first post is newer than delete_user_max_post_age days old" do @@ -2725,33 +2722,33 @@ RSpec.describe Guardian do end end - describe 'can_grant_title?' do - it 'is false without a logged in user' do + describe "can_grant_title?" do + it "is false without a logged in user" do expect(Guardian.new(nil).can_grant_title?(user)).to be_falsey end - it 'is false for regular users' do + it "is false for regular users" do expect(Guardian.new(user).can_grant_title?(user)).to be_falsey end - it 'is true for moderators' do + it "is true for moderators" do expect(Guardian.new(moderator).can_grant_title?(user)).to be_truthy end - it 'is true for admins' do + it "is true for admins" do expect(Guardian.new(admin).can_grant_title?(user)).to be_truthy end - it 'is false without a user to look at' do + it "is false without a user to look at" do expect(Guardian.new(admin).can_grant_title?(nil)).to be_falsey end - context 'with title argument' do - fab!(:title_badge) { Fabricate(:badge, name: 'Helper', allow_title: true) } - fab!(:no_title_badge) { Fabricate(:badge, name: 'Writer', allow_title: false) } - fab!(:group) { Fabricate(:group, title: 'Groupie') } + context "with title argument" do + fab!(:title_badge) { Fabricate(:badge, name: "Helper", allow_title: true) } + fab!(:no_title_badge) { Fabricate(:badge, name: "Writer", allow_title: false) } + fab!(:group) { Fabricate(:group, title: "Groupie") } - it 'returns true if title belongs to a badge that user has' do + it "returns true if title belongs to a badge that user has" do BadgeGranter.grant(title_badge, user) expect(Guardian.new(user).can_grant_title?(user, title_badge.name)).to eq(true) end @@ -2765,7 +2762,7 @@ RSpec.describe Guardian do expect(Guardian.new(user).can_grant_title?(user, no_title_badge.name)).to eq(false) end - it 'returns true if title is from a group the user belongs to' do + it "returns true if title is from a group the user belongs to" do group.add(user) expect(Guardian.new(user).can_grant_title?(user, group.title)).to eq(true) end @@ -2775,66 +2772,66 @@ RSpec.describe Guardian do end it "returns true if the title is set to an empty string" do - expect(Guardian.new(user).can_grant_title?(user, '')).to eq(true) + expect(Guardian.new(user).can_grant_title?(user, "")).to eq(true) end end end - describe 'can_use_primary_group?' do - fab!(:group) { Fabricate(:group, title: 'Groupie') } + describe "can_use_primary_group?" do + fab!(:group) { Fabricate(:group, title: "Groupie") } - it 'is false without a logged in user' do + it "is false without a logged in user" do expect(Guardian.new(nil).can_use_primary_group?(user)).to be_falsey end - it 'is false with no group_id' do + it "is false with no group_id" do user.update(groups: [group]) expect(Guardian.new(user).can_use_primary_group?(user, nil)).to be_falsey end - it 'is false if the group does not exist' do + it "is false if the group does not exist" do user.update(groups: [group]) expect(Guardian.new(user).can_use_primary_group?(user, Group.last.id + 1)).to be_falsey end - it 'is false if the user is not a part of the group' do + it "is false if the user is not a part of the group" do user.update(groups: []) expect(Guardian.new(user).can_use_primary_group?(user, group.id)).to be_falsey end - it 'is false if the group is automatic' do - user.update(groups: [Group.new(name: 'autooo', automatic: true)]) + it "is false if the group is automatic" do + user.update(groups: [Group.new(name: "autooo", automatic: true)]) expect(Guardian.new(user).can_use_primary_group?(user, group.id)).to be_falsey end - it 'is true if the user is a part of the group, and the group is custom' do + it "is true if the user is a part of the group, and the group is custom" do user.update(groups: [group]) expect(Guardian.new(user).can_use_primary_group?(user, group.id)).to be_truthy end end - describe 'can_use_flair_group?' do - fab!(:group) { Fabricate(:group, title: 'Groupie', flair_icon: 'icon') } + describe "can_use_flair_group?" do + fab!(:group) { Fabricate(:group, title: "Groupie", flair_icon: "icon") } - it 'is false without a logged in user' do + it "is false without a logged in user" do expect(Guardian.new(nil).can_use_flair_group?(user)).to eq(false) end - it 'is false if the group does not exist' do + it "is false if the group does not exist" do expect(Guardian.new(user).can_use_flair_group?(user, nil)).to eq(false) expect(Guardian.new(user).can_use_flair_group?(user, Group.last.id + 1)).to eq(false) end - it 'is false if the user is not a part of the group' do + it "is false if the user is not a part of the group" do expect(Guardian.new(user).can_use_flair_group?(user, group.id)).to eq(false) end - it 'is false if the group does not have a flair' do + it "is false if the group does not have a flair" do group.update(flair_icon: nil) expect(Guardian.new(user).can_use_flair_group?(user, group.id)).to eq(false) end - it 'is true if the user is a part of the group and the group has a flair' do + it "is true if the user is a part of the group and the group has a flair" do user.update(groups: [group]) expect(Guardian.new(user).can_use_flair_group?(user, group.id)).to eq(true) end @@ -2854,9 +2851,7 @@ RSpec.describe Guardian do end context "when moderators_manage_categories_and_groups site setting is enabled" do - before do - SiteSetting.moderators_manage_categories_and_groups = true - end + before { SiteSetting.moderators_manage_categories_and_groups = true } it "returns true for moderators" do expect(Guardian.new(moderator).can_change_primary_group?(user, group)).to eq(true) @@ -2864,9 +2859,7 @@ RSpec.describe Guardian do end context "when moderators_manage_categories_and_groups site setting is disabled" do - before do - SiteSetting.moderators_manage_categories_and_groups = false - end + before { SiteSetting.moderators_manage_categories_and_groups = false } it "returns false for moderators" do expect(Guardian.new(moderator).can_change_primary_group?(user, group)).to eq(false) @@ -2874,32 +2867,37 @@ RSpec.describe Guardian do end end - describe 'can_change_trust_level?' do - - it 'is false without a logged in user' do + describe "can_change_trust_level?" do + it "is false without a logged in user" do expect(Guardian.new(nil).can_change_trust_level?(user)).to be_falsey end - it 'is false for regular users' do + it "is false for regular users" do expect(Guardian.new(user).can_change_trust_level?(user)).to be_falsey end - it 'is true for moderators' do + it "is true for moderators" do expect(Guardian.new(moderator).can_change_trust_level?(user)).to be_truthy end - it 'is true for admins' do + it "is true for admins" do expect(Guardian.new(admin).can_change_trust_level?(user)).to be_truthy end end describe "can_edit_username?" do it "is false without a logged in user" do - expect(Guardian.new(nil).can_edit_username?(Fabricate(:user, created_at: 1.minute.ago))).to be_falsey + expect( + Guardian.new(nil).can_edit_username?(Fabricate(:user, created_at: 1.minute.ago)), + ).to be_falsey end it "is false for regular users to edit another user's username" do - expect(Guardian.new(Fabricate(:user)).can_edit_username?(Fabricate(:user, created_at: 1.minute.ago))).to be_falsey + expect( + Guardian.new(Fabricate(:user)).can_edit_username?( + Fabricate(:user, created_at: 1.minute.ago), + ), + ).to be_falsey end shared_examples "staff can always change usernames" do @@ -2917,16 +2915,14 @@ RSpec.describe Guardian do end context "for anonymous user" do - before do - SiteSetting.allow_anonymous_posting = true - end + before { SiteSetting.allow_anonymous_posting = true } it "is false" do expect(Guardian.new(anonymous_user).can_edit_username?(anonymous_user)).to be_falsey end end - context 'for a new user' do + context "for a new user" do fab!(:target_user) { Fabricate(:user, created_at: 1.minute.ago) } include_examples "staff can always change usernames" @@ -2935,21 +2931,19 @@ RSpec.describe Guardian do end end - context 'for an old user' do - before do - SiteSetting.username_change_period = 3 - end + context "for an old user" do + before { SiteSetting.username_change_period = 3 } let(:target_user) { Fabricate(:user, created_at: 4.days.ago) } - context 'with no posts' do + context "with no posts" do include_examples "staff can always change usernames" it "is false for the user to change their own username" do expect(Guardian.new(target_user).can_edit_username?(target_user)).to be_falsey end end - context 'with posts' do + context "with posts" do before { target_user.stubs(:post_count).returns(1) } include_examples "staff can always change usernames" it "is false for the user to change their own username" do @@ -2958,10 +2952,8 @@ RSpec.describe Guardian do end end - context 'when editing is disabled in preferences' do - before do - SiteSetting.username_change_period = 0 - end + context "when editing is disabled in preferences" do + before { SiteSetting.username_change_period = 0 } include_examples "staff can always change usernames" @@ -2970,7 +2962,7 @@ RSpec.describe Guardian do end end - context 'when SSO username override is active' do + context "when SSO username override is active" do before do SiteSetting.discourse_connect_url = "https://www.example.com/sso" SiteSetting.enable_discourse_connect = true @@ -2992,15 +2984,11 @@ RSpec.describe Guardian do end describe "can_edit_email?" do - context 'when allowed in settings' do - before do - SiteSetting.email_editable = true - end + context "when allowed in settings" do + before { SiteSetting.email_editable = true } context "for anonymous user" do - before do - SiteSetting.allow_anonymous_posting = true - end + before { SiteSetting.allow_anonymous_posting = true } it "is false" do expect(Guardian.new(anonymous_user).can_edit_email?(anonymous_user)).to be_falsey @@ -3008,11 +2996,17 @@ RSpec.describe Guardian do end it "is false when not logged in" do - expect(Guardian.new(nil).can_edit_email?(Fabricate(:user, created_at: 1.minute.ago))).to be_falsey + expect( + Guardian.new(nil).can_edit_email?(Fabricate(:user, created_at: 1.minute.ago)), + ).to be_falsey end it "is false for regular users to edit another user's email" do - expect(Guardian.new(Fabricate(:user)).can_edit_email?(Fabricate(:user, created_at: 1.minute.ago))).to be_falsey + expect( + Guardian.new(Fabricate(:user)).can_edit_email?( + Fabricate(:user, created_at: 1.minute.ago), + ), + ).to be_falsey end it "is true for a regular user to edit their own email" do @@ -3028,17 +3022,21 @@ RSpec.describe Guardian do end end - context 'when not allowed in settings' do - before do - SiteSetting.email_editable = false - end + context "when not allowed in settings" do + before { SiteSetting.email_editable = false } it "is false when not logged in" do - expect(Guardian.new(nil).can_edit_email?(Fabricate(:user, created_at: 1.minute.ago))).to be_falsey + expect( + Guardian.new(nil).can_edit_email?(Fabricate(:user, created_at: 1.minute.ago)), + ).to be_falsey end it "is false for regular users to edit another user's email" do - expect(Guardian.new(Fabricate(:user)).can_edit_email?(Fabricate(:user, created_at: 1.minute.ago))).to be_falsey + expect( + Guardian.new(Fabricate(:user)).can_edit_email?( + Fabricate(:user, created_at: 1.minute.ago), + ), + ).to be_falsey end it "is false for a regular user to edit their own email" do @@ -3054,7 +3052,7 @@ RSpec.describe Guardian do end end - context 'when SSO email override is active' do + context "when SSO email override is active" do before do SiteSetting.email_editable = false SiteSetting.discourse_connect_url = "https://www.example.com/sso" @@ -3076,121 +3074,115 @@ RSpec.describe Guardian do end end - describe 'can_edit_name?' do - it 'is false without a logged in user' do - expect(Guardian.new(nil).can_edit_name?(Fabricate(:user, created_at: 1.minute.ago))).to be_falsey + describe "can_edit_name?" do + it "is false without a logged in user" do + expect( + Guardian.new(nil).can_edit_name?(Fabricate(:user, created_at: 1.minute.ago)), + ).to be_falsey end it "is false for regular users to edit another user's name" do - expect(Guardian.new(Fabricate(:user)).can_edit_name?(Fabricate(:user, created_at: 1.minute.ago))).to be_falsey + expect( + Guardian.new(Fabricate(:user)).can_edit_name?(Fabricate(:user, created_at: 1.minute.ago)), + ).to be_falsey end context "for anonymous user" do - before do - SiteSetting.allow_anonymous_posting = true - end + before { SiteSetting.allow_anonymous_posting = true } it "is false" do expect(Guardian.new(anonymous_user).can_edit_name?(anonymous_user)).to be_falsey end end - context 'for a new user' do + context "for a new user" do let(:target_user) { Fabricate(:user, created_at: 1.minute.ago) } - it 'is true for the user to change their own name' do + it "is true for the user to change their own name" do expect(Guardian.new(target_user).can_edit_name?(target_user)).to be_truthy end - it 'is true for moderators' do + it "is true for moderators" do expect(Guardian.new(moderator).can_edit_name?(user)).to be_truthy end - it 'is true for admins' do + it "is true for admins" do expect(Guardian.new(admin).can_edit_name?(user)).to be_truthy end end - context 'when name is disabled in preferences' do - before do - SiteSetting.enable_names = false - end + context "when name is disabled in preferences" do + before { SiteSetting.enable_names = false } - it 'is false for the user to change their own name' do + it "is false for the user to change their own name" do expect(Guardian.new(user).can_edit_name?(user)).to be_falsey end - it 'is false for moderators' do + it "is false for moderators" do expect(Guardian.new(moderator).can_edit_name?(user)).to be_falsey end - it 'is false for admins' do + it "is false for admins" do expect(Guardian.new(admin).can_edit_name?(user)).to be_falsey end end - context 'when name is enabled in preferences' do - before do - SiteSetting.enable_names = true - end + context "when name is enabled in preferences" do + before { SiteSetting.enable_names = true } - context 'when SSO is disabled' do + context "when SSO is disabled" do before do SiteSetting.enable_discourse_connect = false SiteSetting.auth_overrides_name = false end - it 'is true for admins' do + it "is true for admins" do expect(Guardian.new(admin).can_edit_name?(admin)).to be_truthy end - it 'is true for moderators' do + it "is true for moderators" do expect(Guardian.new(moderator).can_edit_name?(moderator)).to be_truthy end - it 'is true for users' do + it "is true for users" do expect(Guardian.new(user).can_edit_name?(user)).to be_truthy end end - context 'when SSO is enabled' do + context "when SSO is enabled" do before do SiteSetting.discourse_connect_url = "https://www.example.com/sso" SiteSetting.enable_discourse_connect = true end - context 'when SSO name override is active' do - before do - SiteSetting.auth_overrides_name = true - end + context "when SSO name override is active" do + before { SiteSetting.auth_overrides_name = true } - it 'is false for admins' do + it "is false for admins" do expect(Guardian.new(admin).can_edit_name?(admin)).to be_falsey end - it 'is false for moderators' do + it "is false for moderators" do expect(Guardian.new(moderator).can_edit_name?(moderator)).to be_falsey end - it 'is false for users' do + it "is false for users" do expect(Guardian.new(user).can_edit_name?(user)).to be_falsey end end - context 'when SSO name override is not active' do - before do - SiteSetting.auth_overrides_name = false - end + context "when SSO name override is not active" do + before { SiteSetting.auth_overrides_name = false } - it 'is true for admins' do + it "is true for admins" do expect(Guardian.new(admin).can_edit_name?(admin)).to be_truthy end - it 'is true for moderators' do + it "is true for moderators" do expect(Guardian.new(moderator).can_edit_name?(moderator)).to be_truthy end - it 'is true for users' do + it "is true for users" do expect(Guardian.new(user).can_edit_name?(user)).to be_truthy end end @@ -3198,38 +3190,36 @@ RSpec.describe Guardian do end end - describe '#can_export_entity?' do + describe "#can_export_entity?" do let(:anonymous_guardian) { Guardian.new } let(:user_guardian) { Guardian.new(user) } let(:moderator_guardian) { Guardian.new(moderator) } let(:admin_guardian) { Guardian.new(admin) } - it 'only allows admins to export user_list' do - expect(user_guardian.can_export_entity?('user_list')).to be_falsey - expect(moderator_guardian.can_export_entity?('user_list')).to be_falsey - expect(admin_guardian.can_export_entity?('user_list')).to be_truthy + it "only allows admins to export user_list" do + expect(user_guardian.can_export_entity?("user_list")).to be_falsey + expect(moderator_guardian.can_export_entity?("user_list")).to be_falsey + expect(admin_guardian.can_export_entity?("user_list")).to be_truthy end - it 'allow moderators to export other admin entities' do - expect(user_guardian.can_export_entity?('staff_action')).to be_falsey - expect(moderator_guardian.can_export_entity?('staff_action')).to be_truthy - expect(admin_guardian.can_export_entity?('staff_action')).to be_truthy + it "allow moderators to export other admin entities" do + expect(user_guardian.can_export_entity?("staff_action")).to be_falsey + expect(moderator_guardian.can_export_entity?("staff_action")).to be_truthy + expect(admin_guardian.can_export_entity?("staff_action")).to be_truthy end - it 'does not allow anonymous to export' do - expect(anonymous_guardian.can_export_entity?('user_archive')).to be_falsey + it "does not allow anonymous to export" do + expect(anonymous_guardian.can_export_entity?("user_archive")).to be_falsey end end - describe '#can_ignore_user?' do - before do - SiteSetting.min_trust_level_to_allow_ignore = 1 - end + describe "#can_ignore_user?" do + before { SiteSetting.min_trust_level_to_allow_ignore = 1 } let(:guardian) { Guardian.new(trust_level_2) } context "when ignored user is the same as guardian user" do - it 'does not allow ignoring user' do + it "does not allow ignoring user" do expect(guardian.can_ignore_user?(trust_level_2)).to eq(false) end end @@ -3237,52 +3227,51 @@ RSpec.describe Guardian do context "when ignored user is a staff user" do let!(:admin) { Fabricate(:user, admin: true) } - it 'does not allow ignoring user' do + it "does not allow ignoring user" do expect(guardian.can_ignore_user?(admin)).to eq(false) end end context "when ignored user is a normal user" do - it 'allows ignoring user' do + it "allows ignoring user" do expect(guardian.can_ignore_user?(another_user)).to eq(true) end end context "when ignorer is staff" do let(:guardian) { Guardian.new(admin) } - it 'allows ignoring user' do + it "allows ignoring user" do expect(guardian.can_ignore_user?(another_user)).to eq(true) end end context "when ignorer's trust level is below min_trust_level_to_allow_ignore" do let(:guardian) { Guardian.new(trust_level_0) } - it 'does not allow ignoring user' do + it "does not allow ignoring user" do expect(guardian.can_ignore_user?(another_user)).to eq(false) end end context "when ignorer's trust level is equal to min_trust_level_to_allow_ignore site setting" do let(:guardian) { Guardian.new(trust_level_1) } - it 'allows ignoring user' do + it "allows ignoring user" do expect(guardian.can_ignore_user?(another_user)).to eq(true) end end context "when ignorer's trust level is above min_trust_level_to_allow_ignore site setting" do let(:guardian) { Guardian.new(trust_level_3) } - it 'allows ignoring user' do + it "allows ignoring user" do expect(guardian.can_ignore_user?(another_user)).to eq(true) end end end - describe '#can_mute_user?' do - + describe "#can_mute_user?" do let(:guardian) { Guardian.new(trust_level_1) } context "when muted user is the same as guardian user" do - it 'does not allow muting user' do + it "does not allow muting user" do expect(guardian.can_mute_user?(trust_level_1)).to eq(false) end end @@ -3290,13 +3279,13 @@ RSpec.describe Guardian do context "when muted user is a staff user" do let!(:admin) { Fabricate(:user, admin: true) } - it 'does not allow muting user' do + it "does not allow muting user" do expect(guardian.can_mute_user?(admin)).to eq(false) end end context "when muted user is a normal user" do - it 'allows muting user' do + it "allows muting user" do expect(guardian.can_mute_user?(another_user)).to eq(true) end end @@ -3305,7 +3294,7 @@ RSpec.describe Guardian do let(:guardian) { Guardian.new(trust_level_0) } let!(:trust_level_0) { Fabricate(:user, trust_level: 0) } - it 'does not allow muting user' do + it "does not allow muting user" do expect(guardian.can_mute_user?(another_user)).to eq(false) end end @@ -3313,7 +3302,7 @@ RSpec.describe Guardian do context "when muter is staff" do let(:guardian) { Guardian.new(admin) } - it 'allows muting user' do + it "allows muting user" do expect(guardian.can_mute_user?(another_user)).to eq(true) end end @@ -3321,7 +3310,7 @@ RSpec.describe Guardian do context "when muters's trust level is tl1" do let(:guardian) { Guardian.new(trust_level_1) } - it 'allows muting user' do + it "allows muting user" do expect(guardian.can_mute_user?(another_user)).to eq(true) end end @@ -3346,9 +3335,8 @@ RSpec.describe Guardian do expect(guardian.allow_themes?([theme.id], include_preview: true)).to eq(true) - expect(guardian.allowed_theme_repo_import?('https://x.com/git')).to eq(true) - expect(guardian.allowed_theme_repo_import?('https:/evil.com/git')).to eq(false) - + expect(guardian.allowed_theme_repo_import?("https://x.com/git")).to eq(true) + expect(guardian.allowed_theme_repo_import?("https:/evil.com/git")).to eq(false) end end @@ -3356,8 +3344,12 @@ RSpec.describe Guardian do expect(Guardian.new(moderator).allow_themes?([theme.id, theme2.id])).to eq(false) expect(Guardian.new(admin).allow_themes?([theme.id, theme2.id])).to eq(false) - expect(Guardian.new(moderator).allow_themes?([theme.id, theme2.id], include_preview: true)).to eq(true) - expect(Guardian.new(admin).allow_themes?([theme.id, theme2.id], include_preview: true)).to eq(true) + expect( + Guardian.new(moderator).allow_themes?([theme.id, theme2.id], include_preview: true), + ).to eq(true) + expect(Guardian.new(admin).allow_themes?([theme.id, theme2.id], include_preview: true)).to eq( + true, + ) end it "only allows normal users to use user-selectable themes or default theme" do @@ -3391,10 +3383,10 @@ RSpec.describe Guardian do end end - describe 'can_wiki?' do + describe "can_wiki?" do let(:post) { Fabricate(:post, created_at: 1.minute.ago) } - it 'returns false for regular user' do + it "returns false for regular user" do expect(Guardian.new(coding_horror).can_wiki?(post)).to be_falsey end @@ -3408,36 +3400,36 @@ RSpec.describe Guardian do expect(Guardian.new(trust_level_2).can_wiki?(post)).to be_falsey end - it 'returns true when user satisfies trust level and owns the post' do + it "returns true when user satisfies trust level and owns the post" do SiteSetting.min_trust_to_allow_self_wiki = 2 own_post = Fabricate(:post, user: trust_level_2) expect(Guardian.new(trust_level_2).can_wiki?(own_post)).to be_truthy end - it 'returns true for admin user' do + it "returns true for admin user" do expect(Guardian.new(admin).can_wiki?(post)).to be_truthy end - it 'returns true for trust_level_4 user' do + it "returns true for trust_level_4 user" do expect(Guardian.new(trust_level_4).can_wiki?(post)).to be_truthy end - context 'when post is older than post_edit_time_limit' do + context "when post is older than post_edit_time_limit" do let(:old_post) { Fabricate(:post, user: trust_level_2, created_at: 6.minutes.ago) } before do SiteSetting.min_trust_to_allow_self_wiki = 2 SiteSetting.tl2_post_edit_time_limit = 5 end - it 'returns false when user satisfies trust level and owns the post' do + it "returns false when user satisfies trust level and owns the post" do expect(Guardian.new(trust_level_2).can_wiki?(old_post)).to be_falsey end - it 'returns true for admin user' do + it "returns true for admin user" do expect(Guardian.new(admin).can_wiki?(old_post)).to be_truthy end - it 'returns true for trust_level_4 user' do + it "returns true for trust_level_4 user" do expect(Guardian.new(trust_level_4).can_wiki?(post)).to be_truthy end end @@ -3445,9 +3437,7 @@ RSpec.describe Guardian do describe "Tags" do context "with tagging disabled" do - before do - SiteSetting.tagging_enabled = false - end + before { SiteSetting.tagging_enabled = false } it "can_create_tag returns false" do expect(Guardian.new(admin).can_create_tag?).to be_falsey @@ -3468,10 +3458,8 @@ RSpec.describe Guardian do SiteSetting.min_trust_level_to_tag_topics = 1 end - context 'when min_trust_to_create_tag is 3' do - before do - SiteSetting.min_trust_to_create_tag = 3 - end + context "when min_trust_to_create_tag is 3" do + before { SiteSetting.min_trust_to_create_tag = 3 } describe "can_create_tag" do it "returns false if trust level is too low" do @@ -3505,9 +3493,7 @@ RSpec.describe Guardian do end context 'when min_trust_to_create_tag is "staff"' do - before do - SiteSetting.min_trust_to_create_tag = 'staff' - end + before { SiteSetting.min_trust_to_create_tag = "staff" } it "returns false if not staff" do expect(Guardian.new(trust_level_4).can_create_tag?).to eq(false) @@ -3520,9 +3506,7 @@ RSpec.describe Guardian do end context 'when min_trust_to_create_tag is "admin"' do - before do - SiteSetting.min_trust_to_create_tag = 'admin' - end + before { SiteSetting.min_trust_to_create_tag = "admin" } it "returns false if not admin" do expect(Guardian.new(trust_level_4).can_create_tag?).to eq(false) @@ -3553,8 +3537,8 @@ RSpec.describe Guardian do end describe "#can_see_group?" do - it 'Correctly handles owner visible groups' do - group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:owners]) + it "Correctly handles owner visible groups" do + group = Group.new(name: "group", visibility_level: Group.visibility_levels[:owners]) group.add(member) group.save! @@ -3570,8 +3554,8 @@ RSpec.describe Guardian do expect(Guardian.new(owner).can_see_group?(group)).to eq(true) end - it 'Correctly handles staff visible groups' do - group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:staff]) + it "Correctly handles staff visible groups" do + group = Group.new(name: "group", visibility_level: Group.visibility_levels[:staff]) group.add(member) group.save! @@ -3587,8 +3571,8 @@ RSpec.describe Guardian do expect(Guardian.new.can_see_group?(group)).to eq(false) end - it 'Correctly handles member visible groups' do - group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:members]) + it "Correctly handles member visible groups" do + group = Group.new(name: "group", visibility_level: Group.visibility_levels[:members]) group.add(member) group.save! @@ -3604,8 +3588,8 @@ RSpec.describe Guardian do expect(Guardian.new(owner).can_see_group?(group)).to eq(true) end - it 'Correctly handles logged-on-user visible groups' do - group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:logged_on_users]) + it "Correctly handles logged-on-user visible groups" do + group = Group.new(name: "group", visibility_level: Group.visibility_levels[:logged_on_users]) group.add(member) group.save! @@ -3620,16 +3604,16 @@ RSpec.describe Guardian do expect(Guardian.new(another_user).can_see_group?(group)).to eq(true) end - it 'Correctly handles public groups' do - group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:public]) + it "Correctly handles public groups" do + group = Group.new(name: "group", visibility_level: Group.visibility_levels[:public]) expect(Guardian.new.can_see_group?(group)).to eq(true) end end describe "#can_see_group_members?" do - it 'Correctly handles group members visibility for owner' do - group = Group.new(name: 'group', members_visibility_level: Group.visibility_levels[:owners]) + it "Correctly handles group members visibility for owner" do + group = Group.new(name: "group", members_visibility_level: Group.visibility_levels[:owners]) group.add(member) group.save! @@ -3645,8 +3629,8 @@ RSpec.describe Guardian do expect(Guardian.new(owner).can_see_group_members?(group)).to eq(true) end - it 'Correctly handles group members visibility for staff' do - group = Group.new(name: 'group', members_visibility_level: Group.visibility_levels[:staff]) + it "Correctly handles group members visibility for staff" do + group = Group.new(name: "group", members_visibility_level: Group.visibility_levels[:staff]) group.add(member) group.save! @@ -3662,8 +3646,8 @@ RSpec.describe Guardian do expect(Guardian.new.can_see_group_members?(group)).to eq(false) end - it 'Correctly handles group members visibility for member' do - group = Group.new(name: 'group', members_visibility_level: Group.visibility_levels[:members]) + it "Correctly handles group members visibility for member" do + group = Group.new(name: "group", members_visibility_level: Group.visibility_levels[:members]) group.add(member) group.save! @@ -3679,8 +3663,12 @@ RSpec.describe Guardian do expect(Guardian.new(owner).can_see_group_members?(group)).to eq(true) end - it 'Correctly handles group members visibility for logged-on-user' do - group = Group.new(name: 'group', members_visibility_level: Group.visibility_levels[:logged_on_users]) + it "Correctly handles group members visibility for logged-on-user" do + group = + Group.new( + name: "group", + members_visibility_level: Group.visibility_levels[:logged_on_users], + ) group.add(member) group.save! @@ -3695,17 +3683,16 @@ RSpec.describe Guardian do expect(Guardian.new(another_user).can_see_group_members?(group)).to eq(true) end - it 'Correctly handles group members visibility for public' do - group = Group.new(name: 'group', members_visibility_level: Group.visibility_levels[:public]) + it "Correctly handles group members visibility for public" do + group = Group.new(name: "group", members_visibility_level: Group.visibility_levels[:public]) expect(Guardian.new.can_see_group_members?(group)).to eq(true) end - end - describe '#can_see_groups?' do - it 'correctly handles owner visible groups' do - group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:owners]) + describe "#can_see_groups?" do + it "correctly handles owner visible groups" do + group = Group.new(name: "group", visibility_level: Group.visibility_levels[:owners]) group.add(member) group.save! @@ -3721,9 +3708,9 @@ RSpec.describe Guardian do expect(Guardian.new(owner).can_see_groups?([group])).to eq(true) end - it 'correctly handles the case where the user does not own every group' do - group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:owners]) - group2 = Group.new(name: 'group2', visibility_level: Group.visibility_levels[:owners]) + it "correctly handles the case where the user does not own every group" do + group = Group.new(name: "group", visibility_level: Group.visibility_levels[:owners]) + group2 = Group.new(name: "group2", visibility_level: Group.visibility_levels[:owners]) group2.save! group.add(member) @@ -3740,8 +3727,8 @@ RSpec.describe Guardian do expect(Guardian.new(another_user).can_see_groups?([group, group2])).to eq(false) end - it 'correctly handles staff visible groups' do - group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:staff]) + it "correctly handles staff visible groups" do + group = Group.new(name: "group", visibility_level: Group.visibility_levels[:staff]) group.add(member) group.save! @@ -3757,8 +3744,8 @@ RSpec.describe Guardian do expect(Guardian.new(another_user).can_see_groups?([group])).to eq(false) end - it 'correctly handles member visible groups' do - group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:members]) + it "correctly handles member visible groups" do + group = Group.new(name: "group", visibility_level: Group.visibility_levels[:members]) group.add(member) group.save! @@ -3774,8 +3761,8 @@ RSpec.describe Guardian do expect(Guardian.new(owner).can_see_groups?([group])).to eq(true) end - it 'correctly handles logged-on-user visible groups' do - group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:logged_on_users]) + it "correctly handles logged-on-user visible groups" do + group = Group.new(name: "group", visibility_level: Group.visibility_levels[:logged_on_users]) group.add(member) group.save! @@ -3791,9 +3778,9 @@ RSpec.describe Guardian do expect(Guardian.new(another_user).can_see_groups?([group])).to eq(true) end - it 'correctly handles the case where the user is not a member of every group' do - group1 = Group.new(name: 'group', visibility_level: Group.visibility_levels[:members]) - group2 = Group.new(name: 'group2', visibility_level: Group.visibility_levels[:members]) + it "correctly handles the case where the user is not a member of every group" do + group1 = Group.new(name: "group", visibility_level: Group.visibility_levels[:members]) + group2 = Group.new(name: "group2", visibility_level: Group.visibility_levels[:members]) group2.save! group1.add(member) @@ -3809,21 +3796,21 @@ RSpec.describe Guardian do expect(Guardian.new(owner).can_see_groups?([group1, group2])).to eq(false) end - it 'correctly handles public groups' do - group = Group.new(name: 'group', visibility_level: Group.visibility_levels[:public]) + it "correctly handles public groups" do + group = Group.new(name: "group", visibility_level: Group.visibility_levels[:public]) expect(Guardian.new.can_see_groups?([group])).to eq(true) end - it 'correctly handles case where not every group is public' do - group1 = Group.new(name: 'group', visibility_level: Group.visibility_levels[:public]) - group2 = Group.new(name: 'group', visibility_level: Group.visibility_levels[:private]) + it "correctly handles case where not every group is public" do + group1 = Group.new(name: "group", visibility_level: Group.visibility_levels[:public]) + group2 = Group.new(name: "group", visibility_level: Group.visibility_levels[:private]) expect(Guardian.new.can_see_groups?([group1, group2])).to eq(false) end end - describe 'topic featured link category restriction' do + describe "topic featured link category restriction" do before { SiteSetting.topic_featured_link_enabled = true } let(:guardian) { Guardian.new(user) } let(:uncategorized) { Category.find(SiteSetting.uncategorized_category_id) } @@ -3844,15 +3831,15 @@ RSpec.describe Guardian do end end - context 'when exist' do + context "when exist" do fab!(:category) { Fabricate(:category, topic_featured_link_allowed: false) } fab!(:link_category) { Fabricate(:link_category) } - it 'returns true if the category is listed' do + it "returns true if the category is listed" do expect(guardian.can_edit_featured_link?(link_category.id)).to eq(true) end - it 'returns false if the category does not allow it' do + it "returns false if the category does not allow it" do expect(guardian.can_edit_featured_link?(category.id)).to eq(false) end end @@ -3864,9 +3851,7 @@ RSpec.describe Guardian do end context "with hide suspension reason enabled" do - before do - SiteSetting.hide_suspension_reasons = true - end + before { SiteSetting.hide_suspension_reasons = true } it "will not be shown to anonymous users" do expect(Guardian.new.can_see_suspension_reason?(user)).to eq(false) @@ -3882,15 +3867,14 @@ RSpec.describe Guardian do end end - describe '#can_remove_allowed_users?' do - context 'with staff users' do - it 'should be true' do - expect(Guardian.new(moderator).can_remove_allowed_users?(topic)) - .to eq(true) + describe "#can_remove_allowed_users?" do + context "with staff users" do + it "should be true" do + expect(Guardian.new(moderator).can_remove_allowed_users?(topic)).to eq(true) end end - context 'with trust_level >= 2 user' do + context "with trust_level >= 2 user" do fab!(:topic_creator) { Fabricate(:user, trust_level: 2) } fab!(:topic) { Fabricate(:topic, user: topic_creator) } @@ -3899,13 +3883,12 @@ RSpec.describe Guardian do topic.allowed_users << another_user end - it 'should be true' do - expect(Guardian.new(topic_creator).can_remove_allowed_users?(topic)) - .to eq(true) + it "should be true" do + expect(Guardian.new(topic_creator).can_remove_allowed_users?(topic)).to eq(true) end end - context 'with normal user' do + context "with normal user" do fab!(:topic) { Fabricate(:topic, user: Fabricate(:user, trust_level: 1)) } before do @@ -3913,40 +3896,37 @@ RSpec.describe Guardian do topic.allowed_users << another_user end - it 'should be false' do - expect(Guardian.new(user).can_remove_allowed_users?(topic)) - .to eq(false) + it "should be false" do + expect(Guardian.new(user).can_remove_allowed_users?(topic)).to eq(false) end - describe 'target_user is the user' do - describe 'when user is in a pm with another user' do - it 'should return true' do - expect(Guardian.new(user).can_remove_allowed_users?(topic, user)) - .to eq(true) + describe "target_user is the user" do + describe "when user is in a pm with another user" do + it "should return true" do + expect(Guardian.new(user).can_remove_allowed_users?(topic, user)).to eq(true) end end - describe 'when user is the creator of the topic' do - it 'should return false' do - expect(Guardian.new(topic.user).can_remove_allowed_users?(topic, topic.user)) - .to eq(false) + describe "when user is the creator of the topic" do + it "should return false" do + expect(Guardian.new(topic.user).can_remove_allowed_users?(topic, topic.user)).to eq( + false, + ) end end - describe 'when user is the only user in the topic' do - it 'should return false' do + describe "when user is the only user in the topic" do + it "should return false" do topic.remove_allowed_user(Discourse.system_user, another_user.username) - expect(Guardian.new(user).can_remove_allowed_users?(topic, user)) - .to eq(false) + expect(Guardian.new(user).can_remove_allowed_users?(topic, user)).to eq(false) end end end - describe 'target_user is not the user' do - it 'should return false' do - expect(Guardian.new(user).can_remove_allowed_users?(topic, moderator)) - .to eq(false) + describe "target_user is not the user" do + it "should return false" do + expect(Guardian.new(user).can_remove_allowed_users?(topic, moderator)).to eq(false) end end end @@ -3954,11 +3934,11 @@ RSpec.describe Guardian do context "with anonymous users" do fab!(:topic) { Fabricate(:topic) } - it 'should be false' do + it "should be false" do expect(Guardian.new.can_remove_allowed_users?(topic)).to eq(false) end - it 'should be false when the topic does not have a user (for example because the user was removed)' do + it "should be false when the topic does not have a user (for example because the user was removed)" do DB.exec("UPDATE topics SET user_id=NULL WHERE id=#{topic.id}") topic.reload @@ -3967,22 +3947,23 @@ RSpec.describe Guardian do end end - describe '#auth_token' do - it 'returns the correct auth token' do + describe "#auth_token" do + it "returns the correct auth token" do token = UserAuthToken.generate!(user_id: user.id) - cookie = create_auth_cookie( - token: token.unhashed_auth_token, - user_id: user.id, - trust_level: user.trust_level, - issued_at: 5.minutes.ago, - ) + cookie = + create_auth_cookie( + token: token.unhashed_auth_token, + user_id: user.id, + trust_level: user.trust_level, + issued_at: 5.minutes.ago, + ) env = create_request_env(path: "/").merge("HTTP_COOKIE" => "_t=#{cookie};") guardian = Guardian.new(user, ActionDispatch::Request.new(env)) expect(guardian.auth_token).to eq(token.auth_token) end - it 'supports v0 of auth cookie' do + it "supports v0 of auth cookie" do token = UserAuthToken.generate!(user_id: user.id) cookie = token.unhashed_auth_token env = create_request_env(path: "/").merge("HTTP_COOKIE" => "_t=#{cookie};") @@ -4000,9 +3981,7 @@ RSpec.describe Guardian do end context "when enabled" do - before do - SiteSetting.enable_page_publishing = true - end + before { SiteSetting.enable_page_publishing = true } it "is false for anonymous users" do expect(Guardian.new.can_publish_page?(topic)).to eq(false) @@ -4040,9 +4019,7 @@ RSpec.describe Guardian do describe "#can_see_site_contact_details?" do context "when login_required is enabled" do - before do - SiteSetting.login_required = true - end + before { SiteSetting.login_required = true } it "is false for anonymous users" do expect(Guardian.new.can_see_site_contact_details?).to eq(false) @@ -4054,9 +4031,7 @@ RSpec.describe Guardian do end context "when login_required is disabled" do - before do - SiteSetting.login_required = false - end + before { SiteSetting.login_required = false } it "is true for anonymous users" do expect(Guardian.new.can_see_site_contact_details?).to eq(true) @@ -4069,17 +4044,17 @@ RSpec.describe Guardian do end describe "#can_mention_here?" do - it 'returns false if disabled' do + it "returns false if disabled" do SiteSetting.max_here_mentioned = 0 expect(admin.guardian.can_mention_here?).to eq(false) end - it 'returns false if disabled' do - SiteSetting.here_mention = '' + it "returns false if disabled" do + SiteSetting.here_mention = "" expect(admin.guardian.can_mention_here?).to eq(false) end - it 'works with trust levels' do + it "works with trust levels" do SiteSetting.min_trust_level_for_here_mention = 2 expect(trust_level_0.guardian.can_mention_here?).to eq(false) @@ -4091,16 +4066,16 @@ RSpec.describe Guardian do expect(admin.guardian.can_mention_here?).to eq(true) end - it 'works with staff' do - SiteSetting.min_trust_level_for_here_mention = 'staff' + it "works with staff" do + SiteSetting.min_trust_level_for_here_mention = "staff" expect(trust_level_4.guardian.can_mention_here?).to eq(false) expect(moderator.guardian.can_mention_here?).to eq(true) expect(admin.guardian.can_mention_here?).to eq(true) end - it 'works with admin' do - SiteSetting.min_trust_level_for_here_mention = 'admin' + it "works with admin" do + SiteSetting.min_trust_level_for_here_mention = "admin" expect(trust_level_4.guardian.can_mention_here?).to eq(false) expect(moderator.guardian.can_mention_here?).to eq(false) @@ -4109,9 +4084,7 @@ RSpec.describe Guardian do end describe "#is_category_group_moderator" do - before do - SiteSetting.enable_category_group_moderation = true - end + before { SiteSetting.enable_category_group_moderation = true } fab!(:category) { Fabricate(:category) } diff --git a/spec/lib/has_errors_spec.rb b/spec/lib/has_errors_spec.rb index a63b31ef88..4765d50d77 100644 --- a/spec/lib/has_errors_spec.rb +++ b/spec/lib/has_errors_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'has_errors' +require "has_errors" RSpec.describe HasErrors do class ErrorTestClass @@ -11,7 +11,7 @@ RSpec.describe HasErrors do let(:title_error) { "Title can't be blank" } # No title is invalid - let(:invalid_topic) { Fabricate.build(:topic, title: '') } + let(:invalid_topic) { Fabricate.build(:topic, title: "") } it "has no errors by default" do expect(error_test.errors).to be_blank @@ -35,7 +35,9 @@ RSpec.describe HasErrors do it "triggers a rollback" do invalid_topic.valid? - expect { error_test.rollback_from_errors!(invalid_topic) }.to raise_error(ActiveRecord::Rollback) + expect { error_test.rollback_from_errors!(invalid_topic) }.to raise_error( + ActiveRecord::Rollback, + ) expect(error_test.errors).to be_present expect(error_test.errors[:base]).to include(title_error) end @@ -43,12 +45,13 @@ RSpec.describe HasErrors do describe "rollback_with_error!" do it "triggers a rollback" do - - expect do - error_test.rollback_with!(invalid_topic, :too_many_users) - end.to raise_error(ActiveRecord::Rollback) + expect do error_test.rollback_with!(invalid_topic, :too_many_users) end.to raise_error( + ActiveRecord::Rollback, + ) expect(error_test.errors).to be_present - expect(error_test.errors[:base]).to include("You can only send warnings to one user at a time.") + expect(error_test.errors[:base]).to include( + "You can only send warnings to one user at a time.", + ) end end end diff --git a/spec/lib/highlight_js/highlight_js_spec.rb b/spec/lib/highlight_js/highlight_js_spec.rb index d3fd705886..c953ec56db 100644 --- a/spec/lib/highlight_js/highlight_js_spec.rb +++ b/spec/lib/highlight_js/highlight_js_spec.rb @@ -1,18 +1,18 @@ # frozen_string_literal: true RSpec.describe HighlightJs do - it 'can list languages' do - expect(HighlightJs.languages).to include('thrift') + it "can list languages" do + expect(HighlightJs.languages).to include("thrift") end - it 'can generate a packed bundle' do - bundle = HighlightJs.bundle(["thrift", "http"]) + it "can generate a packed bundle" do + bundle = HighlightJs.bundle(%w[thrift http]) expect(bundle).to match(/thrift/) expect(bundle).to match(/http/) expect(bundle).not_to match(/applescript/) end - it 'can get a version string' do + it "can get a version string" do version1 = HighlightJs.version("http|cpp") version2 = HighlightJs.version("rust|cpp|fake") diff --git a/spec/lib/hijack_spec.rb b/spec/lib/hijack_spec.rb index 0e806fd7ab..87e1518a17 100644 --- a/spec/lib/hijack_spec.rb +++ b/spec/lib/hijack_spec.rb @@ -9,10 +9,7 @@ RSpec.describe Hijack do def initialize(env = {}) @io = StringIO.new - env.merge!( - "rack.hijack" => lambda { @io }, - "rack.input" => StringIO.new - ) + env.merge!("rack.hijack" => lambda { @io }, "rack.input" => StringIO.new) self.request = ActionController::TestRequest.new(env, nil, nil) @@ -23,7 +20,6 @@ RSpec.describe Hijack do def hijack_test(&blk) hijack(&blk) end - end let :tester do @@ -44,17 +40,14 @@ RSpec.describe Hijack do @calls = 0 end - after do - Middleware::RequestTracker.unregister_detailed_request_logger logger - end + after { Middleware::RequestTracker.unregister_detailed_request_logger logger } it "can properly track execution" do - app = lambda do |env| - tester = Hijack::Tester.new(env) - tester.hijack_test do - render body: "hello", status: 201 + app = + lambda do |env| + tester = Hijack::Tester.new(env) + tester.hijack_test { render body: "hello", status: 201 } end - end env = create_request_env(path: "/") middleware = Middleware::RequestTracker.new(app) @@ -81,24 +74,20 @@ RSpec.describe Hijack do it "handles cors" do SiteSetting.cors_origins = "www.rainbows.com" - app = lambda do |env| - tester = Hijack::Tester.new(env) - tester.hijack_test do - render body: "hello", status: 201 - end + app = + lambda do |env| + tester = Hijack::Tester.new(env) + tester.hijack_test { render body: "hello", status: 201 } - expect(tester.io.string).to include("Access-Control-Allow-Origin: www.rainbows.com") - end + expect(tester.io.string).to include("Access-Control-Allow-Origin: www.rainbows.com") + end env = {} middleware = Discourse::Cors.new(app) middleware.call(env) # it can do pre-flight - env = { - 'REQUEST_METHOD' => 'OPTIONS', - 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'GET' - } + env = { "REQUEST_METHOD" => "OPTIONS", "HTTP_ACCESS_CONTROL_REQUEST_METHOD" => "GET" } status, headers, _body = middleware.call(env) @@ -106,7 +95,8 @@ RSpec.describe Hijack do expected = { "Access-Control-Allow-Origin" => "www.rainbows.com", - "Access-Control-Allow-Headers" => "Content-Type, Cache-Control, X-Requested-With, X-CSRF-Token, Discourse-Present, User-Api-Key, User-Api-Client-Id, Authorization", + "Access-Control-Allow-Headers" => + "Content-Type, Cache-Control, X-Requested-With, X-CSRF-Token, Discourse-Present, User-Api-Key, User-Api-Client-Id, Authorization", "Access-Control-Allow-Credentials" => "true", "Access-Control-Allow-Methods" => "POST, PUT, GET, OPTIONS, DELETE", "Access-Control-Max-Age" => "7200", @@ -119,24 +109,20 @@ RSpec.describe Hijack do GlobalSetting.stubs(:enable_cors).returns(true) GlobalSetting.stubs(:cors_origin).returns("https://www.rainbows.com/") - app = lambda do |env| - tester = Hijack::Tester.new(env) - tester.hijack_test do - render body: "hello", status: 201 - end + app = + lambda do |env| + tester = Hijack::Tester.new(env) + tester.hijack_test { render body: "hello", status: 201 } - expect(tester.io.string).to include("Access-Control-Allow-Origin: https://www.rainbows.com") - end + expect(tester.io.string).to include("Access-Control-Allow-Origin: https://www.rainbows.com") + end env = {} middleware = Discourse::Cors.new(app) middleware.call(env) # it can do pre-flight - env = { - 'REQUEST_METHOD' => 'OPTIONS', - 'HTTP_ACCESS_CONTROL_REQUEST_METHOD' => 'GET' - } + env = { "REQUEST_METHOD" => "OPTIONS", "HTTP_ACCESS_CONTROL_REQUEST_METHOD" => "GET" } status, headers, _body = middleware.call(env) @@ -144,7 +130,8 @@ RSpec.describe Hijack do expected = { "Access-Control-Allow-Origin" => "https://www.rainbows.com", - "Access-Control-Allow-Headers" => "Content-Type, Cache-Control, X-Requested-With, X-CSRF-Token, Discourse-Present, User-Api-Key, User-Api-Client-Id, Authorization", + "Access-Control-Allow-Headers" => + "Content-Type, Cache-Control, X-Requested-With, X-CSRF-Token, Discourse-Present, User-Api-Key, User-Api-Client-Id, Authorization", "Access-Control-Allow-Credentials" => "true", "Access-Control-Allow-Methods" => "POST, PUT, GET, OPTIONS, DELETE", "Access-Control-Max-Age" => "7200", @@ -173,18 +160,14 @@ RSpec.describe Hijack do end it "renders non 200 status if asked for" do - tester.hijack_test do - render body: "hello world", status: 402 - end + tester.hijack_test { render body: "hello world", status: 402 } expect(tester.io.string).to include("402") expect(tester.io.string).to include("world") end it "handles send_file correctly" do - tester.hijack_test do - send_file __FILE__, disposition: nil - end + tester.hijack_test { send_file __FILE__, disposition: nil } expect(tester.io.string).to start_with("HTTP/1.1 200") end @@ -193,10 +176,11 @@ RSpec.describe Hijack do Process.stubs(:clock_gettime).returns(1.0) tester.hijack_test do Process.stubs(:clock_gettime).returns(2.0) - redirect_to 'http://awesome.com', allow_other_host: true + redirect_to "http://awesome.com", allow_other_host: true end - result = "HTTP/1.1 302 Found\r\nLocation: http://awesome.com\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 84\r\nConnection: close\r\nX-Runtime: 1.000000\r\n\r\nYou are being redirected." + result = + "HTTP/1.1 302 Found\r\nLocation: http://awesome.com\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 84\r\nConnection: close\r\nX-Runtime: 1.000000\r\n\r\nYou are being redirected." expect(tester.io.string).to eq(result) end @@ -207,7 +191,8 @@ RSpec.describe Hijack do render body: nil end - result = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 0\r\nConnection: close\r\nX-Runtime: 1.000000\r\n\r\n" + result = + "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 0\r\nConnection: close\r\nX-Runtime: 1.000000\r\n\r\n" expect(tester.io.string).to eq(result) end @@ -218,7 +203,8 @@ RSpec.describe Hijack do render plain: "hello world" end - result = "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 11\r\nConnection: close\r\nX-Runtime: 1.000000\r\n\r\nhello world" + result = + "HTTP/1.1 200 OK\r\nContent-Type: text/plain; charset=utf-8\r\nContent-Length: 11\r\nConnection: close\r\nX-Runtime: 1.000000\r\n\r\nhello world" expect(tester.io.string).to eq(result) end @@ -226,7 +212,8 @@ RSpec.describe Hijack do Process.stubs(:clock_gettime).returns(1.0) tester.hijack_test - expected = "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 0\r\nConnection: close\r\nX-Runtime: 0.000000\r\n\r\n" + expected = + "HTTP/1.1 500 Internal Server Error\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: 0\r\nConnection: close\r\nX-Runtime: 0.000000\r\n\r\n" expect(tester.io.string).to eq(expected) end @@ -234,9 +221,7 @@ RSpec.describe Hijack do tester.io.close ran = false - tester.hijack_test do - ran = true - end + tester.hijack_test { ran = true } expect(ran).to eq(false) end diff --git a/spec/lib/html_prettify_spec.rb b/spec/lib/html_prettify_spec.rb index e9bd6c5138..4401c07a96 100644 --- a/spec/lib/html_prettify_spec.rb +++ b/spec/lib/html_prettify_spec.rb @@ -1,21 +1,22 @@ # frozen_string_literal: true -require 'html_prettify' +require "html_prettify" RSpec.describe HtmlPrettify do - def t(source, expected) expect(HtmlPrettify.render(source)).to eq(expected) end - it 'correctly prettifies html' do + it "correctly prettifies html" do t "

    All's well!

    ", "

    All’s well!

    " t "

    Eatin' Lunch'.

    ", "

    Eatin’ Lunch’.

    " - t "

    a 1/4. is a fraction but not 1/4/2000

    ", "

    a ¼. is a fraction but not 1/4/2000

    " + t "

    a 1/4. is a fraction but not 1/4/2000

    ", + "

    a ¼. is a fraction but not 1/4/2000

    " t "

    Well that'll be the day

    ", "

    Well that’ll be the day

    " - t %(

    "Quoted text"

    ), %(

    “Quoted text”

    ) + t %(

    "Quoted text"

    ), "

    “Quoted text”

    " t "

    I've been meaning to tell you ..

    ", "

    I’ve been meaning to tell you ..

    " - t "

    single `backticks` in HTML should be preserved

    ", "

    single `backticks` in HTML should be preserved

    " + t "

    single `backticks` in HTML should be preserved

    ", + "

    single `backticks` in HTML should be preserved

    " t "

    double hyphen -- ndash --- mdash

    ", "

    double hyphen – ndash — mdash

    " t "a long time ago...", "a long time ago…" t "is 'this a mistake'?", "is ‘this a mistake’?" @@ -27,7 +28,7 @@ RSpec.describe HtmlPrettify do t '\\\\mnt\\c', "\\\\mnt\\c" - t ERB::Util.html_escape(' yay'), "<img src=“test.png”> yay" + t ERB::Util.html_escape(' yay'), + "<img src=“test.png”> yay" end - end diff --git a/spec/lib/html_to_markdown_spec.rb b/spec/lib/html_to_markdown_spec.rb index 0666688077..c106b00b37 100644 --- a/spec/lib/html_to_markdown_spec.rb +++ b/spec/lib/html_to_markdown_spec.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require 'html_to_markdown' +require "html_to_markdown" RSpec.describe HtmlToMarkdown do - def html_to_markdown(html, opts = {}) HtmlToMarkdown.new(html, opts).to_markdown end @@ -20,7 +19,9 @@ RSpec.describe HtmlToMarkdown do HTML - expect(html_to_markdown(html)).to eq("Hello,\n\nThis is the 1st paragraph.\n\nThis is another paragraph") + expect(html_to_markdown(html)).to eq( + "Hello,\n\nThis is the 1st paragraph.\n\nThis is another paragraph", + ) html = <<~HTML @@ -65,7 +66,6 @@ RSpec.describe HtmlToMarkdown do end it "doesn't error on non-inline elements like (aside, section)" do - html = <<~HTML