FIX: ensures topic’s category allows topics tags (#7060)

FIX: ensures topic’s category allows topics tags (#7060)

diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb
index 11ddf19..16b2ea3 100644
--- a/app/controllers/topics_controller.rb
+++ b/app/controllers/topics_controller.rb
@@ -288,6 +288,18 @@ class TopicsController < ApplicationController
       else
         return render_json_error(I18n.t('category.errors.not_found'))
       end
+
+      if category && topic_tags = (params[:tags] || topic.tags.pluck(:name))
+        allowed_tags = category.tags.pluck(:name)
+
+        if topic_tags.present? && allowed_tags.present?
+          invalid_tags = topic_tags - allowed_tags
+
+          if !invalid_tags.empty?
+            return render_json_error(I18n.t('category.errors.disallowed_topic_tags', tags: invalid_tags.join(", ")))
+          end
+        end
+      end
     end
 
     changes = {}
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 0559c83..b520917 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -588,6 +588,7 @@ en:
       email_already_used_in_category: "'%{email}' is already used by the category '%{category_name}'."
       description_incomplete: "The category description post must have at least one paragraph."
       permission_conflict: "Subcategory permissions cannot be less restrictive than parent's."
+      disallowed_topic_tags: "This topic has tags not allowed by this category: '%{tags}'"
     cannot_delete:
       uncategorized: "Can't delete Uncategorized"
       has_subcategories: "Can't delete this category because it has sub-categories."
diff --git a/spec/requests/topics_controller_spec.rb b/spec/requests/topics_controller_spec.rb
index 022f68f..52fad18 100644
--- a/spec/requests/topics_controller_spec.rb
+++ b/spec/requests/topics_controller_spec.rb
@@ -1011,6 +1011,45 @@ RSpec.describe TopicsController do
           end
         end
 
+        context 'updating to a category with restricted tags' do
+          let!(:category) { Fabricate(:category) }
+          let!(:restricted_category) { Fabricate(:category) }
+          let!(:tag1) { Fabricate(:tag, name: 'tag1') }
+          let!(:tag2) { Fabricate(:tag, name: 'tag2') }
+
+          before do
+            SiteSetting.tagging_enabled = true
+            topic.update!(tags: [tag1])
+          end
+
+          it 'can change to a category disallowing this topic current tags' do
+            restricted_category.allowed_tags = [tag2.name]
+
+            put "/t/#{topic.slug}/#{topic.id}.json", params: { category_id: restricted_category.id }
+
+            result = ::JSON.parse(response.body)
+
+            expect(response.status).to eq(422)
+            expect(result['errors']).to be_present
+            expect(topic.reload.category_id).not_to eq(restricted_category.id)
+          end
+
+          it 'can change to a category allowing this topic current tags' do
+            restricted_category.allowed_tags = [tag1.name]
+            restricted_category.reload
+
+            put "/t/#{topic.slug}/#{topic.id}.json", params: { category_id: restricted_category.id }
+
+            expect(response.status).to eq(200)
+          end
+
+          it 'can change to a category allowing any tag' do
+            put "/t/#{topic.slug}/#{topic.id}.json", params: { category_id: category.id }
+
+            expect(response.status).to eq(200)
+          end
+        end
+
         context "allow_uncategorized_topics is false" do
           before do
             SiteSetting.allow_uncategorized_topics = false

GitHub sha: 7ccb0b88