From 8e539269a70a962eabbb43dbe1e7f3853ac19a82 Mon Sep 17 00:00:00 2001 From: Nat Date: Thu, 21 Nov 2024 01:15:21 +0800 Subject: [PATCH] DEV: Autoload app files --- .../translator_controller.rb | 57 ++++++ app/jobs/scheduled/detect_posts_language.rb | 40 +++++ .../services}/discourse_translator/amazon.rb | 0 .../services}/discourse_translator/base.rb | 0 .../services}/discourse_translator/google.rb | 0 .../discourse_translator/libre_translate.rb | 0 .../discourse_translator/microsoft.rb | 0 .../services}/discourse_translator/yandex.rb | 0 config/routes.rb | 7 + lib/discourse_translator/engine.rb | 12 ++ plugin.rb | 169 +----------------- .../controllers/translator_controller_spec.rb | 151 ---------------- ..._spec.rb => detect_posts_language_spec.rb} | 4 +- spec/jobs/detect_translation_spec.rb | 55 ------ spec/requests/translator_controller_spec.rb | 149 +++++++++++++++ ...nslate_spec.rb => libre_translate_spec.rb} | 0 16 files changed, 275 insertions(+), 369 deletions(-) create mode 100644 app/controllers/discourse_translator/translator_controller.rb create mode 100644 app/jobs/scheduled/detect_posts_language.rb rename {services => app/services}/discourse_translator/amazon.rb (100%) rename {services => app/services}/discourse_translator/base.rb (100%) rename {services => app/services}/discourse_translator/google.rb (100%) rename services/discourse_translator/libretranslate.rb => app/services/discourse_translator/libre_translate.rb (100%) rename {services => app/services}/discourse_translator/microsoft.rb (100%) rename {services => app/services}/discourse_translator/yandex.rb (100%) create mode 100644 config/routes.rb create mode 100644 lib/discourse_translator/engine.rb delete mode 100644 spec/controllers/translator_controller_spec.rb rename spec/jobs/{detect_posts_translation_spec.rb => detect_posts_language_spec.rb} (95%) delete mode 100644 spec/jobs/detect_translation_spec.rb create mode 100644 spec/requests/translator_controller_spec.rb rename spec/services/{libretranslate_spec.rb => libre_translate_spec.rb} (100%) diff --git a/app/controllers/discourse_translator/translator_controller.rb b/app/controllers/discourse_translator/translator_controller.rb new file mode 100644 index 00000000..e6e9d89c --- /dev/null +++ b/app/controllers/discourse_translator/translator_controller.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +module ::DiscourseTranslator + class TranslatorController < ::ApplicationController + requires_plugin PLUGIN_NAME + + before_action :ensure_logged_in + + def translate + if !current_user.staff? + RateLimiter.new( + current_user, + "translate_post", + SiteSetting.max_translations_per_minute, + 1.minute, + ).performed! + end + + params.require(:post_id) + post = Post.find_by(id: params[:post_id]) + raise Discourse::InvalidParameters.new(:post_id) if post.blank? + guardian.ensure_can_see!(post) + + if !guardian.user_group_allow_translate? + raise Discourse::InvalidAccess.new( + "not_in_group", + SiteSetting.restrict_translation_by_group, + custom_message: "not_in_group.user_not_in_group", + group: current_user.groups.pluck(:id), + ) + end + + if !guardian.poster_group_allow_translate?(post) + raise Discourse::InvalidAccess.new( + "not_in_group", + SiteSetting.restrict_translation_by_poster_group, + custom_message: "not_in_group.poster_not_in_group", + ) + end + + begin + title_json = {} + detected_lang, translation = + "DiscourseTranslator::#{SiteSetting.translator}".constantize.translate(post) + if post.is_first_post? + _, title_translation = + "DiscourseTranslator::#{SiteSetting.translator}".constantize.translate(post.topic) + title_json = { title_translation: title_translation } + end + render json: { translation: translation, detected_lang: detected_lang }.merge(title_json), + status: 200 + rescue ::DiscourseTranslator::TranslatorError => e + render_json_error e.message, status: 422 + end + end + end +end diff --git a/app/jobs/scheduled/detect_posts_language.rb b/app/jobs/scheduled/detect_posts_language.rb new file mode 100644 index 00000000..0fa896d4 --- /dev/null +++ b/app/jobs/scheduled/detect_posts_language.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module ::Jobs + class DetectPostsLanguage < ::Jobs::Scheduled + sidekiq_options retry: false + every 5.minutes + + BATCH_SIZE = 100 + MAX_QUEUE_SIZE = 1000 + + def execute(args) + return unless SiteSetting.translator_enabled + + post_ids = Discourse.redis.spop(DiscourseTranslator::LANG_DETECT_NEEDED, MAX_QUEUE_SIZE) + return if post_ids.blank? + + post_ids.each_slice(BATCH_SIZE) { |batch| process_batch(batch) } + end + + private + + def process_batch(post_ids) + posts = Post.where(id: post_ids).to_a + posts.each do |post| + DistributedMutex.synchronize("detect_translation_#{post.id}") do + begin + translator = "DiscourseTranslator::#{SiteSetting.translator}".constantize + translator.detect(post) + if !post.custom_fields_clean? + post.save_custom_fields + post.publish_change_to_clients!(:revised) + end + rescue ::DiscourseTranslator::ProblemCheckedTranslationError + # problem-checked translation errors gracefully + end + end + end + end + end +end diff --git a/services/discourse_translator/amazon.rb b/app/services/discourse_translator/amazon.rb similarity index 100% rename from services/discourse_translator/amazon.rb rename to app/services/discourse_translator/amazon.rb diff --git a/services/discourse_translator/base.rb b/app/services/discourse_translator/base.rb similarity index 100% rename from services/discourse_translator/base.rb rename to app/services/discourse_translator/base.rb diff --git a/services/discourse_translator/google.rb b/app/services/discourse_translator/google.rb similarity index 100% rename from services/discourse_translator/google.rb rename to app/services/discourse_translator/google.rb diff --git a/services/discourse_translator/libretranslate.rb b/app/services/discourse_translator/libre_translate.rb similarity index 100% rename from services/discourse_translator/libretranslate.rb rename to app/services/discourse_translator/libre_translate.rb diff --git a/services/discourse_translator/microsoft.rb b/app/services/discourse_translator/microsoft.rb similarity index 100% rename from services/discourse_translator/microsoft.rb rename to app/services/discourse_translator/microsoft.rb diff --git a/services/discourse_translator/yandex.rb b/app/services/discourse_translator/yandex.rb similarity index 100% rename from services/discourse_translator/yandex.rb rename to app/services/discourse_translator/yandex.rb diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 00000000..47e7bc52 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +DiscourseTranslator::Engine.routes.draw do + post "/translate" => "translator#translate", :format => :json +end + +Discourse::Application.routes.draw { mount ::DiscourseTranslator::Engine, at: "/translator" } diff --git a/lib/discourse_translator/engine.rb b/lib/discourse_translator/engine.rb new file mode 100644 index 00000000..110dcf58 --- /dev/null +++ b/lib/discourse_translator/engine.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true +module ::DiscourseTranslator + class Engine < ::Rails::Engine + engine_name PLUGIN_NAME + isolate_namespace DiscourseTranslator + config.autoload_paths << File.join(config.root, "lib") + scheduled_job_dir = "#{config.root}/app/jobs/scheduled" + config.to_prepare do + Rails.autoloaders.main.eager_load_dir(scheduled_job_dir) if Dir.exist?(scheduled_job_dir) + end + end +end diff --git a/plugin.rb b/plugin.rb index d7f8205f..f4fa0aca 100644 --- a/plugin.rb +++ b/plugin.rb @@ -12,174 +12,25 @@ enabled_site_setting :translator_enabled register_asset "stylesheets/common/post.scss" -after_initialize do - module ::DiscourseTranslator - PLUGIN_NAME = "discourse_translator".freeze - DETECTED_LANG_CUSTOM_FIELD = "post_detected_lang".freeze - TRANSLATED_CUSTOM_FIELD = "translated_text".freeze - LANG_DETECT_NEEDED = "lang_detect_needed".freeze +module ::DiscourseTranslator + PLUGIN_NAME = "discourse-translator".freeze - autoload :Microsoft, - "#{Rails.root}/plugins/discourse-translator/services/discourse_translator/microsoft" - autoload :Google, - "#{Rails.root}/plugins/discourse-translator/services/discourse_translator/google" - autoload :Amazon, - "#{Rails.root}/plugins/discourse-translator/services/discourse_translator/amazon" - autoload :Yandex, - "#{Rails.root}/plugins/discourse-translator/services/discourse_translator/yandex" - autoload :LibreTranslate, - "#{Rails.root}/plugins/discourse-translator/services/discourse_translator/libretranslate" + DETECTED_LANG_CUSTOM_FIELD = "post_detected_lang".freeze + TRANSLATED_CUSTOM_FIELD = "translated_text".freeze + LANG_DETECT_NEEDED = "lang_detect_needed".freeze +end - class Engine < ::Rails::Engine - engine_name PLUGIN_NAME - isolate_namespace DiscourseTranslator - end - end +require_relative "lib/discourse_translator/engine" - require_relative "app/services/problem_check/missing_translator_api_key" - require_relative "app/services/problem_check/translator_error" +after_initialize do register_problem_check ProblemCheck::MissingTranslatorApiKey register_problem_check ProblemCheck::TranslatorError - class DiscourseTranslator::TranslatorController < ::ApplicationController - before_action :ensure_logged_in - - def translate - raise PluginDisabled if !SiteSetting.translator_enabled - - if !current_user.staff? - RateLimiter.new( - current_user, - "translate_post", - SiteSetting.max_translations_per_minute, - 1.minute, - ).performed! - end - - params.require(:post_id) - post = Post.find_by(id: params[:post_id]) - raise Discourse::InvalidParameters.new(:post_id) if post.blank? - guardian.ensure_can_see!(post) - - if !guardian.user_group_allow_translate? - raise Discourse::InvalidAccess.new( - "not_in_group", - SiteSetting.restrict_translation_by_group, - custom_message: "not_in_group.user_not_in_group", - group: current_user.groups.pluck(:id), - ) - end - - if !guardian.poster_group_allow_translate?(post) - raise Discourse::InvalidAccess.new( - "not_in_group", - SiteSetting.restrict_translation_by_poster_group, - custom_message: "not_in_group.poster_not_in_group", - ) - end - - begin - title_json = {} - detected_lang, translation = - "DiscourseTranslator::#{SiteSetting.translator}".constantize.translate(post) - if post.is_first_post? - _, title_translation = - "DiscourseTranslator::#{SiteSetting.translator}".constantize.translate(post.topic) - title_json = { title_translation: title_translation } - end - render json: { translation: translation, detected_lang: detected_lang }.merge(title_json), - status: 200 - rescue ::DiscourseTranslator::TranslatorError => e - render_json_error e.message, status: 422 - end - end - end - Post.register_custom_field_type(::DiscourseTranslator::TRANSLATED_CUSTOM_FIELD, :json) Topic.register_custom_field_type(::DiscourseTranslator::TRANSLATED_CUSTOM_FIELD, :json) - module ::Jobs - class TranslatorMigrateToAzurePortal < ::Jobs::Onceoff - def execute_onceoff(args) - %w[translator_client_id translator_client_secret].each { |name| DB.exec <<~SQL } - DELETE FROM site_settings WHERE name = '#{name}' - SQL - - DB.exec <<~SQL - UPDATE site_settings - SET name = 'translator_azure_subscription_key' - WHERE name = 'azure_subscription_key' - SQL - end - end - - class DetectTranslation < ::Jobs::Base - sidekiq_options retry: false - - def execute(args) - return if !SiteSetting.translator_enabled - - post = Post.find_by(id: args[:post_id]) - return unless post - - DistributedMutex.synchronize("detect_translation_#{post.id}") do - begin - "DiscourseTranslator::#{SiteSetting.translator}".constantize.detect(post) - if !post.custom_fields_clean? - post.save_custom_fields - post.publish_change_to_clients! :revised - end - rescue ::DiscourseTranslator::ProblemCheckedTranslationError - # The error was handled by ProblemCheck., no need to log errors here - end - end - end - end - - class DetectPostsTranslation < ::Jobs::Scheduled - sidekiq_options retry: false - every 5.minutes - - BATCH_SIZE = 100 - MAX_QUEUE_SIZE = 1000 - - def execute(args) - return unless SiteSetting.translator_enabled - - post_ids = Discourse.redis.spop(DiscourseTranslator::LANG_DETECT_NEEDED, MAX_QUEUE_SIZE) - return if post_ids.blank? - - post_ids.each_slice(BATCH_SIZE) { |batch| process_batch(batch) } - end - - private - - def process_batch(post_ids) - posts = Post.where(id: post_ids).to_a - posts.each do |post| - DistributedMutex.synchronize("detect_translation_#{post.id}") do - begin - translator = "DiscourseTranslator::#{SiteSetting.translator}".constantize - translator.detect(post) - if !post.custom_fields_clean? - post.save_custom_fields - post.publish_change_to_clients!(:revised) - end - rescue ::DiscourseTranslator::ProblemCheckedTranslationError - # problem-checked translation errors gracefully - end - end - end - end - end - end - topic_view_post_custom_fields_allowlister { [::DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD] } - require_relative "lib/discourse_translator/guardian_extension" - require_relative "lib/discourse_translator/post_extension" - require_relative "lib/discourse_translator/topic_extension" - reloadable_patch do |plugin| Guardian.prepend(DiscourseTranslator::GuardianExtension) Post.prepend(DiscourseTranslator::PostExtension) @@ -205,8 +56,4 @@ def process_batch(post_ids) ] end end - - DiscourseTranslator::Engine.routes.draw { post "translate" => "translator#translate" } - - Discourse::Application.routes.append { mount ::DiscourseTranslator::Engine, at: "translator" } end diff --git a/spec/controllers/translator_controller_spec.rb b/spec/controllers/translator_controller_spec.rb deleted file mode 100644 index 455194f2..00000000 --- a/spec/controllers/translator_controller_spec.rb +++ /dev/null @@ -1,151 +0,0 @@ -# frozen_string_literal: true - -require "rails_helper" - -RSpec.describe ::DiscourseTranslator::TranslatorController do - routes { ::DiscourseTranslator::Engine.routes } - - before do - SiteSetting.translator_enabled = true - SiteSetting.translator = "Microsoft" - SiteSetting.restrict_translation_by_group = "#{Group.find_by(name: "trust_level_1").id}" - end - - after { SiteSetting.translator_enabled = false } - - shared_examples "translation_successful" do - it "returns the translated text" do - DiscourseTranslator::Microsoft.expects(:translate).with(reply).returns(%w[ja ニャン猫]) - if reply.is_first_post? - DiscourseTranslator::Microsoft.expects(:translate).with(reply.topic).returns(%w[ja タイトル]) - end - - post :translate, params: { post_id: reply.id }, format: :json - - expect(response).to have_http_status(:ok) - expect(response.body).to eq( - { translation: "ニャン猫", detected_lang: "ja", title_translation: "タイトル" }.to_json, - ) - end - end - - shared_examples "deny_request_to_translate" do - it "should deny request to translate" do - response = post :translate, params: { post_id: reply.id }, format: :json - expect(response).to have_http_status(:forbidden) - end - end - - describe "#translate" do - describe "anon user" do - it "should not allow translation of posts" do - post :translate, params: { post_id: 1 }, format: :json - expect(response).to have_http_status(:forbidden) - end - end - - describe "logged in user" do - let!(:user) do - user = log_in - user.group_users << Fabricate(:group_user, user: user, group: Group[:trust_level_1]) - user - end - let!(:poster) do - poster = Fabricate(:user) - poster.group_users << Fabricate(:group_user, user: user, group: Group[:trust_level_2]) - poster - end - - describe "when config translator_enabled disabled" do - before { SiteSetting.translator_enabled = false } - - it "should deny request to translate" do - response = post :translate, params: { post_id: 1 }, format: :json - - expect(response).to have_http_status(:not_found) - end - end - - describe "when enabled" do - let(:reply) { Fabricate(:post, user: poster) } - - it "raises an error with a missing parameter" do - post :translate, format: :json - expect(response).to have_http_status(:bad_request) - end - - it "raises the right error when post_id is invalid" do - post :translate, params: { post_id: -1 }, format: :json - expect(response).to have_http_status(:bad_request) - end - - it "raises the right error when post is inaccessible" do - user = log_in - mypost = Fabricate(:private_message_post) - post :translate, params: { post_id: mypost.id }, format: :json - expect(response.status).to eq(403) - end - - it "rescues translator errors" do - DiscourseTranslator::Microsoft.expects(:translate).raises( - ::DiscourseTranslator::TranslatorError, - ) - - post :translate, params: { post_id: reply.id }, format: :json - - expect(response).to have_http_status(:unprocessable_entity) - end - - describe "all groups can translate" do - include_examples "translation_successful" - end - - describe "user is in a allowlisted group" do - fab!(:admin) - - before do - SiteSetting.restrict_translation_by_group = - "#{Group.find_by(name: "admins").id}|not_in_the_list" - - log_in_user(admin) - end - - include_examples "translation_successful" - end - - describe "user is not in a allowlisted group" do - before do - SiteSetting.restrict_translation_by_group = "#{Group::AUTO_GROUPS[:moderators]}" - end - - include_examples "deny_request_to_translate" - end - - describe "restrict_translation_by_poster_group" do - fab!(:group) - fab!(:user) { Fabricate(:user, groups: [group]) } - - before do - SiteSetting.restrict_translation_by_group = "#{group.id}|" - - log_in_user(user) - end - describe "post made by an user in a allowlisted group" do - before do - SiteSetting.restrict_translation_by_poster_group = "#{poster.groups.first.id}" - end - include_examples "translation_successful" - end - - describe "post made by an user not in a allowlisted group" do - before do - SiteSetting.restrict_translation_by_poster_group = - "#{Group::AUTO_GROUPS[:moderators]}" - end - include_examples "deny_request_to_translate" - end - end - end - end - end -end diff --git a/spec/jobs/detect_posts_translation_spec.rb b/spec/jobs/detect_posts_language_spec.rb similarity index 95% rename from spec/jobs/detect_posts_translation_spec.rb rename to spec/jobs/detect_posts_language_spec.rb index eb578659..f0605c7b 100644 --- a/spec/jobs/detect_posts_translation_spec.rb +++ b/spec/jobs/detect_posts_language_spec.rb @@ -2,7 +2,7 @@ require "aws-sdk-translate" -describe Jobs::DetectPostsTranslation do +describe Jobs::DetectPostsLanguage do fab!(:posts) { Fabricate.times(5, :post) } let(:redis_key) { DiscourseTranslator::LANG_DETECT_NEEDED } @@ -47,7 +47,7 @@ described_class.new.execute({}) remaining = Discourse.redis.scard(redis_key) - expect(remaining).to eq(large_number - Jobs::DetectPostsTranslation::MAX_QUEUE_SIZE) + expect(remaining).to eq(large_number - Jobs::DetectPostsLanguage::MAX_QUEUE_SIZE) end it "handles an empty Redis queue gracefully" do diff --git a/spec/jobs/detect_translation_spec.rb b/spec/jobs/detect_translation_spec.rb deleted file mode 100644 index 7e48363e..00000000 --- a/spec/jobs/detect_translation_spec.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true - -require "aws-sdk-translate" - -describe Jobs::DetectTranslation do - before do - SiteSetting.translator = "Amazon" - client = Aws::Translate::Client.new(stub_responses: true) - client.stub_responses( - :translate_text, - { translated_text: "大丈夫", source_language_code: "en", target_language_code: "jp" }, - ) - Aws::Translate::Client.stubs(:new).returns(client) - end - - it "does not detect translation if translator disabled" do - SiteSetting.translator_enabled = false - - post = Fabricate(:post) - Jobs::DetectTranslation.new.execute(post_id: post.id) - - expect(post.custom_fields[DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD]).to be_nil - end - - describe "translator enabled" do - before { SiteSetting.translator_enabled = true } - - it "does not detect translation if post does not exist" do - post = Fabricate(:post) - post.destroy - - Jobs::DetectTranslation.new.execute(post_id: post.id) - - expect(post.custom_fields[DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD]).to be_nil - end - - it "detects translation" do - post = Fabricate(:post, raw: "this is a sample post") - - messages = MessageBus.track_publish { Jobs::DetectTranslation.new.execute(post_id: post.id) } - - expect(post.custom_fields[DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD]).to eq("en") - expect(messages.size).to eq(1) - end - - it "does not publish change if no change in translation" do - post = Fabricate(:post, raw: "this is a sample post") - post.custom_fields[DiscourseTranslator::DETECTED_LANG_CUSTOM_FIELD] = "en" - post.save_custom_fields - - messages = MessageBus.track_publish { Jobs::DetectTranslation.new.execute(post_id: post.id) } - expect(messages.size).to eq(0) - end - end -end diff --git a/spec/requests/translator_controller_spec.rb b/spec/requests/translator_controller_spec.rb new file mode 100644 index 00000000..e0356a0d --- /dev/null +++ b/spec/requests/translator_controller_spec.rb @@ -0,0 +1,149 @@ +# frozen_string_literal: true + +require "rails_helper" + +module DiscourseTranslator + describe TranslatorController do + fab!(:user) { Fabricate(:trust_level_1) } + + before do + SiteSetting.translator_enabled = true + SiteSetting.translator = "Microsoft" + SiteSetting.restrict_translation_by_group = "#{Group.find_by(name: "trust_level_1").id}" + end + + shared_examples "translation_successful" do + it "returns the translated text" do + DiscourseTranslator::Microsoft.expects(:translate).with(reply).returns(%w[ja ニャン猫]) + if reply.is_first_post? + DiscourseTranslator::Microsoft.expects(:translate).with(reply.topic).returns(%w[ja タイトル]) + end + + post "/translator/translate.json", params: { post_id: reply.id } + + expect(response).to have_http_status(:ok) + expect(response.body).to eq( + { translation: "ニャン猫", detected_lang: "ja", title_translation: "タイトル" }.to_json, + ) + end + end + + shared_examples "deny_request_to_translate" do + it "should deny request to translate" do + post "/translator/translate.json", params: { post_id: reply.id } + + expect(response).to have_http_status(:forbidden) + end + end + + describe "#translate" do + describe "anon user" do + it "should not allow translation of posts" do + post "/translator/translate.json", params: { post_id: 1 } + + expect(response).to have_http_status(:forbidden) + end + end + + describe "logged in user" do + before { sign_in(user) } + + let!(:poster) do + poster = Fabricate(:user) + poster.group_users << Fabricate(:group_user, user: user, group: Group[:trust_level_2]) + poster + end + + describe "when config translator_enabled disabled" do + before { SiteSetting.translator_enabled = false } + + it "should deny request to translate" do + post "/translator/translate.json", params: { post_id: 1 } + + expect(response).to have_http_status(:not_found) + end + end + + describe "when enabled" do + let(:reply) { Fabricate(:post, user: poster) } + + it "raises an error with a missing parameter" do + post "/translator/translate.json" + expect(response).to have_http_status(:bad_request) + end + + it "raises the right error when post_id is invalid" do + post "/translator/translate.json", params: { post_id: -1 } + expect(response).to have_http_status(:bad_request) + end + + it "raises the right error when post is inaccessible" do + mypost = Fabricate(:private_message_post) + post "/translator/translate.json", params: { post_id: mypost.id } + expect(response.status).to eq(403) + end + + it "rescues translator errors" do + DiscourseTranslator::Microsoft.expects(:translate).raises( + ::DiscourseTranslator::TranslatorError, + ) + + post "/translator/translate.json", params: { post_id: reply.id } + + expect(response).to have_http_status(:unprocessable_entity) + end + + describe "all groups can translate" do + include_examples "translation_successful" + end + + describe "user is in a allowlisted group" do + fab!(:admin) + + before do + SiteSetting.restrict_translation_by_group = + "#{Group.find_by(name: "admins").id}|not_in_the_list" + + log_in_user(admin) + end + + include_examples "translation_successful" + end + + describe "user is not in a allowlisted group" do + before do + SiteSetting.restrict_translation_by_group = "#{Group::AUTO_GROUPS[:moderators]}" + end + + include_examples "deny_request_to_translate" + end + + describe "restrict_translation_by_poster_group" do + fab!(:group) + fab!(:user) { Fabricate(:user, groups: [group]) } + + before do + SiteSetting.restrict_translation_by_group = "#{group.id}|" + + log_in_user(user) + end + describe "post made by an user in a allowlisted group" do + before do + SiteSetting.restrict_translation_by_poster_group = "#{poster.groups.first.id}" + end + include_examples "translation_successful" + end + + describe "post made by an user not in a allowlisted group" do + before do + SiteSetting.restrict_translation_by_poster_group = + "#{Group::AUTO_GROUPS[:moderators]}" + end + include_examples "deny_request_to_translate" + end + end + end + end + end + end +end diff --git a/spec/services/libretranslate_spec.rb b/spec/services/libre_translate_spec.rb similarity index 100% rename from spec/services/libretranslate_spec.rb rename to spec/services/libre_translate_spec.rb